Skip to content

Assert on structured output in the smoke tests#163

Draft
olaservo wants to merge 1 commit into
modelcontextprotocol:mainfrom
olaservo:structured-output-2026-07-28/tests
Draft

Assert on structured output in the smoke tests#163
olaservo wants to merge 1 commit into
modelcontextprotocol:mainfrom
olaservo:structured-output-2026-07-28/tests

Conversation

@olaservo

@olaservo olaservo commented Jul 26, 2026

Copy link
Copy Markdown
Member

Draft until the 2026-07-28 revision and SDK is released

What the test client now does

After listing tools it calls every tool that declares an outputSchema and checks the answer against it:

  • the result must carry structuredContent, and
  • a tool with an array-rooted schema must return a top-level JSON array, while an object-rooted one must return an object.

Protocol revision 2026-07-28 is what makes the array case possible: through 2025-11-25 an outputSchema had to be object-rooted. The client negotiates with mode: "auto" — one server/discover probe, falling back to the initialize handshake — so it keeps working against every example as it stands today while also being able to test a server that speaks the new revision.

Tool calls reach the live NWS API. An unreachable upstream comes back as an error result, which the test reports as a skip rather than a failure; someone else's outage should not fail the build.

Mock server

mock-mcp-server moves to the low-level Server and advertises two tools, one array-rooted and one object-rooted, with the schemas written out literally — what goes on the wire is exactly what is in the file.

Build output is no longer swallowed

ensure_built ran every build with >/dev/null 2>&1 unconditionally. A compile failure therefore left no binary behind and the test reported a downstream spawn ... ENOENT instead of the actual error, which is what makes a broken build hard to diagnose from CI logs alone — #143 is the current example. run_build now prints the compiler output when a build fails.

Two smaller fixes alongside it:

  • check_dependency returns instead of calling exit 1, so a missing toolchain fails its own test and the suite still finishes and prints its summary.
  • Binaries are resolved through a helper that accounts for the .exe suffix, so the suite is runnable on Windows as well as in CI.

CI

The test helpers move to the 2.0 MCP SDK packages, which is where the 2026-07-28 support lives. The examples themselves are untouched.

Those packages require Node 20 or newer, so CI moves off Node 18. It goes to Node 24, the Active LTS, rather than to the floor — Node 20 reached end of life in April 2026.

Still uncovered

The Go examples and the Rust client remain untested here. Each needs a change inside its own directory first — both clients abort when no .env file is present, so neither can be driven without credentials — so that coverage belongs with those changes rather than in this PR.

Verification

CI on this PR is green. Locally, run against main with these changes applied, all five covered examples pass:

✓ weather-server-python     ✓ get_alerts: structuredContent is an object, matching its schema
                            ✓ get_forecast: structuredContent is an object, matching its schema
✓ weather-server-typescript ✓ Verified structured output for 0 tools
✓ weather-server-rust       ✓ Verified structured output for 0 tools
✓ mcp-client-python
✓ mcp-client-typescript

The Python server already exercises the new assertion: its tools return -> str, from which the SDK derives an object-rooted {"result": ...} schema, and the test confirms the structured content matches it. The TypeScript and Rust servers declare no output schemas today, so the assertion correctly no-ops rather than failing them.

Related

First of a set bringing the examples onto 2026-07-28. This one is the prerequisite: it is what lets the smoke tests exercise a server that declares a non-object output schema. The rest of the set is #164 (Python), #165 (TypeScript), #166 (Go), #167 (Rust), with #163 as the shared prerequisite. The Ruby examples are deliberately not in the set: the mcp gem caps at protocol 2025-11-25.


🤖 Generated with Claude Code

@olaservo
olaservo marked this pull request as draft July 26, 2026 02:14
@olaservo
olaservo force-pushed the structured-output-2026-07-28/tests branch 2 times, most recently from 00cf861 to 10903f6 Compare July 26, 2026 04:18
The smoke tests connected to each server, listed its tools and stopped there,
so they could not catch a broken structured result.

mcp-test-client now calls every tool that declares an outputSchema and checks
the answer against it: the result must carry structuredContent, and a tool
with an array-rooted schema must return a top-level JSON array rather than an
object wrapping one. Tools with no output schema are left alone, so this is
not a new requirement on any example. The client negotiates with mode "auto",
so it exercises servers of either protocol era. Tool calls reach the live NWS
API; an error result is reported as a skip rather than a failure.

mock-mcp-server moves to the low-level Server and advertises one array-rooted
and one object-rooted tool, with the schemas written out literally. A client
that compiles every declared outputSchema at connect time fails against the
array-rooted one if it assumes output schemas are always objects.

utils.sh no longer swallows build output. A failed build left no binary behind
and the test reported a downstream spawn ENOENT instead of the compiler error,
which is what makes a broken build hard to diagnose from CI logs; modelcontextprotocol#143 is the
current example. check_dependency returns instead of exiting, so a missing
toolchain fails its own test and the suite still prints its summary. Binaries
resolve through a helper that accounts for the .exe suffix.

The test helpers move to the 2.0 MCP SDK packages, where the 2026-07-28
support lives; the examples themselves are untouched. Those require Node 20 or
newer, so CI moves off Node 18 to Node 24, the Active LTS - Node 20 reached
end of life in April 2026.

The Go examples and the Rust client stay uncovered here. Both clients abort
when no .env file is present, so that coverage lands with those examples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant