
This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.
The OpenAI Agents SDK for Python can use MCP servers in four ways. OpenAI's servers can call a public MCP server for you (hosted tools), or your own process can connect over Streamable HTTP, the older SSE transport, or stdio. The right choice depends on where the tool calls should run and which transport the server speaks.
This tutorial picks the most common path, Streamable HTTP, builds a working agent, then adds tool filtering, approvals and multiple servers. Code was checked against the SDK's MCP documentation and openai-agents 0.24.0 on PyPI on 2 November 2026.
Key Takeaways
HostedMCPToollets OpenAI's Responses API call a publicly reachable server; it only works with OpenAI models that support hosted MCP.MCPServerStreamableHttpandMCPServerStdiorun the MCP connection in your own process and work with the agent'smcp_serverslist.- The SDK supports both major versions of the
mcpPython package (>=1.19.0,<3) and negotiates the protocol version with each server. - Use
tool_filterto expose only the tools you need, andrequire_approvalto pause before risky calls. - MCP tool listing and tool calls show up in the SDK's tracing automatically.
Choose your integration
The docs give a simple decision table:
| What you need | Use |
|---|---|
| OpenAI's Responses API calls a public MCP server on the model's behalf | HostedMCPTool |
| You connect to a Streamable HTTP server, local or remote | MCPServerStreamableHttp |
| You connect to a legacy SSE server | MCPServerSse |
| You launch a local server as a subprocess | MCPServerStdio |
The docs warn that "the MCP project has deprecated the Server-Sent Events transport." Keep SSE only for old servers. Our SSE vs Streamable HTTP page explains why.
Prerequisites
- Python 3.10 or later.
pip install openai-agentsOPENAI_API_KEYset in your environment.- An MCP server. For a local test server, use the FastMCP notes server from our langchain-mcp-adapters tutorial, which serves
http://localhost:8000/mcp, or see our FastMCP tutorial.
Minimal working example (Streamable HTTP)
This is the docs' example, trimmed. It connects to a local server and asks a question that needs a tool:
import asyncio
import os
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
async def main() -> None:
async with MCPServerStreamableHttp(
name="Notes server",
params={
"url": "http://localhost:8000/mcp",
"headers": {"Authorization": f"Bearer {os.environ.get('MCP_SERVER_TOKEN', '')}"},
"timeout": 10,
},
cache_tools_list=True,
max_retry_attempts=3,
) as server:
agent = Agent(
name="Assistant",
instructions="Use the MCP tools to answer questions.",
mcp_servers=[server],
)
result = await Runner.run(agent, "What does the welcome note say?")
print(result.final_output)
asyncio.run(main())
Notes on the options:
cache_tools_list=Trueskips atools/listround trip on every run. Turn it off while you are still changing the server.max_retry_attemptsadds automatic retries forlist_tools()andcall_tool().client_session_timeout_secondsdefaults to5seconds. Raise it for slow servers.- Put tokens in headers, not URLs. The docs say to keep "access tokens in authorization fields or headers rather than URLs."
The stdio variant
For a server that runs as a local command, swap the class. This is the docs' filesystem example:
from pathlib import Path
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
samples_dir = Path(__file__).parent / "sample_files"
async with MCPServerStdio(
name="Filesystem Server via npx",
params={
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
},
client_session_timeout_seconds=30,
) as server:
agent = Agent(
name="Assistant",
instructions="Use the files in the sample directory to answer questions.",
mcp_servers=[server],
)
result = await Runner.run(agent, "List the files available to you.")
print(result.final_output)
The docs flag a cold-start trap: npx -y and uvx "may download packages before the MCP server starts." With MCP Python SDK v2, a longer session timeout does not cover the first server/discover request, so preinstall the package instead. Our filesystem server profile covers that server's tools and risks.
The hosted variant
With HostedMCPTool, your Python code never connects to the server. The tool config is forwarded to the Responses API:
from agents import Agent, HostedMCPTool, Runner
agent = Agent(
name="Assistant",
instructions="Use the DeepWiki hosted MCP server to inspect openai/openai-agents-python.",
tools=[
HostedMCPTool(
tool_config={
"type": "mcp",
"server_label": "deepwiki",
"server_url": "https://mcp.deepwiki.com/mcp",
"require_approval": "never",
}
)
],
)
The hosted server goes in tools, not mcp_servers. It must be reachable from the public internet, and the docs say hosted tools "currently work with OpenAI models that support the Responses API's hosted MCP integration." Use "require_approval": "always" plus an on_approval_request callback for anything that writes data.
Add a tool
Tools live on the server. Add a function to your server, restart it, and the agent sees the new tool on its next list_tools() call. If you set cache_tools_list=True, restart your agent process too, or the old list stays cached.
Write each tool description for the model. The agent picks tools by reading names and descriptions, so "Read one note by its exact title" beats "Gets data."
Limit what the agent can do
Filter tools
create_static_tool_filter gives you allow and block lists:
from agents.mcp import MCPServerStdio, create_static_tool_filter
filesystem_server = MCPServerStdio(
params={
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"],
},
tool_filter=create_static_tool_filter(allowed_tool_names=["read_file", "list_directory"]),
)
If you supply both lists, the SDK applies the allow list first, then removes blocked tools.
Require approval
Local MCP servers accept require_approval as "always", "never", a per-tool map, a grouped object, or a callable:
async with MCPServerStreamableHttp(
name="Filesystem MCP",
params={"url": "http://localhost:8000/mcp"},
require_approval={"always": {"tool_names": ["delete_file"]}},
) as server:
...
The docs make two points worth repeating. A callable policy "does not receive the specific call's arguments," so it cannot approve "delete only drafts." And server-provided annotations are "advisory hints." Our tool permission planner helps you decide which tools need approval.
Avoid name collisions
Two servers can both have a search tool. Set mcp_config={"include_server_in_tool_names": True} on the agent and each local MCP tool gets a server prefix. The SDK still calls the original name on the original server.
Several servers at once
MCPServerManager connects a list of servers up front and gives the agent the ones that connected:
from agents import Agent, Runner
from agents.mcp import MCPServerManager, MCPServerStreamableHttp
servers = [
MCPServerStreamableHttp(name="calendar", params={"url": "http://localhost:8000/mcp"}),
MCPServerStreamableHttp(name="docs", params={"url": "http://localhost:8001/mcp"}),
]
async with MCPServerManager(servers) as manager:
agent = Agent(
name="Assistant",
instructions="Use MCP tools when they help.",
mcp_servers=manager.active_servers,
)
result = await Runner.run(agent, "Which MCP tools are available?")
print(result.final_output)
Test with the MCP Inspector
Check the server alone before you debug the agent:
npx @modelcontextprotocol/inspector --cli http://localhost:8000/mcp --transport http --method tools/list
Then use the SDK's tracing, which "automatically captures MCP activity," to see which tools the agent listed and called. Our Inspector lesson shows how to script these checks.
MCP SDK v1 vs v2 pitfalls
The Agents SDK works with mcp>=1.19.0,<3, which spans both major versions of the MCP Python SDK. Most apps can let the resolver choose. Three things change with v2, per the docs:
- Custom
params["auth"]objects must behttpx2.Auth, nothttpx.Auth. A plainAuthorizationheader works with both. - A custom
httpx_client_factorymust returnhttpx2.AsyncClienton v2. ignore_initialized_notification_failure=Trueis rejected on v2.
With v2 installed, the client first sends server/discover at the newest protocol version and falls back to the legacy initialize handshake for older servers. Installing v2 "does not force every connection to use the newest MCP protocol version."
Next step
New to the protocol? The free MCP course explains servers, tools and transports in order. If you want an MCP server built for your OpenAI agents, with scoped auth and approvals, see our MCP development service.
More in the MCP Guide
Free tools: server.json Validator · MCP Error Code Lookup
Latest research: State of MCP: October 2026
Frequently asked questions
Should I use HostedMCPTool or MCPServerStreamableHttp?
Use HostedMCPTool when the server is public and you want OpenAI to handle the round trip. Use MCPServerStreamableHttp when the server is private, when you need your own approval logic in Python, or when you use non-OpenAI models.
Does the Agents SDK support the 2026-07-28 MCP spec?
The docs say that with MCP Python SDK v2 installed, the client probes with server/discover at the newest version the SDK supports and falls back to initialize for older servers. The negotiated version depends on the server.
Why does my stdio server time out on first run?
npx -y and uvx may download the package before the server starts. Install it ahead of time, and raise client_session_timeout_seconds if the server is slow to answer.
Can I see which MCP tools the agent called?
Yes. The MCP docs say tracing captures calls to list tools and MCP-related information on tool calls. The SDK's tracing guide explains where traces are sent and how to change that.











