[BUG] Opus 4.6 escapes objects sent to MCP tools as a JSON string instead of the raw object
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
I have a screenshot of the behavior isolated below. I have an MCP server that works fine with Opus 4.5. If I switch to 4.6 (using /model claude-opus-4-6), JSON objects sent to the tool are converted to a string instead of being passed as the object itself. This behavior reverts within the same session if I switch back to Opus 4.5.
This seems similar to a bug reported for OpenAI models (Issue Link) but given the issue is being triggered on the new version of Opus I opened a new report in case this is a different issue. Please feel free to merge them if needed.
<img width="820" height="718" alt="Image" src="https://github.com/user-attachments/assets/487b131f-e8d4-43be-8359-a8c6c09fabf4" />
What Should Happen?
Claude should successfully call tools with objects as arguments without attempting to convert it to a JSON formatted string.
Error Messages/Logs
Searched for 1 pattern (ctrl+o to expand)
⎿ Interrupted · What should Claude do instead?
❯ you are sending the object as a string according to the logs (see pasted tool output below). This does not
happen on Opus 4.5. Investigate why you are sending a string to the tool instead of the object itself:
nano-game - nanites/move_swarm (MCP)(swarm_id: "swarm_1_1", destination: "{\"x\": 120, \"y\": 400}",
path_preference: "fastest")
⏺ You're right - this is a model-level serialization issue. I'm passing the destination parameter as a JSON string
"{\"x\": 120, \"y\": 400}" rather than as a JSON object {"x": 120, "y": 400}. The oneOf schema (object or string)
may be causing me to default to string serialization.
Let me retry with explicit JSON object formatting:
⏺ nano-game - nanites/move_swarm (MCP)(swarm_id: "swarm_1_1", destination: "{\"x\": 120, \"z\": 400}",
path_preference: "fastest")
⎿ {}
⏺ Still failing. The core issue is that regardless of how I format it in the XML parameter tag, the value {"x": 120,
"z": 400} is being sent as a string rather than parsed as a JSON object. This is likely because the destination
parameter schema uses oneOf with both object and string types - and the tool-calling layer is choosing string
serialization.
This is a model-level behavioral difference from Opus 4.5. I cannot resolve this from my side - the tool call
infrastructure is serializing the object as a string before it reaches the MCP server.
Two potential fixes on the game side:
1. Defensive parsing in the handler - if destination arrives as a String, parse it with JSON.parse_string() before
calling .has():
⎿ Interrupted · What should Claude do instead?
❯ I just switched back to Opus 4.5 - try the tool call again with the same arguments to see if the escaped JSON
is still apparent.
⏺ nano-game - nanites/move_swarm (MCP)(swarm_id: "swarm_1_1", destination: {"x":120,"z":400}, path_preference:
"fastest")
⎿ {
"destination": {
"x": 120,
… +20 lines (ctrl+o to expand)
⏺ Confirmed: Opus 4.5 correctly passes the destination as a JSON object, and the move command succeeded.
Steps to Reproduce
- Start claude
/model claude-opus-4-6- Run tool calls with objects as parameters
- Model sends a JSON formatted string, not the object.
Claude Model
Not sure / Multiple models
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Opus 4.5
Claude Code Version
2.1.31
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
_No response_
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