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.

Divy ChaurasiaIntermediate 20 minFor builders

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#

bash
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#

python
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)
typescript
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:

FieldPurpose
typeAlways "mcp".
server_labelRequired. A short name used in output items and approvals.
server_urlThe remote endpoint, including its path.
server_descriptionOptional. Helps the model decide when to use the server.
require_approval"always", "never", or a per-tool filter (step 4).
allowed_toolsOptional list that limits which tools are imported (step 3).
authorizationOptional 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, with name, the arguments the model sent, the output the server returned, and an error field if something failed.
  • mcp_approval_request: a call waiting for your approval.
python
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:

python
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:

python
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)
typescript
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:

python
"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:

python
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-client next to the server and pass tunnel_id in place of server_url.
  • Built-in connectors (connector_id, for example connector_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, is https://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:

python
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())
typescript
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, hostedMcpTool defaults requireApproval to "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:

python
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())
typescript
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#

ProblemFix
No mcp_list_tools item, or an error listing toolsCheck the URL and path, and that the server speaks Streamable HTTP or SSE. Test it with MCP Inspector.
Model never calls the toolAdd a server_description, improve tool descriptions, or name the tool in the prompt.
401 errors in mcp_call.errorThe authorization value is missing or expired. Send it on every request.
Approval loop never endsReuse previous_response_id and include every pending approval_request_id.
Agents SDK can't reach localhostHosted tools run on OpenAI's side. Use MCPServerStreamableHttp instead.

Next steps#

Keep reading