Skip to content

Python: fix(claude): send the declared JSON schema of tools to Claude - #9302

Open
Md. Shalah Uddin (shanto462) wants to merge 2 commits into
microsoft:mainfrom
shanto462:fix-claude-tool-json-schema
Open

Md. Shalah Uddin (shanto462) wants to merge 2 commits into
microsoft:mainfrom
shanto462:fix-claude-tool-json-schema

Conversation

@shanto462

@shanto462 Md. Shalah Uddin (shanto462) commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Motivation & Context

ClaudeAgent built each tool's input schema from func_tool.input_model.model_json_schema() and used {} when input_model was None. A FunctionTool declared with a JSON schema dict keeps input_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 only type, properties, required and $defs, so other top-level keywords such as additionalProperties: false were dropped and Claude saw a looser contract than the tool declares.

Description & Review Guide

  • What are the major changes? _function_tool_to_sdk_mcp_tool passes the tool's full JSON schema from func_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 as additionalProperties, title and $defs are kept. type is always "object" and a missing or null properties becomes {}, because the SDK only uses a dict with a string type and a properties key 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, $defs with a $ref) and that its handler still runs, that a Pydantic extra="forbid" model keeps additionalProperties: false, and that a schema without an object type or properties (or with properties: null) gets both while the tool's own schema stays unchanged.
  • What is the impact of these changes? Dict-schema tools reach Claude with their declared parameters and constraints. Pydantic-model and signature-based tools also keep their top-level keywords now, for example title, and additionalProperties: false for a model with extra="forbid". The SDK validates arguments against this schema, so such a tool now rejects extra arguments, as it declares. A schema without required no longer gets an empty required list, which means the same.
  • What do you want reviewers to focus on? Passing the schema through whole matches the Anthropic, Bedrock and GitHub Copilot integrations, which already send tool.parameters() as is. Only type and a missing properties are set, so the SDK does not misread the schema. One thing to know: a declared schema with top-level anyOf, oneOf or allOf now 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

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: issue analysis, implementation, tests, and this PR description.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@shanto462

Copy link
Copy Markdown
Contributor Author

Eduard van Valkenburg (@eavanvalkenburg) Fixed in dfd9f80: the full schema from parameters() is now passed through, with only type: "object" and a missing properties set so the SDK reads it as a JSON schema, and new regressions check that a declared schema and a Pydantic extra="forbid" model keep additionalProperties: false.

This branch was successfully deployed

1 active deployment
github-app-auth — dfd9f800 Deployed Oct 16, 2026 by shanto462 via add_label #25594
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: ClaudeAgent sends tools declared with a JSON schema to Claude without their parameters

3 participants