Skip to content

Conversation

Copy link

Copilot AI commented Nov 22, 2025

Description

MCP tools fail when remote clients send additional parameters due to Pydantic V2's default extra='forbid' behavior. This breaks compatibility with clients that include metadata or context fields like sessionId, action, chatInput, and toolCallId.

Changes

Implemented a filtering wrapper that intercepts tool execution to remove unknown parameters before validation:

Solution:

  • Created _wrap_tool_to_filter_params() function that wraps each tool's run() method
  • Modified create_mcp_server() to intercept get_tool() and lazily apply filtering
  • Filters parameters based on the tool's function signature using inspect
  • Unknown parameters are logged at debug level and silently ignored

Implementation:

def _wrap_tool_to_filter_params(tool):
    """Wraps a tool's run method to filter out extra parameters before validation."""
    original_run = tool.run
    sig = inspect.signature(tool.fn)
    valid_params = set(sig.parameters.keys())
    
    async def filtered_run(arguments: dict[str, Any]):
        # Filter out any extra parameters
        filtered_args = {k: v for k, v in arguments.items() if k in valid_params}
        if filtered_args != arguments:
            extra_keys = set(arguments.keys()) - valid_params
            logger.debug(f"Filtered extra parameters from tool '{tool.name}': {extra_keys}")
        return await original_run(filtered_args)
    
    object.__setattr__(tool, 'run', filtered_run)
    return tool

This approach:

  • Maintains the flat parameter structure (backward compatible)
  • Works with all existing MCP clients without modification
  • Applies filtering only when tools are first accessed via get_tool()

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Project configuration change

Complexity

  • LOW
  • MEDIUM
  • HIGH

Complexity: Runtime parameter filtering with function signature inspection.

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual tests

Added test_tools_ignore_extra_parameters verifying all three tools (get_neo4j_schema, read_neo4j_cypher, write_neo4j_cypher) handle unexpected fields like sessionId, action, chatInput, and toolCallId correctly. All 9 integration tests pass (8 existing + 1 new).

Checklist

  • Documentation has been updated
  • Unit tests have been updated
  • Integration tests have been updated
  • Server has been tested in an MCP application
  • CHANGELOG.md updated if appropriate
Original prompt

Based on my analysis of the code, I've identified why your tools fail when additional parameters are sent by the MCP remote client.

Root Cause
When you define tools using FastMCP's @mcp.tool() decorator with individual Field parameters, FastMCP internally creates a Pydantic model to validate inputs. By default, Pydantic V2 uses extra='forbid', which means any unexpected parameters cause a ValidationError and the tool call fails.

In your current implementation in server.py, if an MCP client sends extra fields like {"query": "...", "params": {}, "extra_field": "value"}, the tool will reject the request instead of ignoring the extra field.

Solution: Configure Pydantic Models with extra='ignore'
To make your tools ignore extra parameters, wrap your parameters in Pydantic BaseModel classes configured with ConfigDict(extra='ignore'):

Python
from pydantic import BaseModel, ConfigDict, Field
from typing import Any

class GetSchemaParams(BaseModel):
model_config = ConfigDict(extra='ignore')

sample_size: int = Field(
    default=1000,
    description="The sample size used to infer the graph schema."
)

class ReadCypherParams(BaseModel):
model_config = ConfigDict(extra='ignore')

query: str = Field(..., description="The Cypher query to execute.")
params: dict[str, Any] = Field(
    default_factory=dict,
    description="The parameters to pass to the Cypher query."
)

class WriteCypherParams(BaseModel):
model_config = ConfigDict(extra='ignore')

query: str = Field(..., description="The Cypher query to execute.")
params: dict[str, Any] = Field(
    default_factory=dict,
    description="The parameters to pass to the Cypher query."
)

Update tool definitions:

@mcp.tool(
name=namespace_prefix + "read_neo4j_cypher",
annotations=ToolAnnotations(...)
)
async def read_neo4j_cypher(params: ReadCypherParams) -> list[ToolResult]:
"""Execute a read Cypher query on the neo4j database."""
query = params.query
cypher_params = params.params
# ... rest of implementation
Benefits
✅ Tools ignore extra parameters instead of failing
✅ Type safety preserved for expected parameters
✅ Better compatibility with different MCP clients
✅ Follows FastMCP and Pydantic V2 best practices


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: egallis31 <18443524+egallis31@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix validation error for extra parameters in tools Fix MCP tools to ignore extra parameters from remote clients Nov 22, 2025
Copilot AI requested a review from egallis31 November 22, 2025 03:43
@egallis31
Copy link

