From 3148a75684c068b40b38536e4fc116843184af86 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:49:51 +0000 Subject: [PATCH] fix: ignore extra fields (security_context) in MCP tool schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #4796 CrewAI's tool execution framework injects a security_context parameter into tool arguments via _add_fingerprint_metadata(). MCP tools created via create_model_from_schema() defaulted to ConfigDict(extra='forbid'), causing Pydantic validation to fail with 'Extra inputs are not permitted'. This change passes ConfigDict(extra='ignore') when creating Pydantic models for MCP tool schemas, so that extra fields like security_context are silently dropped during validation instead of raising errors. Changes: - lib/crewai-tools: MCPServerAdapter's CrewAIToolAdapter now creates args models with extra='ignore' - lib/crewai: Native MCP tool resolver also uses extra='ignore' - Added regression tests for both the adapter and schema utils Co-Authored-By: João --- .../src/crewai_tools/adapters/mcp_adapter.py | 7 +- .../tests/adapters/mcp_adapter_test.py | 68 +++++++++++++++ lib/crewai/src/crewai/mcp/tool_resolver.py | 3 + .../utilities/test_pydantic_schema_utils.py | 83 +++++++++++++++++++ 4 files changed, 159 insertions(+), 2 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py b/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py index 3bd91f164..2f371d4a0 100644 --- a/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py +++ b/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any from crewai.tools import BaseTool from crewai.utilities.pydantic_schema_utils import create_model_from_schema from crewai.utilities.string_utils import sanitize_tool_name -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from crewai_tools.adapters.tool_collection import ToolCollection @@ -51,7 +51,10 @@ try: """ tool_name = sanitize_tool_name(mcp_tool.name) tool_description = mcp_tool.description or "" - args_model = create_model_from_schema(mcp_tool.inputSchema) + args_model = create_model_from_schema( + mcp_tool.inputSchema, + __config__=ConfigDict(extra="ignore"), + ) class CrewAIMCPTool(BaseTool): name: str = tool_name diff --git a/lib/crewai-tools/tests/adapters/mcp_adapter_test.py b/lib/crewai-tools/tests/adapters/mcp_adapter_test.py index 188f86699..7f40298ae 100644 --- a/lib/crewai-tools/tests/adapters/mcp_adapter_test.py +++ b/lib/crewai-tools/tests/adapters/mcp_adapter_test.py @@ -221,6 +221,74 @@ def test_connect_timeout_with_filtered_tools(echo_server_script): assert tools[0].run(text="timeout test") == "Echo: timeout test" +def test_mcp_tool_ignores_security_context(echo_server_script): + """Test that MCP tools ignore extra fields like security_context. + + This is a regression test for https://github.com/crewAIInc/crewAI/issues/4796. + CrewAI's tool execution framework injects a `security_context` parameter + (containing agent_fingerprint and metadata) into tool calls. MCP tools must + ignore this extra field instead of raising a Pydantic validation error. + """ + serverparams = StdioServerParameters( + command="uv", args=["run", "python", "-c", echo_server_script] + ) + with MCPServerAdapter(serverparams) as tools: + echo_tool = tools[0] + # Simulate what CrewAI's tool_usage._add_fingerprint_metadata does: + # it adds security_context to the tool arguments before invocation. + result = echo_tool.run( + text="hello", + security_context={ + "agent_fingerprint": { + "uuid_str": "test-uuid-12345", + "created_at": "2026-01-01T00:00:00", + }, + "metadata": {}, + }, + ) + assert result == "Echo: hello" + + +def test_mcp_tool_ignores_security_context_calc(echo_server_script): + """Test that security_context is ignored for tools with multiple args.""" + serverparams = StdioServerParameters( + command="uv", args=["run", "python", "-c", echo_server_script] + ) + with MCPServerAdapter(serverparams) as tools: + calc_tool = tools[1] + result = calc_tool.run( + a=10, + b=20, + security_context={ + "agent_fingerprint": { + "uuid_str": "test-uuid-67890", + "created_at": "2026-01-01T00:00:00", + }, + "task_fingerprint": { + "uuid_str": "task-uuid-11111", + "created_at": "2026-01-01T00:00:00", + }, + "metadata": {}, + }, + ) + assert result == "30" + + +def test_mcp_tool_args_schema_allows_extra_fields(echo_server_script): + """Test that the args_schema on MCP tools is configured to ignore extra fields.""" + serverparams = StdioServerParameters( + command="uv", args=["run", "python", "-c", echo_server_script] + ) + with MCPServerAdapter(serverparams) as tools: + echo_tool = tools[0] + # Validate that the schema accepts and ignores extra fields + schema = echo_tool.args_schema + validated = schema.model_validate( + {"text": "test", "security_context": {"agent_fingerprint": "abc"}} + ) + assert validated.model_dump() == {"text": "test"} + + @patch("crewai_tools.adapters.mcp_adapter.MCPAdapt") def test_connect_timeout_passed_to_mcpadapt(mock_mcpadapt): mock_adapter_instance = MagicMock() diff --git a/lib/crewai/src/crewai/mcp/tool_resolver.py b/lib/crewai/src/crewai/mcp/tool_resolver.py index 34af189f2..5d14379cd 100644 --- a/lib/crewai/src/crewai/mcp/tool_resolver.py +++ b/lib/crewai/src/crewai/mcp/tool_resolver.py @@ -582,6 +582,8 @@ class MCPToolResolver: @staticmethod def _json_schema_to_pydantic(tool_name: str, json_schema: dict[str, Any]) -> type: """Convert JSON Schema to a Pydantic model for tool arguments.""" + from pydantic import ConfigDict + from crewai.utilities.pydantic_schema_utils import create_model_from_schema model_name = f"{tool_name.replace('-', '_').replace(' ', '_')}Schema" @@ -589,4 +591,5 @@ class MCPToolResolver: json_schema, model_name=model_name, enrich_descriptions=True, + __config__=ConfigDict(extra="ignore"), ) diff --git a/lib/crewai/tests/utilities/test_pydantic_schema_utils.py b/lib/crewai/tests/utilities/test_pydantic_schema_utils.py index 98a5e6aa5..f155f44bc 100644 --- a/lib/crewai/tests/utilities/test_pydantic_schema_utils.py +++ b/lib/crewai/tests/utilities/test_pydantic_schema_utils.py @@ -882,3 +882,86 @@ class TestEndToEndMCPSchema: ) assert obj.filters.date_from == datetime.date(2025, 1, 1) assert obj.filters.categories == ["news", "tech"] + + +class TestExtraFieldsConfig: + """Tests for extra fields handling with ConfigDict. + + Regression tests for https://github.com/crewAIInc/crewAI/issues/4796. + MCP tools need to ignore extra fields like security_context injected + by CrewAI's tool execution framework. + """ + + SIMPLE_SCHEMA: dict = { + "type": "object", + "properties": { + "query": {"type": "string"}, + "top_k": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "default": None, + }, + }, + "required": ["query"], + } + + def test_default_config_forbids_extra(self) -> None: + """By default, create_model_from_schema forbids extra fields.""" + Model = create_model_from_schema(self.SIMPLE_SCHEMA) + with pytest.raises(Exception): + Model(query="test", security_context={"agent_fingerprint": "abc"}) + + def test_ignore_config_allows_extra(self) -> None: + """With extra='ignore', extra fields are silently dropped.""" + from pydantic import ConfigDict + + Model = create_model_from_schema( + self.SIMPLE_SCHEMA, + __config__=ConfigDict(extra="ignore"), + ) + obj = Model( + query="test", + security_context={ + "agent_fingerprint": { + "uuid_str": "test-uuid", + "created_at": "2026-01-01T00:00:00", + }, + "metadata": {}, + }, + ) + assert obj.query == "test" + # security_context should not appear in the dumped model + assert "security_context" not in obj.model_dump() + + def test_ignore_config_preserves_validation(self) -> None: + """Extra='ignore' still validates declared fields correctly.""" + from pydantic import ConfigDict + + Model = create_model_from_schema( + self.SIMPLE_SCHEMA, + __config__=ConfigDict(extra="ignore"), + ) + # Valid input works + obj = Model(query="test", top_k=5) + assert obj.query == "test" + assert obj.top_k == 5 + + # Missing required field still fails + with pytest.raises(Exception): + Model(top_k=5) + + def test_ignore_config_with_mcp_schema(self) -> None: + """Extra='ignore' works with complex MCP-like schemas.""" + from pydantic import ConfigDict + + Model = create_model_from_schema( + TestEndToEndMCPSchema.MCP_SCHEMA, + __config__=ConfigDict(extra="ignore"), + ) + obj = Model( + query="search term", + format="json", + filters={"date_from": "2025-01-01"}, + security_context={"agent_fingerprint": "test"}, + ) + assert obj.query == "search term" + assert "security_context" not in obj.model_dump()