fix: handle complex JSON schemas in MCP adapter to prevent KeyError#4313
fix: handle complex JSON schemas in MCP adapter to prevent KeyError#4313devin-ai-integration[bot] wants to merge 1 commit intomainfrom
Conversation
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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| 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()) |
There was a problem hiding this comment.
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.
|
|
||
| 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] |
There was a problem hiding this comment.
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.
| return created_models[name] | ||
|
|
||
| if name not in forward_refs: | ||
| forward_refs[name] = ForwardRef(name) |
There was a problem hiding this comment.
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)
| ref_schema = schema | ||
| for part in ref_parts: | ||
| ref_schema = ref_schema.get(part, {}) | ||
| process_schema(ref_name, ref_schema) |
There was a problem hiding this comment.
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.


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
$refreferences (e.g., Mapbox MCP server). The error wasKeyError: '#/properties/geometry/anyOf/0/items'.The root cause was that the external
mcpadaptlibrary'sCrewAIAdapterdoesn't fully resolve nested$refreferences before creating Pydantic models. When Pydantic generates JSON schema from the dynamically created model, it encounters unresolved references.This fix creates a custom
CrewAIAdapterWithSchemaFixclass that:jsonrefto fully resolve all$refreferences before creating Pydantic models_create_model_from_schema()to handle complex schema patterns (anyOf, arrays, nested refs)CrewAIAdapterinMCPServerAdapterReview & Testing Checklist for Human
https://mcp.mapbox.com/mcpis recommended to confirm the fix works in practicejsonrefdependency - The code importsjsonref; confirm it's already available in the project dependencies or add it if needed_create_model_from_schemaedge cases - This function (~140 lines) handles many JSON schema patterns; verify it covers schemas from other MCP servers you use_runmethod filters kwargs based on schema nullability; verify this doesn't break existing MCP tool callsRecommended Test Plan
uv run pytest lib/crewai-tools/tests/adapters/mcp_adapter_test.py -k "TestResolveAllRefs or TestCreateModelFromSchema or TestCrewAIAdapterWithSchemaFix" -vNotes
# noqa: UP007for dynamicUnion[tuple(types)]construction which is necessary at runtimeLink 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 introducingCrewAIAdapterWithSchemaFix, which resolves all schema references viajsonrefand builds Pydanticargs_schemamodels with custom handling foranyOf, arrays, nullability, and$defs.MCPServerAdapternow uses this custom adapter instead of the upstreamCrewAIAdapter, and tests were added to cover$refresolution, model generation (includingmodel_json_schema()not raising), and adapter behavior.Written by Cursor Bugbot for commit 5b296fa. This will update automatically on new commits. Configure here.