138 lines
5.5 KiB
Python
138 lines
5.5 KiB
Python
import asyncio
|
|
import os
|
|
import shutil
|
|
from typing import Any
|
|
|
|
from agents import Agent, Runner, gen_trace_id, trace
|
|
from agents.mcp import MCPServer, MCPServerStdio
|
|
from agents.mcp.util import MCPUtil, create_static_tool_filter
|
|
from agents.run_context import RunContextWrapper
|
|
from examples.auto_mode import confirm_with_fallback, is_auto_mode
|
|
|
|
|
|
async def list_tools(server: MCPServer, *, convert_to_strict: bool) -> list[Any]:
|
|
"""Fetch all MCP tools from the server."""
|
|
|
|
run_context: RunContextWrapper[dict[str, str]] = RunContextWrapper(context={})
|
|
agent = Agent(name="ToolFetcher", instructions="Prefetch MCP tools.", mcp_servers=[server])
|
|
|
|
return await MCPUtil.get_all_function_tools(
|
|
[server],
|
|
convert_schemas_to_strict=convert_to_strict,
|
|
run_context=run_context,
|
|
agent=agent,
|
|
)
|
|
|
|
|
|
def prompt_user_approval(interruption_name: str) -> bool:
|
|
"""Ask the user to approve a tool call and return the decision."""
|
|
if is_auto_mode():
|
|
return confirm_with_fallback(
|
|
f"Approve tool call '{interruption_name}'? (y/n): ",
|
|
default=True,
|
|
)
|
|
while True:
|
|
user_input = input(f"Approve tool call '{interruption_name}'? (y/n): ").strip().lower()
|
|
if user_input == "y":
|
|
return True
|
|
if user_input == "n":
|
|
return False
|
|
print("Please enter 'y' or 'n'.")
|
|
|
|
|
|
async def resolve_interruptions(agent: Agent, result: Any) -> Any:
|
|
"""Prompt for approvals until no interruptions remain."""
|
|
current_result = result
|
|
while current_result.interruptions:
|
|
state = current_result.to_state()
|
|
# Human in the loop: prompt for approval on each tool call.
|
|
for interruption in current_result.interruptions:
|
|
if prompt_user_approval(interruption.name):
|
|
print(f"Approving a tool call... (name: {interruption.name})")
|
|
state.approve(interruption)
|
|
else:
|
|
print(f"Rejecting a tool call... (name: {interruption.name})")
|
|
state.reject(interruption)
|
|
current_result = await Runner.run(agent, state)
|
|
return current_result
|
|
|
|
|
|
async def main():
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
samples_dir = os.path.join(current_dir, "sample_files")
|
|
blocked_path = os.path.join(samples_dir, "test.txt")
|
|
|
|
async with MCPServerStdio(
|
|
name="Filesystem Server",
|
|
params={
|
|
"command": "npx",
|
|
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
|
|
"cwd": samples_dir,
|
|
},
|
|
require_approval={"always": {"tool_names": ["read_text_file"]}},
|
|
) as server:
|
|
trace_id = gen_trace_id()
|
|
with trace(workflow_name="MCP get_all_mcp_tools Example", trace_id=trace_id):
|
|
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
|
|
|
print("=== Fetching all tools with strict schemas ===")
|
|
all_tools = await list_tools(server, convert_to_strict=True)
|
|
print(f"Found {len(all_tools)} tool(s):")
|
|
for tool in all_tools:
|
|
description = getattr(tool, "description", "") or ""
|
|
print(f"- {tool.name}: {description}")
|
|
|
|
# Build an agent that uses the prefetched tools instead of mcp_servers.
|
|
prefetched_agent = Agent(
|
|
name="Prefetched MCP Assistant",
|
|
instructions=(
|
|
"Use the prefetched tools to help with file questions. "
|
|
"When using path arguments, prefer absolute paths in the allowed directory."
|
|
),
|
|
tools=all_tools,
|
|
)
|
|
message = (
|
|
f"List files in this allowed directory: {samples_dir}. "
|
|
"Then read one of those files."
|
|
)
|
|
print(f"\nRunning: {message}\n")
|
|
result = await Runner.run(prefetched_agent, message)
|
|
result = await resolve_interruptions(prefetched_agent, result)
|
|
print(result.final_output)
|
|
|
|
# Apply a static tool filter and refetch tools.
|
|
server.tool_filter = create_static_tool_filter(
|
|
allowed_tool_names=["read_file", "list_directory"]
|
|
)
|
|
filtered_tools = await list_tools(server, convert_to_strict=False)
|
|
|
|
print("\n=== After applying tool filter ===")
|
|
print(f"Found {len(filtered_tools)} tool(s):")
|
|
for tool in filtered_tools:
|
|
print(f"- {tool.name}")
|
|
|
|
filtered_agent = Agent(
|
|
name="Filtered MCP Assistant",
|
|
instructions=(
|
|
"Use the filtered tools to respond. "
|
|
"If a request requires a missing tool, explain that the capability is not "
|
|
"available."
|
|
),
|
|
tools=filtered_tools,
|
|
)
|
|
blocked_message = (
|
|
f'Create a file named "{blocked_path}" with the text "hello". '
|
|
"If the available tools cannot create files, explain that clearly."
|
|
)
|
|
print(f"\nRunning: {blocked_message}\n")
|
|
filtered_result = await Runner.run(filtered_agent, blocked_message)
|
|
filtered_result = await resolve_interruptions(filtered_agent, filtered_result)
|
|
print(filtered_result.final_output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if not shutil.which("npx"):
|
|
raise RuntimeError("npx is required. Install it with `npm install -g npx`.")
|
|
|
|
asyncio.run(main())
|