Skip to content

Connect: OpenAI hosted MCP (Responses API)

Recipe · through our public MCP server · their docs checked 2026-10-30

With the Responses API mcp tool, OpenAI's servers call our server for you. So our server must be reachable from the internet, with auth. Data leaves our server to OpenAI with each tool result.

sequenceDiagram
  participant App as Your app
  participant O as OpenAI Responses
  participant S as public_app (/mcp)
  App->>O: responses.create(tools=[{type: "mcp", …}])
  O->>S: tools/list, tools/call + Authorization
  S-->>O: result (hosted tools only)
  O-->>App: output (+ mcp_approval_request when require_approval says so)

Our side

def public_app(tools: Registry, host: str, agent_token: str, reviewer_token: str) -> Any:
    """REST at /, MCP at /mcp, one bearer check for both: for a host on the internet."""
    from mcp.server.transport_security import TransportSecuritySettings

    from agent_adapter.server import create_app

    tokens = {
        agent_token: Principal("hosted-agent"),  # the token the remote framework sends
        reviewer_token: Principal("reviewer@example.com", scopes=frozenset({APPROVER_SCOPE})),
    }
    security = TransportSecuritySettings(allowed_hosts=[host], allowed_origins=[f"https://{host}"])
    return create_app(
        tools,
        authenticate=bearer_auth(tokens),
        host="0.0.0.0",  # a public bind: the public-host checks apply
        allowed_hosts=[host],  # REST: any other Host header gets 421
        mcp_options={"transport_security": security},  # MCP: the same rule
    )


hosted = registry.subset(tags=["hosted"])  # no confirm tools: hosted runtimes cannot resume them

Serve public_app(hosted, host, agent_token, reviewer_token).

Their side

response = client.responses.create(
    model=model,
    input="What is the shipping rate to the US?",
    tools=[
        {
            "type": "mcp",
            "server_label": "shop_tools",
            "server_description": "Shop lookups: shipping rates and invoices.",
            "server_url": "https://tools.example.com/mcp",
            "authorization": agent_token,  # not stored by OpenAI: send it on every request
            "allowed_tools": ["shipping_rate"],
            "require_approval": "always",
        }
    ],
)
Key Meaning
type: "mcp", server_label, server_url which server, under which name
authorization the token OpenAI sends to us; resend it on each request
allowed_tools the tools the model may see
require_approval "always", "never", or {"never": {"tool_names": [...]}}
mcp_approval_request → mcp_approval_response OpenAI's approval round trip: reply with approval_request_id and approve, chained by previous_response_id

Checklist

Check Why
serve hosted and list it in allowed_tools OpenAI does not send our resume keys back; a confirm tool would never run
require_approval is OpenAI's gate an approve: true there adds a step; it never approves our confirm tools (framework approvals)
a token only for OpenAI, with no APPROVER_SCOPE it is sent through a third party; rotate it on its own
everything on the public-host checklist

Sources