Skip to content

Add structured output to the TypeScript weather server and client#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#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 <[email protected]>
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