Skip to content

fix: provider-agnostic tool schema for Bedrock/Gemini MCP tools (#4472) - #4912

Closed
joaquinhuigomez wants to merge 6 commits into
crewAIInc:mainfrom
joaquinhuigomez:fix/mcp-tool-schema-bedrock-gemini
Closed

fix: provider-agnostic tool schema for Bedrock/Gemini MCP tools (#4472)#4912
joaquinhuigomez wants to merge 6 commits into
crewAIInc:mainfrom
joaquinhuigomez:fix/mcp-tool-schema-bedrock-gemini

Conversation

@joaquinhuigomez

@joaquinhuigomez joaquinhuigomez commented Mar 16, 2026

Copy link
Copy Markdown

Summary

Fixes #4472 — MCP tools break with Bedrock and Gemini because extract_tool_info() uses generate_model_description(), which applies OpenAI-specific JSON schema transformations that these providers reject:

  • additionalProperties: false — rejected by Bedrock
  • All properties forced into required — breaks optional field semantics on Gemini/Bedrock
  • title on nested objects — rejected by Gemini

What this PR does

  • Adds generate_tool_parameters_schema() in pydantic_schema_utils.py — a clean, provider-agnostic alternative to generate_model_description() that:
  • Updates extract_tool_info() in common.py to use the new function for the args_schema path, which flows through safe_tool_conversion() to all providers (Bedrock, Gemini, Anthropic, OpenAI, Azure)
  • Adds 8 targeted tests covering: no additionalProperties, optional fields not forced required, no title on nested objects, refs resolved, null stripped, defaults stripped, descriptions preserved, types preserved

Context

Why this approach

The generate_model_description() function is correct for OpenAI structured outputs — it needs additionalProperties: false, all-required, and strict mode. The problem is that extract_tool_info() (shared across providers) was reusing it for tool parameter schemas. This PR separates the two concerns with a dedicated function.

Test plan

  • All 8 new TestGenerateToolParametersSchema tests pass
  • All 81 existing test_pydantic_schema_utils.py tests pass (no regressions)
  • All 59 existing test_agent_utils.py tests pass (no regressions)
  • Manual verification with Bedrock/Gemini MCP tool calls (needs contributor with access)

Note

Medium Risk
Changes how tool args_schema is converted into JSON Schema for all providers, which can affect tool-calling payload validation and optional/required semantics. Mitigated by focused unit tests, but still touches a cross-provider integration surface.

Overview
Fixes MCP tool failures on Bedrock/Gemini by separating provider-agnostic tool parameter schema generation from OpenAI strict-mode schemas.

extract_tool_info() now uses a new generate_tool_parameters_schema() for Pydantic args_schema, producing a cleaned schema (refs inlined, null stripped, and removing title/default/additionalProperties) without forcing all fields required. OpenAI tool conversion keeps (and strengthens) its strict-mode pipeline by explicitly applying strip_unsupported_formats, ensure_type_in_schemas, convert_oneof_to_anyof, ensure_all_properties_required, and force_additional_properties_false.

Adds comprehensive tests for the new provider-agnostic schema output and a few strict-mode transform assertions.

Written by Cursor Bugbot for commit e0a819a. This will update automatically on new commits. Configure here.

Comment thread lib/crewai/src/crewai/utilities/pydantic_schema_utils.py
…flake-connector-python) (crewAIInc#4913)

- authlib 1.6.7 → 1.6.9 (CVE-2026-27962 critical, CVE-2026-28498, CVE-2026-28490)
- PyJWT 2.11.0 → 2.12.1 (CVE-2026-32597)
- snowflake-connector-python 4.2.0 → 4.3.0
@joaquinhuigomez

Copy link
Copy Markdown
Author

Addressed the Cursor Bugbot issue:

Bug: _strip_schema_metadata was popping title, default, and additionalProperties from the properties mapping dict itself, not just from schema nodes. A tool model with a field named title would have its entire property definition silently removed — breaking the schema while required still referenced it.

Fix: Added a _is_properties flag. When True (i.e. we're iterating a properties mapping where keys are field names), metadata stripping is skipped for that dict. Recursion into each property's schema still strips correctly.

Latest commit: a99502cac

@joaquinhuigomez
joaquinhuigomez force-pushed the fix/mcp-tool-schema-bedrock-gemini branch from a99502c to d2f6073 Compare March 17, 2026 01:11
Comment thread lib/crewai/src/crewai/utilities/pydantic_schema_utils.py Outdated
Comment thread lib/crewai/src/crewai/llms/providers/utils/common.py
* fix: enhance LLM response handling and serialization

* Updated the Flow class to improve error handling when both structured and simple prompting fail, ensuring the first outcome is returned as a fallback.
* Introduced a new function, _serialize_llm_for_context, to properly serialize LLM objects with provider prefixes for better context management.
* Added tests to validate the new serialization logic and ensure correct behavior when LLM calls fail.

This update enhances the robustness of LLM interactions and improves the overall flow of handling outcomes.

* fix: patch VCR response handling to prevent httpx.ResponseNotRead errors (crewAIInc#4917)

* fix: enhance LLM response handling and serialization

* Updated the Flow class to improve error handling when both structured and simple prompting fail, ensuring the first outcome is returned as a fallback.
* Introduced a new function, _serialize_llm_for_context, to properly serialize LLM objects with provider prefixes for better context management.
* Added tests to validate the new serialization logic and ensure correct behavior when LLM calls fail.

This update enhances the robustness of LLM interactions and improves the overall flow of handling outcomes.

* fix: patch VCR response handling to prevent httpx.ResponseNotRead errors

VCR's _from_serialized_response mocks httpx.Response.read(), which
prevents the response's internal _content attribute from being properly
initialized. When OpenAI's client (using with_raw_response) accesses
response.content, httpx raises ResponseNotRead.

This patch explicitly sets response._content after the response is
created, ensuring that tests using VCR cassettes work correctly with
the OpenAI client's raw response handling.

Fixes tests:
- test_hierarchical_crew_creation_tasks_with_sync_last
- test_conditional_task_last_task_when_conditional_is_false
- test_crew_log_file_output


---------

Co-authored-by: Joao Moura <joaomdmoura@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: alex-clawd <alex@crewai.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread lib/crewai/src/crewai/llms/providers/openai/completion.py
…AIInc#4472)

Add generate_tool_parameters_schema() that produces clean JSON schemas
without OpenAI-specific artifacts (additionalProperties, forced required
for all fields, title on nested objects) that Bedrock and Gemini reject.

Update extract_tool_info() to use the new function instead of
generate_model_description() for the args_schema code path, which is
shared across all providers via safe_tool_conversion().
…ema_metadata

A tool model with a field named 'title' or 'default' would have its
entire property definition silently removed, producing a broken schema.

Fix by skipping metadata stripping when iterating a 'properties' mapping
(where keys are field names, not schema keywords).
…t mode

- _strip_schema_metadata: distinguish between properties-mapping dicts
  (where keys are user field names) and schema nodes. Previously, fields
  named "title", "default", or "additionalProperties" were silently
  removed from the schema. Now the function only strips metadata keys
  from schema nodes, not from the properties mapping itself.
- _strip_schema_metadata: a field literally named "properties" no longer
  causes its schema node to be misidentified as a properties mapping,
  which would skip metadata stripping.
- OpenAI strict mode: re-apply ensure_all_properties_required in
  _convert_tools_for_interference so that MCP tools with optional
  parameters are not rejected by the OpenAI API.
@joaquinhuigomez
joaquinhuigomez force-pushed the fix/mcp-tool-schema-bedrock-gemini branch from 885b728 to 51ccbf1 Compare March 17, 2026 14:17
…erference

Switching extract_tool_info to generate_tool_parameters_schema removed
three strict-mode transforms that generate_model_description previously
applied. Re-add them explicitly in the OpenAI provider:

  strip_unsupported_formats   — removes 'uri', 'email', 'uuid' formats
  ensure_type_in_schemas      — converts empty {} to {"type": "object"}
  convert_oneof_to_anyof      — normalises oneOf → anyOf for strict mode

ensure_all_properties_required and force_additional_properties_false
were already present. Pipeline now matches the original behaviour exactly.

Also adds:
- test for field named 'properties' not suppressing sibling stripping
- unit tests for all three newly-wired transforms
@joaquinhuigomez

Copy link
Copy Markdown
Author

Addressed all three Cursor Bugbot findings:

High — OpenAI strict mode missing ensure_all_properties_required
Already added in the previous commit. Confirmed present.

Medium — Missing strip_unsupported_formats, ensure_type_in_schemas, convert_oneof_to_anyof
Fixed in latest commit. _convert_tools_for_interference now applies the full 5-transform pipeline in the same order as generate_model_description previously did:

  1. strip_unsupported_formats — removes unsupported format annotations (uri, email, uuid)
  2. ensure_type_in_schemas — converts empty {} to {"type": "object"} in anyOf/oneOf
  3. convert_oneof_to_anyof — normalises oneOf → anyOf for strict mode
  4. ensure_all_properties_required
  5. force_additional_properties_false

Low — Field named properties incorrectly treated as a properties map
Reviewed the implementation: this is not a bug. When _is_properties_map=True, the code iterates for v in d.values() — keys (field names) are never inspected, so a field named properties is correctly processed as a schema node with _is_properties_map=False. Added a regression test (test_field_named_properties_not_treated_as_properties_map) to lock this in.

All 88 schema utils tests pass.

@joaquinhuigomez
joaquinhuigomez force-pushed the fix/mcp-tool-schema-bedrock-gemini branch from e0a819a to 3804278 Compare March 24, 2026 20:51
@joaquinhuigomez

Copy link
Copy Markdown
Author

Addressed the review comments — let me know if you need anything else.

@joaquinhuigomez

Copy link
Copy Markdown
Author

Closing this from my side — it's gone stale and the underlying schema work has likely shifted. Happy to revisit later if there's still interest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] JSON schema issue between CrewAI MCPServerAdapter and Bedrock LLM Claude / Gemini when using Tool Inputs

4 participants