Tool and MCP security
Status: DOCUMENTED (xAI mcp tool LIVE_VERIFIED against a public MCP server on 2026-09-19 — require_approval: "never" silently accepted, no approval item ever produced; Gemini mcpServers on generateContent UNVERIFIED)
Sources: https://developers.openai.com/api/docs/guides/tools-connectors-mcp (Approvals, Filtering tools, Authentication, Risks and safety) · https://developers.openai.com/api/docs/guides/secure-mcp-tunnels · https://platform.claude.com/docs/en/agents-and-tools/mcp-connector · https://platform.claude.com/docs/en/agents-and-tools/remote-mcp-servers · https://platform.claude.com/docs/en/api/messages · xAI: https://docs.x.ai/developers/tools/remote-mcp (server_url, server_label, allowed_tools, authorization, headers; "require_approval, connector_id not supported"), https://docs.x.ai/developers/tools/overview, https://docs.x.ai/developers/tools/function-calling, https://docs.x.ai/developers/tools/advanced-usage (max_turns, parallel_tool_calls), sources/xai/openapi/openapi.json (ModelTool mcp variant) · Gemini: https://ai.google.dev/gemini-api/docs/function-calling (#model-context-protocol-mcp, toolConfig.functionCallingConfig), https://ai.google.dev/api/generate-content (#Tool mcpServers[], #McpServer, #StreamableHttpTransport, #FunctionCallingConfig VALIDATED), https://ai.google.dev/api/interactions (mcp_server{name, url, headers, allowed_tools}), python-genai / js-genai READMEs (mcpToTool, automatic_function_calling.maximum_remote_calls)
Last verified: 2026-09-19
Two very different trust models
| Client (function) tools | Remote MCP / connectors | |
|---|---|---|
| Who executes | your code — the model only proposes name + arguments (Gemini: functionCall.args object; xAI/OpenAI: JSON string) |
the provider's infrastructure calls a third-party server; results flow back into the model without touching your code — OpenAI mcp, Anthropic mcp_servers, xAI mcp, Gemini tools[].mcpServers / Interactions mcp_server. Exception: Gemini's documented MCP path is SDK-side — the MCP client runs in your process and the wire only sees ordinary function calls |
| What can go wrong | your executor trusts model arguments (injection into shell/SQL/paths) | data you send is shared with a third party; the server can return injections; tool behaviour can change silently; your bearer tokens are forwarded by the provider (OpenAI authorization, Anthropic authorization_token, xAI authorization/headers, Gemini headers) |
| Provider controls | schema strict (OpenAI/Anthropic; xAI implicit), tool_choice (Gemini functionCallingConfig.mode incl. VALIDATED), parallel_tool_calls / disable_parallel_tool_use |
OpenAI require_approval, allowed_tools, authorization; Anthropic tool_configuration.{enabled, allowed_tools}, https-only; xAI allowed_tools only (no approval flow); Gemini SDK automatic_function_calling.disable / maximum_remote_calls, Interactions allowed_tools |
xAI Responses — {"type":"mcp", …} tool
| Field | Security meaning |
|---|---|
server_url (required; Streamable HTTP or SSE) · server_label (required; prefixes tool names in usage) · server_description |
identify the server; only trusted servers — xAI executes the call and injects the result into the model inside the same request |
allowed_tools |
string[]; empty = every tool the server lists is injected into context (docs). Always set it |
authorization · headers |
bearer token / extra headers forwarded to the MCP server by xAI: scope minimally, rotate, never reuse your xAI key here |
require_approval, connector_id |
docs: not supported; live: require_approval: "never" was silently accepted. There is no mcp_approval_request item — every listed tool can run without a human step. Gate side-effecting tools by not allowlisting them (or expose them as client function tools you execute) |
defer_loading |
alpha, 403 |
| Output | mcp_call {name, server_label, arguments, output, error} in output[]; output always present (no include needed) — treat as untrusted text (docs/tools/xai/mcp.md) |
| Also | usable inside Batch and Speech-to-Speech requests — same trust model applies to voice agents; bound the agentic loop with max_turns |
Gemini — SDK-side MCP vs server-side mcpServers
- SDK-side (documented, experimental):
config.tools=[mcp_session](Python) /mcpToTool(client)(JS). The SDK lists the server's tools, converts them tofunctionDeclarations, and on eachfunctionCallcalls the MCP server from your process (automatic_function_calling, defaultmaximum_remote_calls10). Credentials never reach Google; the injection surface is the tool list/descriptions and results (same as any client tool). Disable AFC (automatic_function_calling.disable=True) to inspect calls before execution; tools only (no MCP resources/prompts); the Live API has no automatic tool handling. - Server-side
tools[].mcpServers[]{name, streamableHttpTransport{url, headers, timeout, sseReadTimeout, terminateOnClose}}: present in the REST reference and discovery only;headers(your tokens) would be forwarded from Google's side; UNVERIFIED ongenerateContentin this atlas — do not rely on it. The Interactions API documentstools:[{"type":"mcp_server","name","url","headers","allowed_tools"}](Streamable HTTP only, names without-); setallowed_tools, use HTTPS, and remember Interactions store state by default (store: true). - Function calling controls:
toolConfig.functionCallingConfig.modeAUTO | ANY | NONE | VALIDATED(+allowedFunctionNames— a per-request tool allowlist); resetANYtoAUTOafter the forced turn or the model calls forever;parametersJsonSchemafor plain JSON Schema;behavior: NON_BLOCKINGis Live-only. - Gemini 3 thought signatures: replay the model turn verbatim; a tampered signature → 400 "Corrupted thought signature." (integrity check on your side of the loop — do not "fix" histories by hand).
OpenAI Responses — {"type":"mcp", …} tool
| Field | Security meaning |
|---|---|
server_label, server_url or connector_id |
identify the server; only connect to servers you trust ("It is very important that developers trust any remote MCP server they use") |
allowed_tools |
array of tool names the model may call — deny-by-default filtering at list time |
require_approval |
"always" (default), "never", or { "never": {"tool_names":[…]} } — with approvals, the model emits an mcp_approval_request output item; you reply with `{"type":"mcp_approval_response","approval_request_id":…,"approve":true |
authorization |
OAuth access token forwarded to the server. Documented behaviour: OpenAI does not store it, and it is not logged in stored responses — but you must scope it minimally and rotate it |
server_description |
free text seen by the model; keep it factual |
| Logging | with store: true (default), data sent to MCP servers is retained 30 days unless ZDR — the docs recommend logging shared data on your side and reviewing it |
| ZDR / data residency | MCP is compatible, but data leaving to the MCP server is governed by that server's policies — your responsibility |
Secure MCP Tunnels (secure-mcp-tunnels.md) let you expose a private MCP server to OpenAI without opening it to the Internet.
Anthropic Messages — MCP connector (beta)
"mcp_servers": [{
"type": "url", "url": "https://mcp.example.com/sse", "name": "example",
"authorization_token": "<oauth access token>",
"tool_configuration": {"enabled": true, "allowed_tools": ["read_issue", "search"]}
}]Header anthropic-beta: mcp-client-2025-11-20 (legacy mcp-client-2025-04-04 still accepted). Rules from the docs: url must start with https://; OAuth tokens are obtained by you (the API consumer handles the OAuth flow — the MCP Inspector "Quick OAuth Flow" is the documented helper) and passed per request; allowed_tools restricts what the model can call; enabled: false disables a server without removing it. Results arrive as mcp_tool_use / mcp_tool_result blocks (is_error on failure). There is no server-side approval flow equivalent to OpenAI's require_approval — gate side-effecting MCP tools by not allowlisting them, or route them as client tools you execute yourself.
Client (function) tools — all four providers
- Schema strictness: OpenAI
tools[].strict: true; Anthropictools[].strict: true; xAI: tool schemas are always strictly enforced (thestrictflag is accepted and ignored) andadditionalPropertiesdefaults to false; Geminimode: VALIDATEDvalidates calls against the declaration, otherwise best-effort. Strictness guarantees shape, not safety — a valid{"path": "../../etc/passwd"}is still valid. - Tool choice: force or forbid tools per request (
tool_choice: "none"/{type:"none"}/ Geminimode: NONE) when reading untrusted content; per-request allowlists via OpenAI/xAI{type:"function", name}or GeminiallowedFunctionNames. - Parallel calls: OpenAI/xAI
parallel_tool_calls: false, Anthropicdisable_parallel_tool_use: truewhen tools have ordering-dependent side effects; Gemini parallel calls arrive as severalfunctionCallparts — answer all of them in one Content, and note only the first carries athoughtSignature. - Agentic loop bounds: xAI
max_turns(server-side tools run inside one request — a runaway loop is billed per call: web_search/x_search/code_interpreter $5/1k), Gemini SDKmaximum_remote_calls, your ownmax_roundsin the shared tool loop. - Anthropic programmatic tool calling:
allowed_callers: ["code_execution_20250825"]— decide explicitly whether a tool may be invoked from generated code. Gemini 3 "tool combination" similarly lets server tools and your functions interleave (includeServerSideToolInvocations). - Descriptions are part of your prompt: keep them precise; never embed secrets (API URLs with tokens) in descriptions — on xAI the MCP
server_descriptionis also model-visible. - Validate in your executor (
schema-validation.md,command-and-path-injection.md) and return errors withis_error: true(Anthropic) / explicit status text (OpenAI, xAI) /{"error": …}object (GeminifunctionResponse.response) (untrusted-tool-outputs.md).
Checklist
- Every MCP server is inventoried with owner, URL, auth scope, and reviewed tool list.
- OpenAI:
allowed_toolsset;require_approvalleft atalwaysexcept for reviewed read-only tools; approval UI shows the exact arguments. - Anthropic:
tool_configuration.allowed_toolsset; https URLs only; beta header pinned; side-effecting MCP tools excluded or executed client-side with a human gate. - xAI:
allowed_toolsalways set (empty = everything); only read-only tools on remote servers because there is no approval flow;authorization/headersscoped to that server;max_turnsbounded. - Gemini: prefer SDK-side MCP (credentials stay local); disable automatic function calling when a human must approve; Interactions
mcp_server.allowed_toolsset; server-sidemcpServerson generateContent not used until verified. - OAuth tokens for MCP scoped minimally, short-lived, rotated; never logged; never a provider API key.
- Data sent to MCP servers logged on your side (per OpenAI recommendation) and reviewed periodically; xAI/Gemini MCP outputs treated as untrusted text.
- Tool descriptions and lists pinned; alert on change.
- Client tools: strict schemas (
VALIDATEDon Gemini) + executor-side validation + idempotency per call id. -
tool_choice: none/mode: NONE(or no tools) when the request's only job is to read untrusted content.