@copilot [11/22/25 04:17:31] Error validating tool 'read_neo4j_cypher': 2 validation
errors for call[read_neo4j_cypher]
params.query
Field required [type=missing, input_value={},
input_type=dict]
For further information visit
https://errors.pydantic.dev/2.12/v/missing
query
Unexpected keyword argument
[type=unexpected_keyword_argument, input_value='CALL
db.schema.visualization()', input_type=str]
For further information visit
https://errors.pydantic.dev/2.12/v/unexpected_keyword_argume
nt
╭─────────── Traceback (most recent call last) ────────────╮
│ /usr/local/lib/python3.11/site-packages/fastmcp/tools/to │
│ ol_manager.py:160 in call_tool │
│ │
│ /usr/local/lib/python3.11/site-packages/fastmcp/tools/to │
│ ol.py:330 in run │
│ │
│ /usr/local/lib/python3.11/site-packages/pydantic/type_ad │
│ apter.py:441 in validate_python │
╰──────────────────────────────────────────────────────────╯
ValidationError: 2 validation errors for
call[read_neo4j_cypher]
params.query
Field required [type=missing, input_value={},
input_type=dict]
For further information visit
https://errors.pydantic.dev/2.12/v/missing
query
Unexpected keyword argument
[type=unexpected_keyword_argument, input_value='CALL
db.schema.visualization()', input_type=str]
For further information visit
https://errors.pydantic.dev/2.12/v/unexpected_keyword_argume
nt

Copy link
Author

Copilot AI commented Nov 22, 2025

@copilot [11/22/25 04:17:31] Error validating tool 'read_neo4j_cypher': 2 validation
errors for call[read_neo4j_cypher]
params.query...

I've investigated this issue extensively. The errors show that MCP clients are sending parameters in a flat structure (e.g., {"query": "...", "sessionId": "123"}) but also including extra fields like sessionId, action, chatInput, and toolCallId that the tools don't expect.

My initial implementation using Pydantic BaseModel with extra='ignore' successfully ignores extra fields, but it changes the calling convention - clients would need to send {"params": {"query": "..."}} instead of {"query": "..."}. This breaks backwards compatibility.

I've tried multiple approaches to make tools ignore extra parameters while keeping the flat parameter structure:

  1. Using **kwargs - not supported by FastMCP
  2. Monkey-patching FastMCP's TypeAdapter creation - the patches don't take effect properly
  3. Custom validation wrappers - can't intercept FastMCP's validation layer

The root cause: FastMCP creates TypeAdapters from function signatures with Pydantic's default extra='forbid', and there's no clean way to override this without modifying FastMCP itself.

Questions:

  1. Are these extra fields (sessionId, action, chatInput, toolCallId) being sent by a specific MCP client or proxy?
  2. Would it be acceptable to update that client/proxy to not send these extra fields?
  3. Alternatively, should we file an issue with FastMCP to support extra='ignore' configuration?

I can proceed with the BaseModel approach (which requires wrapping parameters), but this will be a breaking change for existing clients. Let me know how you'd like to proceed.

@copilot [11/22/25 04:17:31] Error validating tool 'read_neo4j_cypher': 2 validation
errors for call[read_neo4j_cypher]
params.query...

I've successfully implemented a solution that allows tools to ignore extra parameters while maintaining backward compatibility.

Solution: Wraps each tool's run() method to filter out unknown parameters before FastMCP's validation. This is done lazily when get_tool() is called.

Changes in commit b3e4d81:

  • Tools now accept and ignore extra fields like sessionId, action, chatInput, toolCallId
  • No API changes - existing clients continue to work
  • All 9 integration tests pass (8 existing + 1 new test for extra params)
  • Code review and security checks passed

The solution filters parameters at runtime based on each tool's function signature, so only expected parameters are passed to validation. Extra parameters are logged at debug level and silently ignored.

Copilot AI and others added 3 commits November 22, 2025 04:38
- Implemented filtering wrapper that intercepts tool.run() to remove unknown parameters
- Preserves flat parameter structure (backward compatible)
- All existing tests pass + new test for extra parameter handling
- Solution wraps get_tool() to lazily apply filtering to each tool

Co-authored-by: egallis31 <18443524+egallis31@users.noreply.github.com>
Co-authored-by: egallis31 <18443524+egallis31@users.noreply.github.com>
- Use default_factory for mutable dict defaults
- Add type annotation for wrapped_tools set

Co-authored-by: egallis31 <18443524+egallis31@users.noreply.github.com>
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.

2 participants