chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# MCP Filesystem Example
|
||||
|
||||
This example uses the [filesystem MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), running locally via `npx`.
|
||||
|
||||
Run it via:
|
||||
|
||||
```
|
||||
uv run python examples/mcp/filesystem_example/main.py
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
The example uses the `MCPServerStdio` class from `agents.mcp`, with the command:
|
||||
|
||||
```bash
|
||||
npx -y "@modelcontextprotocol/server-filesystem" <samples_directory>
|
||||
```
|
||||
|
||||
It's only given access to the `sample_files` directory adjacent to the example, which contains some sample data.
|
||||
|
||||
Under the hood:
|
||||
|
||||
1. The server is spun up in a subprocess, and exposes a bunch of tools like `list_directory()`, `read_file()`, etc.
|
||||
2. We add the server instance to the Agent via `mcp_agents`.
|
||||
3. Each time the agent runs, we call out to the MCP server to fetch the list of tools via `server.list_tools()`.
|
||||
4. If the LLM chooses to use an MCP tool, we call the MCP server to run the tool via `server.run_tool()`.
|
||||
@@ -0,0 +1,57 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServer, MCPServerStdio
|
||||
|
||||
|
||||
async def run(mcp_server: MCPServer):
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the tools to read the filesystem and answer questions based on those files.",
|
||||
mcp_servers=[mcp_server],
|
||||
)
|
||||
|
||||
# List the files it can read
|
||||
message = "Read the files and list them."
|
||||
print(f"Running: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
# Ask about books
|
||||
message = "Read favorite_books.txt and tell me my #1 favorite book."
|
||||
print(f"\n\nRunning: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
# Ask a question that reads then reasons.
|
||||
message = "Read favorite_songs.txt and suggest one new song that I might like."
|
||||
print(f"\n\nRunning: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def main():
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
samples_dir = os.path.join(current_dir, "sample_files")
|
||||
|
||||
async with MCPServerStdio(
|
||||
name="Filesystem Server, via npx",
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
|
||||
},
|
||||
) as server:
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="MCP Filesystem Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
await run(server)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Let's make sure the user has npx installed
|
||||
if not shutil.which("npx"):
|
||||
raise RuntimeError("npx is not installed. Please install it with `npm install -g npx`.")
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,20 @@
|
||||
1. To Kill a Mockingbird – Harper Lee
|
||||
2. Pride and Prejudice – Jane Austen
|
||||
3. 1984 – George Orwell
|
||||
4. The Hobbit – J.R.R. Tolkien
|
||||
5. Harry Potter and the Sorcerer’s Stone – J.K. Rowling
|
||||
6. The Great Gatsby – F. Scott Fitzgerald
|
||||
7. Charlotte’s Web – E.B. White
|
||||
8. Anne of Green Gables – Lucy Maud Montgomery
|
||||
9. The Alchemist – Paulo Coelho
|
||||
10. Little Women – Louisa May Alcott
|
||||
11. The Catcher in the Rye – J.D. Salinger
|
||||
12. Animal Farm – George Orwell
|
||||
13. The Chronicles of Narnia: The Lion, the Witch, and the Wardrobe – C.S. Lewis
|
||||
14. The Book Thief – Markus Zusak
|
||||
15. A Wrinkle in Time – Madeleine L’Engle
|
||||
16. The Secret Garden – Frances Hodgson Burnett
|
||||
17. Moby-Dick – Herman Melville
|
||||
18. Fahrenheit 451 – Ray Bradbury
|
||||
19. Jane Eyre – Charlotte Brontë
|
||||
20. The Little Prince – Antoine de Saint-Exupéry
|
||||
@@ -0,0 +1,4 @@
|
||||
- In the summer, I love visiting London.
|
||||
- In the winter, Tokyo is great.
|
||||
- In the spring, San Francisco.
|
||||
- In the fall, New York is the best.
|
||||
@@ -0,0 +1,10 @@
|
||||
1. "Here Comes the Sun" – The Beatles
|
||||
2. "Imagine" – John Lennon
|
||||
3. "Bohemian Rhapsody" – Queen
|
||||
4. "Shake It Off" – Taylor Swift
|
||||
5. "Billie Jean" – Michael Jackson
|
||||
6. "Uptown Funk" – Mark Ronson ft. Bruno Mars
|
||||
7. "Don’t Stop Believin’" – Journey
|
||||
8. "Dancing Queen" – ABBA
|
||||
9. "Happy" – Pharrell Williams
|
||||
10. "Wonderwall" – Oasis
|
||||
@@ -0,0 +1,20 @@
|
||||
# MCP get_all_mcp_tools Example
|
||||
|
||||
Python port of the JS `examples/mcp/get-all-mcp-tools-example.ts`. It demonstrates:
|
||||
|
||||
- Spinning up a local filesystem MCP server via `npx`.
|
||||
- Prefetching all MCP tools with `MCPUtil.get_all_function_tools`.
|
||||
- Building an agent that uses those prefetched tools instead of `mcp_servers`.
|
||||
- Applying a static tool filter and refetching tools.
|
||||
- Enabling `require_approval="always"` on the server and auto-approving interruptions in code to exercise the HITL path.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
uv run python examples/mcp/get_all_mcp_tools_example/main.py
|
||||
```
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- `npx` available on your PATH.
|
||||
- `OPENAI_API_KEY` set for the model calls.
|
||||
@@ -0,0 +1,137 @@
|
||||
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())
|
||||
@@ -0,0 +1,20 @@
|
||||
1. To Kill a Mockingbird – Harper Lee
|
||||
2. Pride and Prejudice – Jane Austen
|
||||
3. 1984 – George Orwell
|
||||
4. The Hobbit – J.R.R. Tolkien
|
||||
5. Harry Potter and the Sorcerer’s Stone – J.K. Rowling
|
||||
6. The Great Gatsby – F. Scott Fitzgerald
|
||||
7. Charlotte’s Web – E.B. White
|
||||
8. Anne of Green Gables – Lucy Maud Montgomery
|
||||
9. The Alchemist – Paulo Coelho
|
||||
10. Little Women – Louisa May Alcott
|
||||
11. The Catcher in the Rye – J.D. Salinger
|
||||
12. Animal Farm – George Orwell
|
||||
13. The Chronicles of Narnia: The Lion, the Witch, and the Wardrobe – C.S. Lewis
|
||||
14. The Book Thief – Markus Zusak
|
||||
15. A Wrinkle in Time – Madeleine L’Engle
|
||||
16. The Secret Garden – Frances Hodgson Burnett
|
||||
17. Moby-Dick – Herman Melville
|
||||
18. Fahrenheit 451 – Ray Bradbury
|
||||
19. Jane Eyre – Charlotte Brontë
|
||||
20. The Little Prince – Antoine de Saint-Exupéry
|
||||
@@ -0,0 +1,10 @@
|
||||
1. "Here Comes the Sun" – The Beatles
|
||||
2. "Imagine" – John Lennon
|
||||
3. "Bohemian Rhapsody" – Queen
|
||||
4. "Shake It Off" – Taylor Swift
|
||||
5. "Billie Jean" – Michael Jackson
|
||||
6. "Uptown Funk" – Mark Ronson ft. Bruno Mars
|
||||
7. "Don’t Stop Believin’" – Journey
|
||||
8. "Dancing Queen" – ABBA
|
||||
9. "Happy" – Pharrell Williams
|
||||
10. "Wonderwall" – Oasis
|
||||
@@ -0,0 +1,26 @@
|
||||
# MCP Git Example
|
||||
|
||||
This example uses the [git MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/git), running locally via `uvx`.
|
||||
|
||||
Run it via:
|
||||
|
||||
```
|
||||
uv run python examples/mcp/git_example/main.py
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
The example uses the `MCPServerStdio` class from `agents.mcp`, with the command:
|
||||
|
||||
```bash
|
||||
uvx mcp-server-git
|
||||
```
|
||||
|
||||
Prior to running the agent, the user is prompted to provide a local directory path to their git repo. Using that, the Agent can invoke Git MCP tools like `git_log` to inspect the git commit log.
|
||||
|
||||
Under the hood:
|
||||
|
||||
1. The server is spun up in a subprocess, and exposes a bunch of tools like `git_log()`
|
||||
2. We add the server instance to the Agent via `mcp_agents`.
|
||||
3. Each time the agent runs, we call out to the MCP server to fetch the list of tools via `server.list_tools()`. The result is cached.
|
||||
4. If the LLM chooses to use an MCP tool, we call the MCP server to run the tool via `server.run_tool()`.
|
||||
@@ -0,0 +1,48 @@
|
||||
import asyncio
|
||||
import shutil
|
||||
|
||||
from agents import Agent, Runner, trace
|
||||
from agents.mcp import MCPServer, MCPServerStdio
|
||||
from examples.auto_mode import input_with_fallback
|
||||
|
||||
|
||||
async def run(mcp_server: MCPServer, directory_path: str):
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions=f"Answer questions about the git repository at {directory_path}, use that for repo_path",
|
||||
mcp_servers=[mcp_server],
|
||||
)
|
||||
|
||||
message = "Who's the most frequent contributor?"
|
||||
print("\n" + "-" * 40)
|
||||
print(f"Running: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
message = "Summarize the last change in the repository."
|
||||
print("\n" + "-" * 40)
|
||||
print(f"Running: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def main():
|
||||
# Ask the user for the directory path
|
||||
directory_path = input_with_fallback(
|
||||
"Please enter the path to the git repository: ",
|
||||
".",
|
||||
)
|
||||
|
||||
async with MCPServerStdio(
|
||||
cache_tools_list=True, # Cache the tools list, for demonstration
|
||||
params={"command": "uvx", "args": ["mcp-server-git"]},
|
||||
) as server:
|
||||
with trace(workflow_name="MCP Git Example"):
|
||||
await run(server, directory_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not shutil.which("uvx"):
|
||||
raise RuntimeError("uvx is not installed. Please install it with `pip install uvx`.")
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,78 @@
|
||||
# MCP Manager Example (FastAPI)
|
||||
|
||||
This example shows how to use `MCPServerManager` to keep MCP server lifecycle management in a single task inside a FastAPI app with the Streamable HTTP transport.
|
||||
|
||||
## Run the MCP server (Streamable HTTP)
|
||||
|
||||
```
|
||||
uv run python examples/mcp/manager_example/mcp_server.py
|
||||
```
|
||||
|
||||
The server listens at `http://localhost:8000/mcp` by default.
|
||||
|
||||
You can override the host/port with:
|
||||
|
||||
```
|
||||
export STREAMABLE_HTTP_HOST=127.0.0.1
|
||||
export STREAMABLE_HTTP_PORT=8000
|
||||
```
|
||||
|
||||
This example also configures an inactive MCP server at `http://localhost:8001/mcp` to demonstrate how the manager drops failed
|
||||
servers. You can override it with:
|
||||
|
||||
```
|
||||
export INACTIVE_MCP_SERVER_URL=http://localhost:8001/mcp
|
||||
```
|
||||
|
||||
## Run the FastAPI app
|
||||
|
||||
```
|
||||
uv run python examples/mcp/manager_example/app.py
|
||||
```
|
||||
|
||||
The app listens at `http://127.0.0.1:9001`.
|
||||
|
||||
## Run the smoke test
|
||||
|
||||
To verify the MCP manager and app integration without calling a model:
|
||||
|
||||
```
|
||||
uv run python -m examples.mcp.manager_example.smoke_test
|
||||
```
|
||||
|
||||
The smoke test starts the local MCP server on a temporary port, points both app MCP server settings at that server, and checks `/health`, `/tools`, and `/add`.
|
||||
|
||||
## Toggle MCP manager usage
|
||||
|
||||
By default, the app uses `MCPServerManager`. To disable it:
|
||||
|
||||
```
|
||||
export USE_MCP_MANAGER=0
|
||||
```
|
||||
|
||||
## Try the endpoints
|
||||
|
||||
```
|
||||
curl http://127.0.0.1:9001/health
|
||||
curl http://127.0.0.1:9001/tools
|
||||
curl -X POST http://127.0.0.1:9001/add \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"a": 2, "b": 3}'
|
||||
```
|
||||
|
||||
Reconnect failed MCP servers (manager must be enabled):
|
||||
|
||||
```
|
||||
curl -X POST http://127.0.0.1:9001/reconnect \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"failed_only": true}'
|
||||
```
|
||||
|
||||
To use `/run`, set `OPENAI_API_KEY`:
|
||||
|
||||
```
|
||||
export OPENAI_API_KEY=...
|
||||
curl -X POST http://127.0.0.1:9001/run \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"input": "Add 4 and 9."}'
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServer, MCPServerManager, MCPServerStreamableHttp
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", "http://localhost:8000/mcp")
|
||||
INACTIVE_MCP_SERVER_URL = os.getenv("INACTIVE_MCP_SERVER_URL", "http://localhost:8001/mcp")
|
||||
APP_HOST = "127.0.0.1"
|
||||
APP_PORT = 9001
|
||||
USE_MCP_MANAGER = os.getenv("USE_MCP_MANAGER", "1") != "0"
|
||||
|
||||
|
||||
class AddRequest(BaseModel):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
|
||||
class RunRequest(BaseModel):
|
||||
input: str
|
||||
|
||||
|
||||
class ReconnectRequest(BaseModel):
|
||||
failed_only: bool = True
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
server = MCPServerStreamableHttp({"url": MCP_SERVER_URL})
|
||||
inactive_server = MCPServerStreamableHttp({"url": INACTIVE_MCP_SERVER_URL})
|
||||
servers = [server, inactive_server]
|
||||
if USE_MCP_MANAGER:
|
||||
async with MCPServerManager(
|
||||
servers=servers,
|
||||
connect_in_parallel=True,
|
||||
) as manager:
|
||||
app.state.mcp_manager = manager
|
||||
app.state.mcp_servers = servers
|
||||
yield
|
||||
return
|
||||
|
||||
await server.connect()
|
||||
app.state.mcp_servers = servers
|
||||
app.state.active_servers = [server]
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await server.cleanup()
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> dict[str, object]:
|
||||
if USE_MCP_MANAGER:
|
||||
manager: MCPServerManager = app.state.mcp_manager
|
||||
return {
|
||||
"connected_servers": [server.name for server in manager.active_servers],
|
||||
"failed_servers": [server.name for server in manager.failed_servers],
|
||||
}
|
||||
|
||||
active_servers = _get_active_servers()
|
||||
return {
|
||||
"connected_servers": [server.name for server in active_servers],
|
||||
"failed_servers": [],
|
||||
}
|
||||
|
||||
|
||||
@app.get("/tools")
|
||||
async def list_tools() -> dict[str, object]:
|
||||
active_servers = _get_active_servers()
|
||||
if not active_servers:
|
||||
return {"tools": []}
|
||||
tools = await active_servers[0].list_tools()
|
||||
return {"tools": [tool.name for tool in tools]}
|
||||
|
||||
|
||||
@app.post("/add")
|
||||
async def add(req: AddRequest) -> dict[str, object]:
|
||||
active_servers = _get_active_servers()
|
||||
if not active_servers:
|
||||
raise HTTPException(status_code=503, detail="No MCP servers available")
|
||||
result = await active_servers[0].call_tool("add", {"a": req.a, "b": req.b})
|
||||
return {"result": result.model_dump(mode="json")}
|
||||
|
||||
|
||||
@app.post("/run")
|
||||
async def run_agent(req: RunRequest) -> dict[str, object]:
|
||||
if not os.getenv("OPENAI_API_KEY"):
|
||||
raise HTTPException(status_code=400, detail="OPENAI_API_KEY is required")
|
||||
|
||||
servers = _get_active_servers()
|
||||
if not servers:
|
||||
raise HTTPException(status_code=503, detail="No MCP servers available")
|
||||
|
||||
agent = Agent(
|
||||
name="FastAPI Agent",
|
||||
instructions="Use the MCP tools when needed.",
|
||||
mcp_servers=servers,
|
||||
model_settings=ModelSettings(tool_choice="auto"),
|
||||
)
|
||||
result = await Runner.run(starting_agent=agent, input=req.input)
|
||||
return {"output": result.final_output}
|
||||
|
||||
|
||||
@app.post("/reconnect")
|
||||
async def reconnect(req: ReconnectRequest) -> dict[str, object]:
|
||||
if not USE_MCP_MANAGER:
|
||||
raise HTTPException(status_code=400, detail="MCPServerManager is disabled")
|
||||
manager: MCPServerManager = app.state.mcp_manager
|
||||
servers = await manager.reconnect(failed_only=req.failed_only)
|
||||
return {"connected_servers": [server.name for server in servers]}
|
||||
|
||||
|
||||
def _get_active_servers() -> list[MCPServer]:
|
||||
if USE_MCP_MANAGER:
|
||||
manager: MCPServerManager = app.state.mcp_manager
|
||||
return list(manager.active_servers)
|
||||
return list(app.state.active_servers)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host=APP_HOST, port=APP_PORT)
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
STREAMABLE_HTTP_PORT = int(os.getenv("STREAMABLE_HTTP_PORT", "8000"))
|
||||
|
||||
mcp = FastMCP(
|
||||
"FastAPI Example Server",
|
||||
host=STREAMABLE_HTTP_HOST,
|
||||
port=STREAMABLE_HTTP_PORT,
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def echo(message: str) -> str:
|
||||
return f"echo: {message}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
@@ -0,0 +1,144 @@
|
||||
"""Smoke test for the MCP manager example app.
|
||||
|
||||
This script starts the sibling Streamable HTTP MCP server on a temporary local
|
||||
port, loads the app with matching environment variables, and verifies the
|
||||
manager-backed endpoints without calling a model.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT_WAIT_SECONDS = 10.0
|
||||
|
||||
|
||||
def _free_port() -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind((HOST, 0))
|
||||
return int(sock.getsockname()[1])
|
||||
|
||||
|
||||
def _wait_for_port(process: subprocess.Popen[str], port: int) -> None:
|
||||
deadline = time.monotonic() + PORT_WAIT_SECONDS
|
||||
while time.monotonic() < deadline:
|
||||
if process.poll() is not None:
|
||||
output, _ = process.communicate(timeout=1)
|
||||
raise RuntimeError(f"MCP server exited before it was ready:\n{output}")
|
||||
try:
|
||||
with socket.create_connection((HOST, port), timeout=0.2):
|
||||
return
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError(f"MCP server did not listen on {HOST}:{port}.")
|
||||
|
||||
|
||||
def _stop_process(process: subprocess.Popen[str]) -> str:
|
||||
if process.poll() is not None:
|
||||
output, _ = process.communicate(timeout=1)
|
||||
return output
|
||||
|
||||
process.terminate()
|
||||
try:
|
||||
output, _ = process.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
output, _ = process.communicate(timeout=5)
|
||||
return output
|
||||
|
||||
|
||||
def _start_mcp_server(port: int) -> subprocess.Popen[str]:
|
||||
env = {
|
||||
**os.environ,
|
||||
"STREAMABLE_HTTP_HOST": HOST,
|
||||
"STREAMABLE_HTTP_PORT": str(port),
|
||||
}
|
||||
return subprocess.Popen(
|
||||
[sys.executable, "-u", "-m", "examples.mcp.manager_example.mcp_server"],
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def _load_app_module(mcp_port: int) -> Any:
|
||||
mcp_server_url = f"http://{HOST}:{mcp_port}/mcp"
|
||||
os.environ["MCP_SERVER_URL"] = mcp_server_url
|
||||
# Point both configured MCP servers at the same temporary server so this
|
||||
# smoke test stays on the clean app integration path.
|
||||
os.environ["INACTIVE_MCP_SERVER_URL"] = mcp_server_url
|
||||
os.environ["USE_MCP_MANAGER"] = "1"
|
||||
|
||||
module_name = "examples.mcp.manager_example.app"
|
||||
if module_name in sys.modules:
|
||||
module = importlib.reload(sys.modules[module_name])
|
||||
else:
|
||||
module = importlib.import_module(module_name)
|
||||
return cast(Any, module)
|
||||
|
||||
|
||||
def _require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
async def _exercise_app(mcp_port: int) -> None:
|
||||
app_module = _load_app_module(mcp_port)
|
||||
app = app_module.app
|
||||
expected_server_url = f"http://{HOST}:{mcp_port}/mcp"
|
||||
|
||||
async with app.router.lifespan_context(app):
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
health_response = await client.get("/health")
|
||||
health_response.raise_for_status()
|
||||
health = health_response.json()
|
||||
_require(
|
||||
any(expected_server_url in name for name in health["connected_servers"]),
|
||||
f"Expected connected MCP server in health response: {health}",
|
||||
)
|
||||
_require(
|
||||
health["failed_servers"] == [],
|
||||
f"Expected no failed MCP servers in health response: {health}",
|
||||
)
|
||||
|
||||
tools_response = await client.get("/tools")
|
||||
tools_response.raise_for_status()
|
||||
tools = tools_response.json()["tools"]
|
||||
_require({"add", "echo"} <= set(tools), f"Expected add and echo tools: {tools}")
|
||||
|
||||
add_response = await client.post("/add", json={"a": 2, "b": 3})
|
||||
add_response.raise_for_status()
|
||||
add_result = add_response.json()["result"]
|
||||
texts = [
|
||||
item.get("text")
|
||||
for item in add_result.get("content", [])
|
||||
if item.get("type") == "text"
|
||||
]
|
||||
_require("5" in texts, f"Expected add tool result to include 5: {add_result}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
mcp_port = _free_port()
|
||||
process = _start_mcp_server(mcp_port)
|
||||
try:
|
||||
_wait_for_port(process, mcp_port)
|
||||
await _exercise_app(mcp_port)
|
||||
finally:
|
||||
_stop_process(process)
|
||||
|
||||
print("MCP manager example smoke test completed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,29 @@
|
||||
# MCP Prompt Server Example
|
||||
|
||||
This example uses a local MCP prompt server in [server.py](server.py).
|
||||
|
||||
Run the example via:
|
||||
|
||||
```
|
||||
uv run python examples/mcp/prompt_server/main.py
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
The example uses the `MCPServerStreamableHttp` class from `agents.mcp`. The script auto-selects an open localhost port (or honors `STREAMABLE_HTTP_PORT`) and runs the server at `http://<host>:<port>/mcp`, providing user-controlled prompts that generate agent instructions. If you need a specific address, set `STREAMABLE_HTTP_PORT` and `STREAMABLE_HTTP_HOST`.
|
||||
|
||||
The server exposes prompts like `generate_code_review_instructions` that take parameters such as focus area and programming language. The agent calls these prompts to dynamically generate its system instructions based on user-provided parameters.
|
||||
|
||||
## Workflow
|
||||
|
||||
The example demonstrates two key functions:
|
||||
|
||||
1. **`show_available_prompts`** - Lists all available prompts on the MCP server, showing users what prompts they can select from. This demonstrates the discovery aspect of MCP prompts.
|
||||
|
||||
2. **`demo_code_review`** - Shows the complete user-controlled prompt workflow:
|
||||
- Calls `generate_code_review_instructions` with specific parameters (focus: "security vulnerabilities", language: "python")
|
||||
- Uses the generated instructions to create an Agent with specialized code review capabilities
|
||||
- Runs the agent against vulnerable sample code (command injection via `os.system`)
|
||||
- The agent analyzes the code and provides security-focused feedback using available tools
|
||||
|
||||
This pattern allows users to dynamically configure agent behavior through MCP prompts rather than hardcoded instructions.
|
||||
@@ -0,0 +1,131 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServer, MCPServerStreamableHttp
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
|
||||
|
||||
def _choose_port() -> int:
|
||||
env_port = os.getenv("STREAMABLE_HTTP_PORT")
|
||||
if env_port:
|
||||
return int(env_port)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((STREAMABLE_HTTP_HOST, 0))
|
||||
address = cast(tuple[str, int], s.getsockname())
|
||||
return address[1]
|
||||
|
||||
|
||||
STREAMABLE_HTTP_PORT = _choose_port()
|
||||
os.environ.setdefault("STREAMABLE_HTTP_PORT", str(STREAMABLE_HTTP_PORT))
|
||||
STREAMABLE_HTTP_URL = f"http://{STREAMABLE_HTTP_HOST}:{STREAMABLE_HTTP_PORT}/mcp"
|
||||
|
||||
|
||||
async def get_instructions_from_prompt(mcp_server: MCPServer, prompt_name: str, **kwargs) -> str:
|
||||
"""Get agent instructions by calling MCP prompt endpoint (user-controlled)"""
|
||||
print(f"Getting instructions from prompt: {prompt_name}")
|
||||
|
||||
try:
|
||||
prompt_result = await mcp_server.get_prompt(prompt_name, kwargs)
|
||||
content = prompt_result.messages[0].content
|
||||
if hasattr(content, "text"):
|
||||
instructions = content.text
|
||||
else:
|
||||
instructions = str(content)
|
||||
print("Generated instructions")
|
||||
return instructions
|
||||
except Exception as e:
|
||||
print(f"Failed to get instructions: {e}")
|
||||
return f"You are a helpful assistant. Error: {e}"
|
||||
|
||||
|
||||
async def demo_code_review(mcp_server: MCPServer):
|
||||
"""Demo: Code review with user-selected prompt"""
|
||||
print("=== CODE REVIEW DEMO ===")
|
||||
|
||||
# User explicitly selects prompt and parameters
|
||||
instructions = await get_instructions_from_prompt(
|
||||
mcp_server,
|
||||
"generate_code_review_instructions",
|
||||
focus="security vulnerabilities",
|
||||
language="python",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Code Reviewer Agent",
|
||||
instructions=instructions, # Instructions from MCP prompt
|
||||
model_settings=ModelSettings(tool_choice="auto"),
|
||||
)
|
||||
|
||||
message = """Please review this code:
|
||||
|
||||
def process_user_input(user_input):
|
||||
command = f"echo {user_input}"
|
||||
os.system(command)
|
||||
return "Command executed"
|
||||
|
||||
"""
|
||||
|
||||
print(f"Running: {message[:60]}...")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
print("\n" + "=" * 50 + "\n")
|
||||
|
||||
|
||||
async def show_available_prompts(mcp_server: MCPServer):
|
||||
"""Show available prompts for user selection"""
|
||||
print("=== AVAILABLE PROMPTS ===")
|
||||
|
||||
prompts_result = await mcp_server.list_prompts()
|
||||
print("User can select from these prompts:")
|
||||
for i, prompt in enumerate(prompts_result.prompts, 1):
|
||||
print(f" {i}. {prompt.name} - {prompt.description}")
|
||||
print()
|
||||
|
||||
|
||||
async def main():
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Simple Prompt Server",
|
||||
params={"url": STREAMABLE_HTTP_URL},
|
||||
) as server:
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="Simple Prompt Demo", trace_id=trace_id):
|
||||
print(f"Trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
|
||||
await show_available_prompts(server)
|
||||
await demo_code_review(server)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not shutil.which("uv"):
|
||||
raise RuntimeError("uv is not installed")
|
||||
|
||||
process: subprocess.Popen[Any] | None = None
|
||||
try:
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_file = os.path.join(this_dir, "server.py")
|
||||
|
||||
print(f"Starting Simple Prompt Server at {STREAMABLE_HTTP_URL} ...")
|
||||
env = os.environ.copy()
|
||||
env.setdefault("STREAMABLE_HTTP_HOST", STREAMABLE_HTTP_HOST)
|
||||
env.setdefault("STREAMABLE_HTTP_PORT", str(STREAMABLE_HTTP_PORT))
|
||||
process = subprocess.Popen(["uv", "run", server_file], env=env)
|
||||
time.sleep(3)
|
||||
print("Server started\n")
|
||||
except Exception as e:
|
||||
print(f"Error starting server: {e}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
finally:
|
||||
if process:
|
||||
process.terminate()
|
||||
print("Server terminated.")
|
||||
@@ -0,0 +1,42 @@
|
||||
import os
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
STREAMABLE_HTTP_PORT = int(os.getenv("STREAMABLE_HTTP_PORT", "18080"))
|
||||
|
||||
# Create server
|
||||
mcp = FastMCP("Prompt Server", host=STREAMABLE_HTTP_HOST, port=STREAMABLE_HTTP_PORT)
|
||||
|
||||
|
||||
# Instruction-generating prompts (user-controlled)
|
||||
@mcp.prompt()
|
||||
def generate_code_review_instructions(
|
||||
focus: str = "general code quality", language: str = "python"
|
||||
) -> str:
|
||||
"""Generate agent instructions for code review tasks"""
|
||||
print(f"[debug-server] generate_code_review_instructions({focus}, {language})")
|
||||
|
||||
return f"""You are a senior {language} code review specialist. Your role is to provide comprehensive code analysis with focus on {focus}.
|
||||
|
||||
INSTRUCTIONS:
|
||||
- Analyze code for quality, security, performance, and best practices
|
||||
- Provide specific, actionable feedback with examples
|
||||
- Identify potential bugs, vulnerabilities, and optimization opportunities
|
||||
- Suggest improvements with code examples when applicable
|
||||
- Be constructive and educational in your feedback
|
||||
- Focus particularly on {focus} aspects
|
||||
|
||||
RESPONSE FORMAT:
|
||||
1. Overall Assessment
|
||||
2. Specific Issues Found
|
||||
3. Security Considerations
|
||||
4. Performance Notes
|
||||
5. Recommended Improvements
|
||||
6. Best Practices Suggestions
|
||||
|
||||
Use the available tools to check current time if you need timestamps for your analysis."""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
@@ -0,0 +1,13 @@
|
||||
# MCP SSE Example
|
||||
|
||||
This example uses a local SSE server in [server.py](server.py).
|
||||
|
||||
Run the example via:
|
||||
|
||||
```
|
||||
uv run python examples/mcp/sse_example/main.py
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
The example uses the `MCPServerSse` class from `agents.mcp`. The server runs in a sub-process at `https://localhost:8000/sse`.
|
||||
@@ -0,0 +1,104 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServer, MCPServerSse
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
SSE_HOST = os.getenv("SSE_HOST", "127.0.0.1")
|
||||
|
||||
|
||||
def _choose_port() -> int:
|
||||
env_port = os.getenv("SSE_PORT")
|
||||
if env_port:
|
||||
return int(env_port)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((SSE_HOST, 0))
|
||||
address = cast(tuple[str, int], s.getsockname())
|
||||
return address[1]
|
||||
|
||||
|
||||
SSE_PORT = _choose_port()
|
||||
os.environ.setdefault("SSE_PORT", str(SSE_PORT))
|
||||
SSE_URL = f"http://{SSE_HOST}:{SSE_PORT}/sse"
|
||||
|
||||
|
||||
async def run(mcp_server: MCPServer):
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the tools to answer the questions.",
|
||||
mcp_servers=[mcp_server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
# Use the `add` tool to add two numbers
|
||||
message = "Add these numbers: 7 and 22."
|
||||
print(f"Running: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
# Run the `get_weather` tool
|
||||
message = "What's the weather in Tokyo?"
|
||||
print(f"\n\nRunning: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
# Run the `get_secret_word` tool
|
||||
message = "What's the secret word?"
|
||||
print(f"\n\nRunning: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def main():
|
||||
async with MCPServerSse(
|
||||
name="SSE Python Server",
|
||||
params={
|
||||
"url": SSE_URL,
|
||||
},
|
||||
) as server:
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="SSE Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
await run(server)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Let's make sure the user has uv installed
|
||||
if not shutil.which("uv"):
|
||||
raise RuntimeError(
|
||||
"uv is not installed. Please install it: https://docs.astral.sh/uv/getting-started/installation/"
|
||||
)
|
||||
|
||||
# We'll run the SSE server in a subprocess. Usually this would be a remote server, but for this
|
||||
# demo, we'll run it locally at SSE_URL.
|
||||
process: subprocess.Popen[Any] | None = None
|
||||
try:
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_file = os.path.join(this_dir, "server.py")
|
||||
|
||||
print(f"Starting SSE server at {SSE_URL} ...")
|
||||
|
||||
# Run `uv run server.py` to start the SSE server
|
||||
env = os.environ.copy()
|
||||
env.setdefault("SSE_HOST", SSE_HOST)
|
||||
env.setdefault("SSE_PORT", str(SSE_PORT))
|
||||
process = subprocess.Popen(["uv", "run", server_file], env=env)
|
||||
# Give it 3 seconds to start
|
||||
time.sleep(3)
|
||||
|
||||
print("SSE server started. Running example...\n\n")
|
||||
except Exception as e:
|
||||
print(f"Error starting SSE server: {e}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
finally:
|
||||
if process:
|
||||
process.terminate()
|
||||
@@ -0,0 +1,42 @@
|
||||
import os
|
||||
import random
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
SSE_HOST = os.getenv("SSE_HOST", "127.0.0.1")
|
||||
SSE_PORT = int(os.getenv("SSE_PORT", "8000"))
|
||||
|
||||
# Create server
|
||||
mcp = FastMCP("Echo Server", host=SSE_HOST, port=SSE_PORT)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
print(f"[debug-server] add({a}, {b})")
|
||||
return a + b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_secret_word() -> str:
|
||||
print("[debug-server] get_secret_word()")
|
||||
return random.choice(["apple", "banana", "cherry"])
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_current_weather(city: str) -> str:
|
||||
print(f"[debug-server] get_current_weather({city})")
|
||||
# Keep tool output deterministic so this example is stable in CI and offline environments.
|
||||
weather_by_city = {
|
||||
"tokyo": "sunny with a light breeze and 20°C",
|
||||
"san francisco": "cool and foggy with 14°C",
|
||||
"new york": "partly cloudy with 18°C",
|
||||
}
|
||||
forecast = weather_by_city.get(city.strip().lower())
|
||||
if forecast:
|
||||
return f"The weather in {city} is {forecast}."
|
||||
return f"The weather data for {city} is unavailable in this demo."
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="sse")
|
||||
@@ -0,0 +1,13 @@
|
||||
# MCP SSE Remote Example
|
||||
|
||||
Python port of the JS `examples/mcp/sse-example.ts`. By default it starts the bundled local SSE MCP server and lets the agent use those tools. Set `MCP_SSE_REMOTE_URL` to try a compatible remote SSE server instead.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
uv run python examples/mcp/sse_remote_example/main.py
|
||||
```
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- `OPENAI_API_KEY` set for the model calls.
|
||||
@@ -0,0 +1,100 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServerSse
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
SSE_HOST = os.getenv("SSE_HOST", "127.0.0.1")
|
||||
REMOTE_SSE_URL = os.getenv("MCP_SSE_REMOTE_URL")
|
||||
|
||||
|
||||
def _choose_port() -> int:
|
||||
env_port = os.getenv("SSE_PORT")
|
||||
if env_port:
|
||||
return int(env_port)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind((SSE_HOST, 0))
|
||||
address = cast(tuple[str, int], sock.getsockname())
|
||||
return address[1]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def local_sse_server() -> Iterator[str]:
|
||||
if not shutil.which("uv"):
|
||||
raise RuntimeError(
|
||||
"uv is not installed. Please install it: "
|
||||
"https://docs.astral.sh/uv/getting-started/installation/"
|
||||
)
|
||||
|
||||
sse_port = _choose_port()
|
||||
sse_url = f"http://{SSE_HOST}:{sse_port}/sse"
|
||||
server_file = Path(__file__).resolve().parents[1] / "sse_example" / "server.py"
|
||||
|
||||
print(f"Starting local SSE server at {sse_url} ...", flush=True)
|
||||
env = os.environ.copy()
|
||||
env.setdefault("SSE_HOST", SSE_HOST)
|
||||
env["SSE_PORT"] = str(sse_port)
|
||||
process: subprocess.Popen[Any] | None = None
|
||||
|
||||
try:
|
||||
process = subprocess.Popen(["uv", "run", str(server_file)], env=env)
|
||||
time.sleep(3)
|
||||
yield sse_url
|
||||
finally:
|
||||
if process is not None:
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
|
||||
|
||||
async def run(url: str, name: str) -> None:
|
||||
async with MCPServerSse(
|
||||
name=name,
|
||||
params={
|
||||
"url": url,
|
||||
"timeout": 5,
|
||||
"sse_read_timeout": 30,
|
||||
},
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="SSE Assistant",
|
||||
instructions="Use the available MCP tools to answer the user.",
|
||||
mcp_servers=[server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="SSE MCP Server Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
result = await Runner.run(agent, "Use the MCP add tool to add 7 and 22.")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
if REMOTE_SSE_URL:
|
||||
print(f"Connecting to remote SSE server at {REMOTE_SSE_URL} ...", flush=True)
|
||||
await run(REMOTE_SSE_URL, "Remote SSE Server")
|
||||
return
|
||||
|
||||
print(
|
||||
"MCP_SSE_REMOTE_URL is not set; using the bundled local SSE server for this demo.",
|
||||
flush=True,
|
||||
)
|
||||
with local_sse_server() as url:
|
||||
await run(url, "Local SSE Server")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,13 @@
|
||||
# MCP Streamable HTTP Remote Example
|
||||
|
||||
Python port of the JS `examples/mcp/streamable-http-example.ts`. It connects to DeepWiki over the Streamable HTTP transport (`https://mcp.deepwiki.com/mcp`) and lets the agent use those tools.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
uv run python examples/mcp/streamable_http_remote_example/main.py
|
||||
```
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- `OPENAI_API_KEY` set for the model calls.
|
||||
@@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServerStreamableHttp
|
||||
|
||||
|
||||
async def main():
|
||||
async with MCPServerStreamableHttp(
|
||||
name="DeepWiki MCP Streamable HTTP Server",
|
||||
params={
|
||||
"url": "https://mcp.deepwiki.com/mcp",
|
||||
# Allow more time for remote tool responses.
|
||||
"timeout": 15,
|
||||
"sse_read_timeout": 300,
|
||||
},
|
||||
# Retry slow/unstable remote calls a couple of times.
|
||||
max_retry_attempts=2,
|
||||
retry_backoff_seconds_base=2.0,
|
||||
client_session_timeout_seconds=15,
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="DeepWiki Assistant",
|
||||
instructions="Use the tools to respond to user requests.",
|
||||
mcp_servers=[server],
|
||||
)
|
||||
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="DeepWiki Streamable HTTP Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"For the repository openai/codex, tell me the primary programming language.",
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,63 @@
|
||||
# Custom HTTP Client Factory Example
|
||||
|
||||
This example demonstrates how to use the new `httpx_client_factory` parameter in `MCPServerStreamableHttp` to configure custom HTTP client behavior for MCP StreamableHTTP connections.
|
||||
|
||||
## Features Demonstrated
|
||||
|
||||
- **Custom SSL Configuration**: Configure SSL certificates and verification settings
|
||||
- **Custom Headers**: Add custom headers to all HTTP requests
|
||||
- **Custom Timeouts**: Set custom timeout values for requests
|
||||
- **Proxy Configuration**: Configure HTTP proxy settings
|
||||
- **Custom Retry Logic**: Set up custom retry behavior (through httpx configuration)
|
||||
|
||||
## Running the Example
|
||||
|
||||
1. Make sure you have `uv` installed: https://docs.astral.sh/uv/getting-started/installation/
|
||||
|
||||
2. Run the example:
|
||||
```bash
|
||||
cd examples/mcp/streamablehttp_custom_client_example
|
||||
uv run main.py
|
||||
```
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Basic Custom Client
|
||||
|
||||
```python
|
||||
import httpx
|
||||
from agents.mcp import MCPServerStreamableHttp
|
||||
|
||||
def create_custom_http_client() -> httpx.AsyncClient:
|
||||
return httpx.AsyncClient(
|
||||
verify=False, # Disable SSL verification for testing
|
||||
timeout=httpx.Timeout(60.0, read=120.0),
|
||||
headers={"X-Custom-Client": "my-app"},
|
||||
)
|
||||
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Custom Client Server",
|
||||
params={
|
||||
"url": "http://localhost:<port>/mcp",
|
||||
"httpx_client_factory": create_custom_http_client,
|
||||
},
|
||||
) as server:
|
||||
# Use the server...
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Corporate Networks**: Configure proxy settings for corporate environments
|
||||
- **SSL/TLS Requirements**: Use custom SSL certificates for secure connections
|
||||
- **Custom Authentication**: Add custom headers for API authentication
|
||||
- **Network Optimization**: Configure timeouts and connection pooling
|
||||
- **Debugging**: Disable SSL verification for development environments
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Flexibility**: Configure HTTP client behavior to match your network requirements
|
||||
- **Security**: Use custom SSL certificates and authentication methods
|
||||
- **Performance**: Optimize timeouts and connection settings for your use case
|
||||
- **Compatibility**: Work with corporate proxies and network restrictions
|
||||
|
||||
This example will auto-pick a free localhost port unless you set `STREAMABLE_HTTP_PORT`; use `STREAMABLE_HTTP_HOST` to change the bind address.
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Example demonstrating custom httpx_client_factory for MCPServerStreamableHttp.
|
||||
|
||||
This example shows how to configure custom HTTP client behavior for MCP StreamableHTTP
|
||||
connections, including SSL certificates, proxy settings, and custom timeouts.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServer, MCPServerStreamableHttp
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
|
||||
|
||||
def _choose_port() -> int:
|
||||
env_port = os.getenv("STREAMABLE_HTTP_PORT")
|
||||
if env_port:
|
||||
return int(env_port)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((STREAMABLE_HTTP_HOST, 0))
|
||||
address = cast(tuple[str, int], s.getsockname())
|
||||
return address[1]
|
||||
|
||||
|
||||
STREAMABLE_HTTP_PORT = _choose_port()
|
||||
os.environ.setdefault("STREAMABLE_HTTP_PORT", str(STREAMABLE_HTTP_PORT))
|
||||
STREAMABLE_HTTP_URL = f"http://{STREAMABLE_HTTP_HOST}:{STREAMABLE_HTTP_PORT}/mcp"
|
||||
|
||||
|
||||
def create_custom_http_client(
|
||||
headers: dict[str, str] | None = None,
|
||||
timeout: httpx.Timeout | None = None,
|
||||
auth: httpx.Auth | None = None,
|
||||
) -> httpx.AsyncClient:
|
||||
"""Create a custom HTTP client with specific configurations.
|
||||
|
||||
This function demonstrates how to configure:
|
||||
- Custom SSL verification settings
|
||||
- Custom timeouts
|
||||
- Custom headers
|
||||
- Proxy settings (commented out)
|
||||
"""
|
||||
if headers is None:
|
||||
headers = {
|
||||
"X-Custom-Client": "agents-mcp-example",
|
||||
"User-Agent": "OpenAI-Agents-MCP/1.0",
|
||||
}
|
||||
if timeout is None:
|
||||
timeout = httpx.Timeout(60.0, read=120.0)
|
||||
if auth is None:
|
||||
auth = None
|
||||
return httpx.AsyncClient(
|
||||
# Disable SSL verification for testing (not recommended for production)
|
||||
verify=False,
|
||||
# Set custom timeout
|
||||
timeout=httpx.Timeout(60.0, read=120.0),
|
||||
# Add custom headers that will be sent with every request
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
||||
async def run_with_custom_client(mcp_server: MCPServer):
|
||||
"""Run the agent with a custom HTTP client configuration."""
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the tools to answer the questions.",
|
||||
mcp_servers=[mcp_server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
# Use the `add` tool to add two numbers
|
||||
message = "Add these numbers: 7 and 22."
|
||||
print(f"Running: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function demonstrating different HTTP client configurations."""
|
||||
|
||||
print("=== Example: Custom HTTP Client with SSL disabled and custom headers ===")
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Streamable HTTP with Custom Client",
|
||||
params={
|
||||
"url": STREAMABLE_HTTP_URL,
|
||||
"httpx_client_factory": create_custom_http_client,
|
||||
},
|
||||
) as server:
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="Custom HTTP Client Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/logs/trace?trace_id={trace_id}\n")
|
||||
await run_with_custom_client(server)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Let's make sure the user has uv installed
|
||||
if not shutil.which("uv"):
|
||||
raise RuntimeError(
|
||||
"uv is not installed. Please install it: https://docs.astral.sh/uv/getting-started/installation/"
|
||||
)
|
||||
|
||||
# We'll run the Streamable HTTP server in a subprocess. Usually this would be a remote server, but for this
|
||||
# demo, we'll run it locally at STREAMABLE_HTTP_URL
|
||||
process: subprocess.Popen[Any] | None = None
|
||||
try:
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_file = os.path.join(this_dir, "server.py")
|
||||
|
||||
print(f"Starting Streamable HTTP server at {STREAMABLE_HTTP_URL} ...")
|
||||
|
||||
# Run `uv run server.py` to start the Streamable HTTP server
|
||||
env = os.environ.copy()
|
||||
env.setdefault("STREAMABLE_HTTP_HOST", STREAMABLE_HTTP_HOST)
|
||||
env.setdefault("STREAMABLE_HTTP_PORT", str(STREAMABLE_HTTP_PORT))
|
||||
process = subprocess.Popen(["uv", "run", server_file], env=env)
|
||||
# Give it 3 seconds to start
|
||||
time.sleep(3)
|
||||
|
||||
print("Streamable HTTP server started. Running example...\n\n")
|
||||
except Exception as e:
|
||||
print(f"Error starting Streamable HTTP server: {e}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
finally:
|
||||
if process:
|
||||
process.terminate()
|
||||
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
import random
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
STREAMABLE_HTTP_PORT = int(os.getenv("STREAMABLE_HTTP_PORT", "18080"))
|
||||
|
||||
# Create server
|
||||
mcp = FastMCP("Echo Server", host=STREAMABLE_HTTP_HOST, port=STREAMABLE_HTTP_PORT)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
print(f"[debug-server] add({a}, {b})")
|
||||
return a + b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_secret_word() -> str:
|
||||
print("[debug-server] get_secret_word()")
|
||||
return random.choice(["apple", "banana", "cherry"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
@@ -0,0 +1,13 @@
|
||||
# MCP Streamable HTTP Example
|
||||
|
||||
This example uses a local Streamable HTTP server in [server.py](server.py).
|
||||
|
||||
Run the example via:
|
||||
|
||||
```
|
||||
uv run python examples/mcp/streamablehttp_example/main.py
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
The example uses the `MCPServerStreamableHttp` class from `agents.mcp`. The script picks an open localhost port automatically (or honors `STREAMABLE_HTTP_PORT` if you set it) and starts the server at `http://<host>:<port>/mcp`. Set `STREAMABLE_HTTP_HOST` if you need a different bind address.
|
||||
@@ -0,0 +1,104 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServer, MCPServerStreamableHttp
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
|
||||
|
||||
def _choose_port() -> int:
|
||||
env_port = os.getenv("STREAMABLE_HTTP_PORT")
|
||||
if env_port:
|
||||
return int(env_port)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((STREAMABLE_HTTP_HOST, 0))
|
||||
address = cast(tuple[str, int], s.getsockname())
|
||||
return address[1]
|
||||
|
||||
|
||||
STREAMABLE_HTTP_PORT = _choose_port()
|
||||
os.environ.setdefault("STREAMABLE_HTTP_PORT", str(STREAMABLE_HTTP_PORT))
|
||||
STREAMABLE_HTTP_URL = f"http://{STREAMABLE_HTTP_HOST}:{STREAMABLE_HTTP_PORT}/mcp"
|
||||
|
||||
|
||||
async def run(mcp_server: MCPServer):
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the tools to answer the questions.",
|
||||
mcp_servers=[mcp_server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
# Use the `add` tool to add two numbers
|
||||
message = "Add these numbers: 7 and 22."
|
||||
print(f"Running: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
# Run the `get_weather` tool
|
||||
message = "What's the weather in Tokyo?"
|
||||
print(f"\n\nRunning: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
# Run the `get_secret_word` tool
|
||||
message = "What's the secret word?"
|
||||
print(f"\n\nRunning: {message}")
|
||||
result = await Runner.run(starting_agent=agent, input=message)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
async def main():
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Streamable HTTP Python Server",
|
||||
params={
|
||||
"url": STREAMABLE_HTTP_URL,
|
||||
},
|
||||
) as server:
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="Streamable HTTP Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
await run(server)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Let's make sure the user has uv installed
|
||||
if not shutil.which("uv"):
|
||||
raise RuntimeError(
|
||||
"uv is not installed. Please install it: https://docs.astral.sh/uv/getting-started/installation/"
|
||||
)
|
||||
|
||||
# We'll run the Streamable HTTP server in a subprocess. Usually this would be a remote server, but for this
|
||||
# demo, we'll run it locally at STREAMABLE_HTTP_URL
|
||||
process: subprocess.Popen[Any] | None = None
|
||||
try:
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_file = os.path.join(this_dir, "server.py")
|
||||
|
||||
print(f"Starting Streamable HTTP server at {STREAMABLE_HTTP_URL} ...")
|
||||
|
||||
# Run `uv run server.py` to start the Streamable HTTP server
|
||||
env = os.environ.copy()
|
||||
env.setdefault("STREAMABLE_HTTP_HOST", STREAMABLE_HTTP_HOST)
|
||||
env.setdefault("STREAMABLE_HTTP_PORT", str(STREAMABLE_HTTP_PORT))
|
||||
process = subprocess.Popen(["uv", "run", server_file], env=env)
|
||||
# Give it 3 seconds to start
|
||||
time.sleep(3)
|
||||
|
||||
print("Streamable HTTP server started. Running example...\n\n")
|
||||
except Exception as e:
|
||||
print(f"Error starting Streamable HTTP server: {e}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
finally:
|
||||
if process:
|
||||
process.terminate()
|
||||
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
import random
|
||||
|
||||
import requests
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
STREAMABLE_HTTP_HOST = os.getenv("STREAMABLE_HTTP_HOST", "127.0.0.1")
|
||||
STREAMABLE_HTTP_PORT = int(os.getenv("STREAMABLE_HTTP_PORT", "18080"))
|
||||
|
||||
# Create server
|
||||
mcp = FastMCP("Echo Server", host=STREAMABLE_HTTP_HOST, port=STREAMABLE_HTTP_PORT)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
print(f"[debug-server] add({a}, {b})")
|
||||
return a + b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_secret_word() -> str:
|
||||
print("[debug-server] get_secret_word()")
|
||||
return random.choice(["apple", "banana", "cherry"])
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_current_weather(city: str) -> str:
|
||||
print(f"[debug-server] get_current_weather({city})")
|
||||
# Avoid slow or flaky network calls during automated runs.
|
||||
try:
|
||||
endpoint = "https://wttr.in"
|
||||
response = requests.get(f"{endpoint}/{city}", timeout=2)
|
||||
if response.ok:
|
||||
return response.text
|
||||
except Exception:
|
||||
pass
|
||||
# Fallback keeps the tool responsive even when offline.
|
||||
return f"Weather data unavailable right now; assume clear skies in {city}."
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
@@ -0,0 +1,19 @@
|
||||
# MCP Tool Filter Example
|
||||
|
||||
Python port of the JS `examples/mcp/tool-filter-example.ts`. It shows how to:
|
||||
|
||||
- Run the filesystem MCP server locally via `npx`.
|
||||
- Apply a static tool filter so only specific tools are exposed to the model.
|
||||
- Observe that blocked tools are not available.
|
||||
- Enable `require_approval="always"` and auto-approve interruptions in code so the HITL path is exercised.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
uv run python examples/mcp/tool_filter_example/main.py
|
||||
```
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- `npx` available on your PATH.
|
||||
- `OPENAI_API_KEY` set for the model calls.
|
||||
@@ -0,0 +1,75 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
from typing import Any, cast
|
||||
|
||||
from agents import Agent, Runner, gen_trace_id, trace
|
||||
from agents.mcp import MCPServerStdio
|
||||
from agents.mcp.util import create_static_tool_filter
|
||||
|
||||
|
||||
async def run_with_auto_approval(agent: Agent[Any], message: str) -> str | None:
|
||||
"""Run and auto-approve interruptions."""
|
||||
|
||||
result = await Runner.run(agent, message)
|
||||
while result.interruptions:
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
print(f"Approving a tool call... (name: {interruption.name})")
|
||||
state.approve(interruption, always_approve=True)
|
||||
result = await Runner.run(agent, state)
|
||||
return cast(str | None, result.final_output)
|
||||
|
||||
|
||||
async def main():
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
samples_dir = os.path.join(current_dir, "sample_files")
|
||||
target_path = os.path.join(samples_dir, "test.txt")
|
||||
|
||||
async with MCPServerStdio(
|
||||
name="Filesystem Server with filter",
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
|
||||
"cwd": samples_dir,
|
||||
},
|
||||
require_approval="always",
|
||||
tool_filter=create_static_tool_filter(
|
||||
allowed_tool_names=["read_file", "list_directory"],
|
||||
blocked_tool_names=["write_file"],
|
||||
),
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="MCP Assistant",
|
||||
instructions=(
|
||||
"Use only the available filesystem tools. "
|
||||
"All file paths should be absolute paths inside the allowed directory. "
|
||||
"If a user asks for an action that requires an unavailable tool, "
|
||||
"explicitly explain that it is blocked by the tool filter."
|
||||
),
|
||||
mcp_servers=[server],
|
||||
)
|
||||
trace_id = gen_trace_id()
|
||||
with trace(workflow_name="MCP Tool Filter Example", trace_id=trace_id):
|
||||
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}\n")
|
||||
result = await run_with_auto_approval(
|
||||
agent, f"List the files in this allowed directory: {samples_dir}"
|
||||
)
|
||||
print(result)
|
||||
|
||||
blocked_result = await run_with_auto_approval(
|
||||
agent,
|
||||
(
|
||||
f'Create a file at "{target_path}" with the text "hello". '
|
||||
"If you cannot, explain that write operations are blocked by the tool filter."
|
||||
),
|
||||
)
|
||||
print("\nAttempting to write a file (should be blocked):")
|
||||
print(blocked_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not shutil.which("npx"):
|
||||
raise RuntimeError("npx is required. Install it with `npm install -g npx`.")
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,20 @@
|
||||
1. To Kill a Mockingbird – Harper Lee
|
||||
2. Pride and Prejudice – Jane Austen
|
||||
3. 1984 – George Orwell
|
||||
4. The Hobbit – J.R.R. Tolkien
|
||||
5. Harry Potter and the Sorcerer’s Stone – J.K. Rowling
|
||||
6. The Great Gatsby – F. Scott Fitzgerald
|
||||
7. Charlotte’s Web – E.B. White
|
||||
8. Anne of Green Gables – Lucy Maud Montgomery
|
||||
9. The Alchemist – Paulo Coelho
|
||||
10. Little Women – Louisa May Alcott
|
||||
11. The Catcher in the Rye – J.D. Salinger
|
||||
12. Animal Farm – George Orwell
|
||||
13. The Chronicles of Narnia: The Lion, the Witch, and the Wardrobe – C.S. Lewis
|
||||
14. The Book Thief – Markus Zusak
|
||||
15. A Wrinkle in Time – Madeleine L’Engle
|
||||
16. The Secret Garden – Frances Hodgson Burnett
|
||||
17. Moby-Dick – Herman Melville
|
||||
18. Fahrenheit 451 – Ray Bradbury
|
||||
19. Jane Eyre – Charlotte Brontë
|
||||
20. The Little Prince – Antoine de Saint-Exupéry
|
||||
@@ -0,0 +1,10 @@
|
||||
1. "Here Comes the Sun" – The Beatles
|
||||
2. "Imagine" – John Lennon
|
||||
3. "Bohemian Rhapsody" – Queen
|
||||
4. "Shake It Off" – Taylor Swift
|
||||
5. "Billie Jean" – Michael Jackson
|
||||
6. "Uptown Funk" – Mark Ronson ft. Bruno Mars
|
||||
7. "Don’t Stop Believin’" – Journey
|
||||
8. "Dancing Queen" – ABBA
|
||||
9. "Happy" – Pharrell Williams
|
||||
10. "Wonderwall" – Oasis
|
||||
Reference in New Issue
Block a user