Use remote MCP servers in the OpenAI API and Agents SDK
Call remote MCP servers from the OpenAI Responses API with the mcp tool type: server_url, allowed_tools, approvals and authorization, plus Agents SDK examples.
The Responses API can use any remote MCP server as a tool. You pass the server's URL in the request, and OpenAI's side lists the server's tools, lets the model call them, and puts the results back into the model's context. Your code doesn't need an MCP client.
This guide covers the mcp tool type in the Responses API, then the same thing in the Agents SDK, with Python and TypeScript examples.
Before you start#
pip install --upgrade openai # Python
npm install openai # TypeScript
export OPENAI_API_KEY=sk-...You also need a remote MCP server that speaks Streamable HTTP or HTTP/SSE. The examples use two public servers that need no auth: OpenAI's dice demo (https://dmcp-server.deno.dev/mcp) and DeepWiki (https://mcp.deepwiki.com/mcp). The model is gpt-6-astra, which OpenAI's MCP guide uses; check the models page for MCP tool support if you pick another.
Note: You pay only for the tokens used to import tool definitions and make tool calls. There's no extra fee per MCP call.
1. Make your first call#
from openai import OpenAI
client = OpenAI()
resp = client.responses.create(
model="gpt-6-astra",
tools=[
{
"type": "mcp",
"server_label": "dmcp",
"server_description": "A Dungeons and Dragons MCP server to assist with dice rolling.",
"server_url": "https://dmcp-server.deno.dev/mcp",
"require_approval": "never",
}
],
input="Roll 2d4+1",
)
print(resp.output_text)import OpenAI from "openai";
const client = new OpenAI();
const resp = await client.responses.create({
model: "gpt-6-astra",
tools: [
{
type: "mcp",
server_label: "dmcp",
server_description: "A Dungeons and Dragons MCP server to assist with dice rolling.",
server_url: "https://dmcp-server.deno.dev/mcp",
require_approval: "never",
},
],
input: "Roll 2d4+1",
});
console.log(resp.output_text);The fields:
| Field | Purpose |
|---|---|
type | Always "mcp". |
server_label | Required. A short name used in output items and approvals. |
server_url | The remote endpoint, including its path. |
server_description | Optional. Helps the model decide when to use the server. |
require_approval | "always", "never", or a per-tool filter (step 4). |
allowed_tools | Optional list that limits which tools are imported (step 3). |
authorization | Optional OAuth token or API key for the server (step 5). |
2. Read the output items#
MCP activity shows up as items in resp.output:
mcp_list_tools: the tools imported from the server, with their input schemas. The API fetches this on the first turn. As long as this item stays in the conversation context, the API won't fetch the list again on later turns, so keep it there to save latency.mcp_call: one per tool call, withname, theargumentsthe model sent, theoutputthe server returned, and anerrorfield if something failed.mcp_approval_request: a call waiting for your approval.
for item in resp.output:
if item.type == "mcp_call":
print(item.name, item.arguments, "->", item.output, item.error)3. Limit the tools with allowed_tools#
Servers can expose dozens of tools. Every imported definition costs tokens and gives the model more ways to go wrong. Import only what you need:
tool = {
"type": "mcp",
"server_label": "deepwiki",
"server_url": "https://mcp.deepwiki.com/mcp",
"allowed_tools": ["ask_question", "read_wiki_structure"],
"require_approval": "never",
}If you use tool search, "defer_loading": True on the MCP tool defers loading its definitions until the model needs them.
4. Handle approvals#
By default the API asks for your approval before sending any data to a remote MCP server. Instead of calling the tool, the response contains mcp_approval_request items. You review them and reply with mcp_approval_response items in a follow-up request:
tool = {
"type": "mcp",
"server_label": "dmcp",
"server_url": "https://dmcp-server.deno.dev/mcp",
"require_approval": "always",
}
resp = client.responses.create(model="gpt-6-astra", tools=[tool], input="Roll 2d4+1")
approvals = []
for item in resp.output:
if item.type == "mcp_approval_request":
print(f"{item.server_label}.{item.name} wants: {item.arguments}")
approvals.append({
"type": "mcp_approval_response",
"approve": True, # decide per call in real code
"approval_request_id": item.id,
})
if approvals:
resp = client.responses.create(
model="gpt-6-astra",
tools=[tool],
previous_response_id=resp.id,
input=approvals,
)
print(resp.output_text)const tool = {
type: "mcp" as const,
server_label: "dmcp",
server_url: "https://dmcp-server.deno.dev/mcp",
require_approval: "always" as const,
};
let resp = await client.responses.create({ model: "gpt-6-astra", tools: [tool], input: "Roll 2d4+1" });
const approvals = resp.output.flatMap((item) =>
item.type === "mcp_approval_request"
? [{
type: "mcp_approval_response" as const,
approve: true, // decide per call in real code
approval_request_id: item.id,
}]
: []
);
if (approvals.length > 0) {
resp = await client.responses.create({
model: "gpt-6-astra",
tools: [tool],
previous_response_id: resp.id,
input: approvals,
});
}
console.log(resp.output_text);Once you trust a server, skip approvals for its read-only tools and keep them for everything else:
"require_approval": {"never": {"tool_names": ["ask_question", "read_wiki_structure"]}}"never" skips approvals for all tools on that server.
5. Authenticate#
Most real servers need a token. Pass it in authorization:
import os
resp = client.responses.create(
model="gpt-6-astra",
input="Create a payment link for $20",
tools=[
{
"type": "mcp",
"server_label": "stripe",
"server_url": "https://mcp.stripe.com",
"authorization": os.environ["STRIPE_OAUTH_ACCESS_TOKEN"],
}
],
)The API doesn't store this value and doesn't echo it in the response object, so you must send it on every request, including approval follow-ups. Your application is responsible for getting the token, for example through the service's OAuth flow.
6. Private servers and legacy connectors#
- Private or on-premises servers. Use Secure MCP Tunnel instead of exposing the server publicly. Run OpenAI's
tunnel-clientnext to the server and passtunnel_idin place ofserver_url. - Built-in connectors (
connector_id, for exampleconnector_gmail) are deprecated for models released after September 1, 2026. Older models keep them. For new work, use the service's own remote MCP server; GitHub's, for example, ishttps://api.githubcopilot.com/mcp/.
7. Use MCP from the Agents SDK#
The Agents SDK supports two patterns.
Hosted MCP tools send the round trip through the Responses API, exactly as above:
import asyncio
from agents import Agent, HostedMCPTool, Runner
agent = Agent(
name="Repo expert",
instructions="Answer questions about GitHub repositories using DeepWiki.",
model="gpt-6-astra",
tools=[
HostedMCPTool(
tool_config={
"type": "mcp",
"server_label": "deepwiki",
"server_url": "https://mcp.deepwiki.com/mcp",
"allowed_tools": ["ask_question"],
"require_approval": "never",
}
)
],
)
async def main():
result = await Runner.run(agent, "How does routing work in vercel/next.js?")
print(result.final_output)
asyncio.run(main())import { Agent, hostedMcpTool, run } from "@openai/agents";
const agent = new Agent({
name: "Repo expert",
instructions: "Answer questions about GitHub repositories using DeepWiki.",
model: "gpt-6-astra",
tools: [
hostedMcpTool({
serverLabel: "deepwiki",
serverUrl: "https://mcp.deepwiki.com/mcp",
allowedTools: ["ask_question"],
requireApproval: "never",
}),
],
});
const result = await run(agent, "How does routing work in vercel/next.js?");
console.log(result.finalOutput);Note: In the TypeScript SDK,
hostedMcpTooldefaultsrequireApprovalto"never", unlike the raw Responses API. Set it explicitly so the behavior is obvious to the next reader.
SDK-managed servers connect from your own process. Use this for servers on localhost or a private network, where OpenAI can't reach them:
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
async def main():
async with MCPServerStreamableHttp(
name="dice",
params={"url": "http://localhost:8787/mcp"},
) as server:
agent = Agent(name="Dice", instructions="Use the tools to roll dice.",
model="gpt-6-astra", mcp_servers=[server])
result = await Runner.run(agent, "Roll 3d6")
print(result.final_output)
asyncio.run(main())import { Agent, MCPServerStreamableHttp, run } from "@openai/agents";
const server = new MCPServerStreamableHttp({ url: "http://localhost:8787/mcp", name: "dice" });
await server.connect();
try {
const agent = new Agent({ name: "Dice", instructions: "Use the tools to roll dice.",
model: "gpt-6-astra", mcpServers: [server] });
const result = await run(agent, "Roll 3d6");
console.log(result.finalOutput);
} finally {
await server.close();
}MCPServerStdio works the same way for servers started as a local command. Tracing is on by default, so every MCP call shows up on the Platform's Traces page.
Safety#
Warning: Remote MCP servers aren't verified by OpenAI. A malicious server can try to pull data out of anything in the model's context, or hide instructions in tool descriptions and results.
- Prefer official servers run by the service itself (Stripe's at
mcp.stripe.com, not a third-party proxy). - Keep approvals on for anything that writes, sends or spends.
- Limit tools with
allowed_tools. - Log what you send to each server and review it.
- Be careful with URLs that come back in tool output before fetching or embedding them.
- Data sent to an MCP server falls under that server's retention and residency policies, not OpenAI's.
Troubleshooting#
| Problem | Fix |
|---|---|
No mcp_list_tools item, or an error listing tools | Check the URL and path, and that the server speaks Streamable HTTP or SSE. Test it with MCP Inspector. |
| Model never calls the tool | Add a server_description, improve tool descriptions, or name the tool in the prompt. |
401 errors in mcp_call.error | The authorization value is missing or expired. Send it on every request. |
| Approval loop never ends | Reuse previous_response_id and include every pending approval_request_id. |
Agents SDK can't reach localhost | Hosted tools run on OpenAI's side. Use MCPServerStreamableHttp instead. |
Next steps#
- Find remote servers to call in the MCP directory.
- Build your own server: Build your first ChatGPT app.
- Use the same servers in ChatGPT: How to add a custom MCP server to ChatGPT.