Skip to content

fix: handle complex JSON schemas in MCP adapter to prevent KeyError#4313

Closed
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1769793634-fix-mcp-adapter-json-schema
Closed

fix: handle complex JSON schemas in MCP adapter to prevent KeyError#4313
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1769793634-fix-mcp-adapter-json-schema

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Jan 30, 2026

fix: handle complex JSON schemas in MCP adapter to prevent KeyError

Fixes #4312

Summary

The MCP adapter was failing to initialize when connecting to MCP servers with complex JSON schemas containing internal $ref references (e.g., Mapbox MCP server). The error was KeyError: '#/properties/geometry/anyOf/0/items'.

The root cause was that the external mcpadapt library's CrewAIAdapter doesn't fully resolve nested $ref references before creating Pydantic models. When Pydantic generates JSON schema from the dynamically created model, it encounters unresolved references.

This fix creates a custom CrewAIAdapterWithSchemaFix class that:

  • Uses jsonref to fully resolve all $ref references before creating Pydantic models
  • Implements _create_model_from_schema() to handle complex schema patterns (anyOf, arrays, nested refs)
  • Replaces the default CrewAIAdapter in MCPServerAdapter

Review & Testing Checklist for Human

  • Test with actual Mapbox MCP server - The unit tests use synthetic schemas mimicking the problematic patterns, but end-to-end testing with https://mcp.mapbox.com/mcp is recommended to confirm the fix works in practice
  • Verify jsonref dependency - The code imports jsonref; confirm it's already available in the project dependencies or add it if needed
  • Review _create_model_from_schema edge cases - This function (~140 lines) handles many JSON schema patterns; verify it covers schemas from other MCP servers you use
  • Check nullable field handling - The _run method filters kwargs based on schema nullability; verify this doesn't break existing MCP tool calls

Recommended Test Plan

  1. Run the new unit tests: uv run pytest lib/crewai-tools/tests/adapters/mcp_adapter_test.py -k "TestResolveAllRefs or TestCreateModelFromSchema or TestCrewAIAdapterWithSchemaFix" -v
  2. Test with Mapbox MCP server using the reproduction code from issue [BUG] An error occurred while running the crew: Failed to initialize MCP Adapter: '#/properties/geometry/anyOf/0/items' #4312
  3. Test with any other MCP servers you commonly use to ensure no regressions

Notes

  • The existing SSE tests have pre-existing timeout issues unrelated to this change
  • Uses # noqa: UP007 for dynamic Union[tuple(types)] construction which is necessary at runtime

Link to Devin run: https://app.devin.ai/sessions/54d65cdd4d604f2fafeebd6d0237007a
Requested by: João


Note

Medium Risk
Changes the MCP-to-CrewAI tool adaptation path by replacing the upstream adapter and generating dynamic Pydantic models from resolved schemas, which could affect tool argument validation/serialization across MCP servers.

Overview
Prevents MCP adapter initialization failures on complex tool schemas (notably nested/internal $refs) by introducing CrewAIAdapterWithSchemaFix, which resolves all schema references via jsonref and builds Pydantic args_schema models with custom handling for anyOf, arrays, nullability, and $defs.

MCPServerAdapter now uses this custom adapter instead of the upstream CrewAIAdapter, and tests were added to cover $ref resolution, model generation (including model_json_schema() not raising), and adapter behavior.

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

Fixes #4312

The issue was that MCP servers with complex JSON schemas containing internal
$ref references (like Mapbox MCP server) would cause a KeyError when
Pydantic tried to generate JSON schemas from dynamically created models.

This fix:
- Creates a custom CrewAI adapter (CrewAIAdapterWithSchemaFix) that properly
  resolves all $ref references using jsonref before creating Pydantic models
- Implements _resolve_all_refs() to fully resolve JSON Schema references
- Implements _create_model_from_schema() that creates Pydantic models without
  passing problematic extra fields that cause issues during schema generation
- Adds comprehensive tests for the new schema handling functionality

Co-Authored-By: João <joao@crewai.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 5 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.


result = func(filtered_kwargs)
return (
result.content[0].text
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing text attribute check causes crash for non-text content

High Severity

The _run method accesses result.content[0].text directly when there's exactly one content item, without verifying the content has a text attribute. MCP tools can return non-text content types (like ImageContent or EmbeddedResource) that don't have a text attribute. The else branch correctly uses hasattr(content, "text") to filter items, but this check is missing for the single-item case, causing an AttributeError crash when an MCP tool returns a single non-text response.

Fix in Cursor Fix in Web

return field_type, ... if is_required else default

if field_schema.get("type") == "array" and "items" in field_schema:
item_type, _ = get_field_type("item", field_schema["items"], set())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Array items incorrectly become nullable types

Medium Severity

The get_field_type function misinterprets JSON schema nullability and required status. It incorrectly makes array items nullable and removes nullability from explicitly nullable, required fields. This results in Pydantic models that reject valid None values.

Fix in Cursor Fix in Web


if field_schema.get("type") == "array" and "items" in field_schema:
item_type, _ = get_field_type("item", field_schema["items"], set())
field_type = list[item_type]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tuple validation schemas crash on array items

Medium Severity

When items is an array (JSON Schema tuple validation, e.g., {"type": "array", "items": [{"type": "number"}, {"type": "number"}]}), the recursive call get_field_type("item", field_schema["items"], set()) passes a list instead of a dict. The subsequent field_schema.get("type") at line 187 or 191 crashes with AttributeError: 'list' object has no attribute 'get'. This pattern is common for coordinates (latitude/longitude pairs), making it relevant for the Mapbox MCP server this PR targets.

Fix in Cursor Fix in Web

return created_models[name]

if name not in forward_refs:
forward_refs[name] = ForwardRef(name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused forward_refs dictionary and ForwardRef import

Low Severity

The forward_refs dictionary is created and populated but its values are never read or used. ForwardRef objects are stored at line 104 but the stored values serve no purpose in the code. The only read operation is the membership check name not in forward_refs at line 103, but the actual ForwardRef object is never retrieved or used. This is dead code, and the ForwardRef import at line 23 is also unnecessary as a result.

Additional Locations (1)

Fix in Cursor Fix in Web

ref_schema = schema
for part in ref_parts:
ref_schema = ref_schema.get(part, {})
process_schema(ref_name, ref_schema)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicated $ref resolution logic within same function

Low Severity

The $ref resolution logic is duplicated within get_field_type. Lines 133-140 handle $ref at the top level of a field schema, and lines 161-168 handle $ref inside anyOf options. Both blocks contain nearly identical code: parsing the ref path, extracting the ref name, checking if the model exists, walking the schema to find the referenced definition, and calling process_schema. This duplication increases maintenance burden and risks inconsistent bug fixes.

Additional Locations (1)

Fix in Cursor Fix in Web

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] An error occurred while running the crew: Failed to initialize MCP Adapter: '#/properties/geometry/anyOf/0/items'

1 participant