Summary
On some MCP tool-calls, the SDK appears to emit (or fail to fully parse) a tool-call where the model wrote <prompt>…</prompt> instead of <parameter name="prompt">…</parameter>. The result reaching the MCP server is that the first string parameter contains the whole trailing </prompt>\n<parameter name="X">VALUE… blob, and the rest of the parameters arrive as undefined.
Environment
@anthropic-ai/claude-agent-sdk 0.2.92
- Model:
claude-opus-4-8
- Transport: stdio MCP server from
@modelcontextprotocol/sdk
- Tools registered with
server.tool(name, desc, zod-shape, handler) — three string args (prompt, preset?, caption?)
Observed payload
Three independent calls in the same session — every one of them stored the same shape in our DB (real values, untouched):
prompt:
"Flat 2D UI design of an iOS 26 music player … dark mode.</prompt>\n<parameter name=\"preset\">[\"1024x1536\",\"format=png\",\"quality=high\"]"
preset: undefined
caption: undefined
i.e. the model wrote <prompt>…</prompt> rather than <parameter name="prompt">…</parameter>, the parser didn't find a matching </parameter>, and instead of bouncing the whole call or treating the next <parameter name="…"> as the start of a new arg, it concatenated the tail into args.prompt.
Expected behaviour
One of:
- Treat
<TAG>…</TAG> (where TAG matches a declared parameter name) as a valid alternate form of <parameter name="TAG">…</parameter>, OR
- Return a structured tool-call parse error so the agent can self-correct, OR
- Stop the value at the next
<parameter name="…"> boundary even when the current </parameter> is missing.
Current behaviour silently corrupts the args.
Why it bites users
In our case this caused FED-32 — an image_generation tool whose preset arrived as undefined, so the host's defaults kicked in and every call returned a 1024×1024 square regardless of the explicit ["portrait"] / ["1024x1536"] requested. Without inspecting the model's raw tool-call we'd have spent a long time looking for the bug in our preset parser.
We've shipped a workaround that detects the boundary, restores known params, and refuses calls where the boundary fired but no recognized parameter could be recovered (so the failure becomes visible instead of silently defaulting). It's a clear compensation — happy to share the normalizer code if useful.
Asks
- Confirm whether this is a known issue (couldn't find a direct match).
- Decide whether the right fix lives in the SDK's tool-call parser (option 3 above seems lowest-risk).
- Anything you'd like us to capture next time we see it in the wild — full request trace, MCP frames, model output, etc?
Summary
On some MCP tool-calls, the SDK appears to emit (or fail to fully parse) a tool-call where the model wrote
<prompt>…</prompt>instead of<parameter name="prompt">…</parameter>. The result reaching the MCP server is that the first string parameter contains the whole trailing</prompt>\n<parameter name="X">VALUE…blob, and the rest of the parameters arrive asundefined.Environment
@anthropic-ai/claude-agent-sdk0.2.92claude-opus-4-8@modelcontextprotocol/sdkserver.tool(name, desc, zod-shape, handler)— three string args (prompt,preset?,caption?)Observed payload
Three independent calls in the same session — every one of them stored the same shape in our DB (real values, untouched):
i.e. the model wrote
<prompt>…</prompt>rather than<parameter name="prompt">…</parameter>, the parser didn't find a matching</parameter>, and instead of bouncing the whole call or treating the next<parameter name="…">as the start of a new arg, it concatenated the tail intoargs.prompt.Expected behaviour
One of:
<TAG>…</TAG>(whereTAGmatches a declared parameter name) as a valid alternate form of<parameter name="TAG">…</parameter>, OR<parameter name="…">boundary even when the current</parameter>is missing.Current behaviour silently corrupts the args.
Why it bites users
In our case this caused FED-32 — an
image_generationtool whosepresetarrived asundefined, so the host's defaults kicked in and every call returned a 1024×1024 square regardless of the explicit["portrait"]/["1024x1536"]requested. Without inspecting the model's raw tool-call we'd have spent a long time looking for the bug in our preset parser.We've shipped a workaround that detects the boundary, restores known params, and refuses calls where the boundary fired but no recognized parameter could be recovered (so the failure becomes visible instead of silently defaulting). It's a clear compensation — happy to share the normalizer code if useful.
Asks