mcpServers when you open a thread. Those servers are attached to this thread only.
{ type: "http" | "sse", url, headers? }{ type: "stdio", command, args?, env? }
mcpServers turns on strict mode unless you set loadUserMcpServers: true. Strict means: withhold the user’s and project’s own MCP config, and try to load only the servers you supplied.
Honesty table
mcpCapability
Every thread that requested MCP reports what it actually got. null means one of two things: this thread never asked for MCP, or it asked and an older runtime omitted the report. The SDK logs the second case (requested MCP but the runtime reported no capability). Do not read null as “no MCP was requested” when you passed mcpServers or loadUserMcpServers: false — treat the guarantee as unverified.
On a delivery-only thread (
loadUserMcpServers: true) the user’s own MCP loads by design. strictRequested is false, residual is null, and level makes no isolation claim. Treating level === "enforced" as “only my tools” on that thread would be backwards.
Presence of the mcpCapability object is not a guarantee. Read strictRequested, then branch on level.
After setModel, the report is refreshed. A Claude thread switched to Codex will not keep advertising enforced.
Opt back in
false is not the same as omitting the field. Omitting both mcpServers and loadUserMcpServers lets the embedded session profile decide (SDK threads are strict by default).
Next
Chat UI
Show tool activity to your users as labels, not raw tool names.
