create_sdk_mcp_server tools cannot opt into strict tool use: tools/list reconstruction drops the field

Author: robosoulCreated Sep 1, 2026Updated Sep 7, 2026
Labelsenhancement

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

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

  1. The CLI honors strict when 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.
  2. Top-level strict on the MCP Tool never crosses the wire — dropped by the reconstruction above (confirmed by invoking _handle_sdk_mcp_request directly with a synthetic tools/list message: the emitted dict has keys name/description/inputSchema only).
  3. _meta: {"anthropic/strict": true} crosses the wire but nothing reads it — a byte search of the CLI binary finds zero occurrences of anthropic/strict; the closed set of anthropic/* _meta keys it knows is alwaysLoad, claude, configs, devicePassthrough, dirSync, hearth, id, maxResultSizeChars, models, permissionDisplay, remoteToolExecution, requiresUserInteraction, searchHint, v.
  4. 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.
  5. No hook can compensate: for a tool call whose input JSON cannot be parsed, no hook fires at all — PreToolUse, PostToolUse, PostToolUseFailure were 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