Repository navigation
Python: fix(claude): send the declared JSON schema of tools to Claude - #9302
Md. Shalah Uddin (shanto462) wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The focused fix uses the established schema API and includes appropriate regression coverage.
0 open findings
What changed in this PR
Fixes Claude tool conversion so dict-based JSON schemas retain their declared parameters.
Changes:
- Uses
FunctionTool.parameters()as the schema source. - Adds regression coverage for schema preservation and handler execution.
| File | Description |
|---|---|
python/packages/claude/agent_framework_claude/_agent.py |
Sources schemas from the unified tool API. |
python/packages/claude/tests/test_claude_agent.py |
Tests dict-schema conversion and invocation. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Eduard van Valkenburg (eavanvalkenburg)
left a comment
There was a problem hiding this comment.
Md. Shalah Uddin (@shanto462) The PR now reads the declared tool schema, but _agent.py rebuilds it with only type, properties, required, and $defs, so valid top-level constraints such as additionalProperties: false are still dropped before reaching the Claude Agent SDK. That makes Claude see a looser contract than the tool declares. Please pass the full schema through (or preserve every top-level keyword) and add a regression proving a schema-declared tool retains additionalProperties: false.
|
Eduard van Valkenburg (@eavanvalkenburg) Fixed in dfd9f80: the full schema from |
Motivation & Context
ClaudeAgentbuilt each tool's input schema fromfunc_tool.input_model.model_json_schema()and used{}wheninput_modelwasNone. AFunctionTooldeclared with a JSON schema dict keepsinput_model = None, so these tools reached Claude with no parameters. That includes@tool(schema={...}),Agent.as_tool()and the vector store tools. Claude then called them without the arguments they need. The conversion also rebuilt every schema from onlytype,properties,requiredand$defs, so other top-level keywords such asadditionalProperties: falsewere dropped and Claude saw a looser contract than the tool declares.Description & Review Guide
_function_tool_to_sdk_mcp_toolpasses the tool's full JSON schema fromfunc_tool.parameters()(the Pydantic model's JSON schema or the dict schema the tool was declared with) to the Claude Agent SDK, so top-level keywords such asadditionalProperties,titleand$defsare kept.typeis always"object"and a missing ornullpropertiesbecomes{}, because the SDK only uses a dict with a stringtypeand apropertieskey as a JSON schema; any other dict is read as a{name: python type}map. Tests check that a dict-schema tool keeps its full schema (additionalProperties: false,title,$defswith a$ref) and that its handler still runs, that a Pydanticextra="forbid"model keepsadditionalProperties: false, and that a schema without an objecttypeorproperties(or withproperties: null) gets both while the tool's own schema stays unchanged.title, andadditionalProperties: falsefor a model withextra="forbid". The SDK validates arguments against this schema, so such a tool now rejects extra arguments, as it declares. A schema withoutrequiredno longer gets an emptyrequiredlist, which means the same.tool.parameters()as is. Onlytypeand a missingpropertiesare set, so the SDK does not misread the schema. One thing to know: a declared schema with top-levelanyOf,oneOforallOfnow sends those keywords to Claude too, as the Anthropic chat client already does; before, they were dropped together with the parameters.Related Issue
Fixes #9301
No other open PR covers this issue.
AI Assistance
AI assistance details: issue analysis, implementation, tests, and this PR description.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.