create_sdk_mcp_server tools cannot opt into strict tool use: tools/list reconstruction drops the field
Summary
Tools registered through create_sdk_mcp_server cannot opt into strict tool use. The SDK rebuilds the tools/list response by hand and keeps only name / description / inputSchema / annotations / _meta, so a strict field set by the tool author is silently dropped before it reaches the CLI. There is no other route: the CLI has no _meta key for it either, and no hook fires for the failure strict would prevent.
For agents whose custom tools take array parameters this matters in production: we measure the model emitting unparseable JSON for an array argument in 17 of 18 turns on one tool shape (details below), the exact failure class strict: true exists to eliminate.
Where the field is dropped
claude_agent_sdk/_internal/query.py, _handle_sdk_mcp_request, tools/list branch (v0.2.131, lines ~653-672):
tool_data: dict[str, Any] = {
"name": tool.name,
"description": tool.description,
"inputSchema": (...),
}
if tool.annotations:
tool_data["annotations"] = tool.annotations.model_dump(exclude_none=True)
if tool.meta:
tool_data["_meta"] = tool.meta
tools_data.append(tool_data)mcp.types.Tool is extra="allow", so Tool.model_validate({..., "strict": True}) succeeds — but the reconstruction above discards the extra field instead of forwarding it.
What we verified before filing (SDK 0.2.131, CLI 2.1.252, macOS arm64)
- The CLI honors
strictwhen its internal tool object carries it. The bundled binary contains the pipeline:if(e.strict===!0){...fe.strict=!0, fe.input_schema=<normalized>} else <warn "Tool ${name} has strict: true but its schema is not strict-compatible">, and the API tool definition builder spreads...fe.strict&&{strict:!0}. So the gap is transport, not capability. - Top-level
stricton the MCPToolnever crosses the wire — dropped by the reconstruction above (confirmed by invoking_handle_sdk_mcp_requestdirectly with a synthetictools/listmessage: the emitted dict has keysname/description/inputSchemaonly). _meta: {"anthropic/strict": true}crosses the wire but nothing reads it — a byte search of the CLI binary finds zero occurrences ofanthropic/strict; the closed set ofanthropic/*_metakeys it knows isalwaysLoad, claude, configs, devicePassthrough, dirSync, hearth, id, maxResultSizeChars, models, permissionDisplay, remoteToolExecution, requiresUserInteraction, searchHint, v.- Live confirmation: with both injection variants applied, 5 of 5 turns still produced a tool call whose input the CLI replaced with
{"__unparsedToolInput": ...}, and the CLI's strict-incompatible-schema warning never appeared on stderr. - No hook can compensate: for a tool call whose input JSON cannot be parsed, no hook fires at all —
PreToolUse,PostToolUse,PostToolUseFailurewere all instrumented; 20/20 successful calls in the same turns logged hooks, the malformed call logged none.
Why this matters (measured impact)
Production agent (claude-sonnet-5, effort=high, adaptive thinking), five in-process MCP retrieval tools. One tool has an array-of-strings parameter next to a required enum parameter. The model emits the array value as bare unquoted words — {"jurisdiction": "se", "terms": token machine payout percentage} — in 17 of 18 turns on that tool shape (1 of 9 on the same tool without the enum). The call never runs; the CLI substitutes __unparsedToolInput and returns its own InputValidationError. Description engineering reduced it to 4/18; it cannot reach zero, and the model sometimes abandons the search after the rejection instead of re-sending it.
Independent report of the same regression class on this model family, where strict: true eliminated it via the raw API: https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/
Ask
Let an SdkMcpTool (and/or the MCP Tool listing) declare strict: true and forward it to the CLI's tool-definition pipeline, which already knows how to normalize the schema or warn. The same forwarding gap presumably applies to input_examples.
Environment
- claude-agent-sdk 0.2.131 (Python 3.12), mcp 1.29.1
- Claude Code CLI 2.1.252, macOS arm64
- Model: claude-sonnet-5, effort high, adaptive thinking
Source: anthropics/claude-agent-sdk-python