Skip to content

Add structured output to the TypeScript weather server and client and update to v2#165

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

Add structured output to the TypeScript weather server and client and update to v2#165
olaservo wants to merge 1 commit into
modelcontextprotocol:mainfrom
olaservo:structured-output-2026-07-28/typescript

Conversation

@olaservo

@olaservo olaservo commented Jul 26, 2026

Copy link
Copy Markdown
Member

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

Brings the TypeScript examples onto MCP SDK 2.0 and protocol revision 2026-07-28, and gives both tools a declared outputSchema with matching structuredContent.

get-alerts answers with a top-level JSON array:

[
  { "event": "Flood Warning", "area": "La Salle County", ... },
  { "event": "Heat Advisory",  "area": "Wheeler County",  ... }
]

Through 2025-11-25 an outputSchema had to be object-rooted, so a tool returning a list had to invent a key to hang it off. get-forecast returns an object, for contrast.

This costs older clients nothing

The server declares the array schema once and never branches on protocol version. serveStdio serves both eras from one factory, and the SDK projects the schema for whichever era connects.

A 2026-07-28 client sees the schema as written. A 2025-11-25 client sees {"type":"object","properties":{"result":{...}}} and gets structuredContent as {"result":[...]}. Verified against both.

Other changes

Error paths throw. A tool declaring an outputSchema MUST return conforming structured content, so a path with no data has to fail rather than answer with a bare text result.

The client negotiates. versionNegotiation: { mode: 'auto' } probes server/discover and falls back to the 2025-11-25 handshake; the SDK's default is 'legacy'.

Each channel goes to its stated reader. content is forwarded to the model; structuredContent is used as data, reporting how many items came back. The SDK validates it against the declared schema, so the client-side SHOULD needs no code.

Package split. @modelcontextprotocol/sdk becomes @modelcontextprotocol/server and @modelcontextprotocol/client, which is where the 2026-07-28 support lives. Model identifier moves to claude-sonnet-5.

Verification

Captured off the raw wire — a real get-alerts("TX") call against live NWS data, no SDK on the client side:

server/discover -> OK, versions=["2026-07-28"]
tools/list      -> get-alerts outputSchema.type = "array"
  structuredContent: ARRAY
  content: 1 block(s)

Related

One of a set bringing the examples onto 2026-07-28, one PR per language so the four can be compared: #164 (Python), #165 (TypeScript), #166 (Go), #167 (Rust). #163 is the shared prerequisite. CI here is green, which is itself worth noting: main's smoke test uses an MCP SDK v1 helper that negotiates 2025-11-25, and it accepts this server because the SDK projects the array-rooted schema down to the object form that revision expects. The behaviour described above, confirmed by a real v1 client. Extending that test to assert on structured output comes with #163.

The Ruby examples are deliberately not in the set: the mcp gem caps at protocol 2025-11-25.


🤖 Generated with Claude Code

Both tools declare an outputSchema and return structuredContent alongside the
text.

get-alerts declares z.array(...), so it answers with a top-level JSON array
rather than an array nested in an object, which protocol revision 2026-07-28
is the first to allow. "No alerts" is simply []. get-forecast returns an
object, for contrast.

This costs older clients nothing. The server declares the array schema once
and never branches on protocol version: serveStdio serves both eras from one
factory, and the SDK projects the schema down to
{"type":"object","properties":{"result":...}} for a 2025-11-25 client, wrapping
the structured content to match. Verified against both eras.

Error paths throw: a tool declaring an outputSchema MUST return conforming
structured content, so a path with no data has to fail.

The client passes versionNegotiation {mode:"auto"}; the SDK default is
"legacy". The SDK validates every result against the declared schema, so the
client-side SHOULD needs no code. Each channel goes to its stated reader:
content is forwarded to the model, structuredContent is used as data,
reporting how many items came back.

Moves to the 2.0 beta packages, where the 2026-07-28 support lives:
@modelcontextprotocol/sdk is replaced by @modelcontextprotocol/server and
@modelcontextprotocol/client. Model identifier moves to claude-sonnet-5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the TypeScript weather server/client examples to MCP SDK v2 (split client/server packages) and adds declared tool outputSchema with matching structuredContent, including an array-rooted schema for get-alerts to align with protocol revision 2026-07-28.

Changes:

  • Migrate server to @modelcontextprotocol/server and use serveStdio(buildServer) with per-tool outputSchema + structuredContent for get-alerts (array) and get-forecast (object).
  • Migrate client to @modelcontextprotocol/client, enable automatic protocol version negotiation, and surface basic structured-output handling.
  • Update READMEs and dependency manifests/lockfiles for the new package split and Zod v4 usage.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
weather-server-typescript/src/index.ts Switches to v2 server APIs, adds output schemas + structured results, and refactors server startup via serveStdio.
weather-server-typescript/README.md Documents structured output behavior and backward-compat projection.
weather-server-typescript/package.json Replaces @modelcontextprotocol/sdk with @modelcontextprotocol/server and adds zod.
weather-server-typescript/package-lock.json Locks updated dependency graph for server v2 + Zod v4.
mcp-client-typescript/index.ts Switches to v2 client APIs, enables versionNegotiation: auto, and distinguishes content vs structuredContent.
mcp-client-typescript/README.md Documents structured output handling and version negotiation.
mcp-client-typescript/package.json Replaces @modelcontextprotocol/sdk with @modelcontextprotocol/client.
mcp-client-typescript/package-lock.json Locks updated dependency graph for client v2.
Files not reviewed (2)
  • mcp-client-typescript/package-lock.json: Generated file
  • weather-server-typescript/package-lock.json: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +3
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

The SDK validates every result against the tool's declared `outputSchema`, so the spec's client-side SHOULD needs no code here.

The two channels go to different readers: `content` is forwarded to the model, while `structuredContent` is used as data — the client counts the items it returns. See [Structured Content](https://modelcontextprotocol.io/specification/draft/server/tools#structured-content).
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.

2 participants