Skip to content

Add structured content to the Go weather server and client and update to latest SDK and protocol version - #166

Open
olaservo wants to merge 5 commits into
modelcontextprotocol:mainfrom
olaservo:structured-output-2026-07-28/go
Open

Add structured content to the Go weather server and client and update to latest SDK and protocol version#166
olaservo wants to merge 5 commits into
modelcontextprotocol:mainfrom
olaservo:structured-output-2026-07-28/go

Conversation

@olaservo

@olaservo olaservo commented Jul 26, 2026

Copy link
Copy Markdown
Member

Updates the Go examples to go-sdk v1.7.0 and protocol revision 2026-07-28, and gives both tools a declared OutputSchema with matching StructuredContent.

get_alerts returns []Alert, so it answers with a top-level JSON array. Before 2026-07-28 an output schema 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. Non-object output schemas (SEP-2106) landed after v1.6.1; v1.7.0 is the first stable release with them.

Schema inference, and where it's overridden. get_forecast sets no OutputSchema — the SDK infers one from the handler's return type, which is the idiomatic Go spelling and worth showing. get_alerts overrides it, because jsonschema-go infers ["null","array"] for a slice (a nil slice marshals to null) and this handler always builds one. Re-checked at v1.7.0: removing the override still yields the nullable form, so it's still needed.

Three structural fixes

  • 14MB of build artefacts were committed by mistake in this branch's first commit (mcp-client-go.exe, weather-server-go/weather.exe). Removed; the ignore rules landed with Assert on structured content in the smoke tests and update to v2 #163.
  • Forecast periods get their own type. Forecast.Periods reused the struct that decodes the NWS response, leaking its camelCase json tags into the declared schema — temperatureUnit where the other four publish temperature_unit. Splitting out a Period type follows what this file already does with Alert vs AlertProperties: what NWS sends and what the tool publishes are different contracts.
  • Stops advertising the deprecated logging capability. A nil ServerOptions defaults capabilities to {"logging":{}} — "for historical reasons", per the SDK's own doc comment. This server does no logging, and 2026-07-28 deprecates it (SEP-2577). An empty Capabilities suppresses the default; tools is still inferred.

Also

  • Error paths return an error — a tool declaring an OutputSchema MUST return conforming structured content.
  • The client compiles every declared schema at connect and validates results (the spec's client-side SHOULD). jsonschema-go is already an SDK dependency, so this adds none.
  • Content goes to the model, StructuredContent is used as data.
  • A missing .env is no longer fatal, and the key is checked after connecting — matching Python and TypeScript, and what lets the connection be exercised without credentials.
  • Model identifier moves to claude-sonnet-5 (the two calls in the tool loop had drifted apart).

One caveat

The SDK sends an array-rooted schema as written rather than projecting it down the way TypeScript does, so a 2025-11-25 client rejects the tool list:

Invalid result for tools/list: path ["tools", 0, "outputSchema", "type"], expected "object"

Use an object root if you need to serve both eras. The README says so.

Verification

A real get_alerts("TX") call against live NWS, captured off the raw wire with no SDK on the client side: outputSchema.type = "array", resultType: "complete", ttlMs: 0, cacheScope: "public", structuredContent an 8-item array, content two blocks — the SDK appends serialised JSON as a compatibility fallback alongside the tool's prose. CI also covers this now: #163 added the Go server to the smoke test and installed the Go toolchain, so both tools are called and asserted on.

One of a set, one PR per language: #164 (Python, merged), #165 (TypeScript), #166 (Go), #167 (Rust); #163 was the shared test prerequisite and has merged. Ruby is not in the set — the mcp gem never emits the mandatory resultType, so a spec-strict client rejects every response. That needs an upstream fix.


🤖 Generated with Claude Code

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 Go weather server and Go MCP client examples to support MCP protocol revision 2026-07-28, adding structured tool outputs and aligning dependencies with the latest Go SDK prerelease needed for non-object output schemas.

Changes:

  • Weather server tools now return structured outputs (get_forecast as an object, get_alerts as a top-level JSON array) and convert prior “no data” paths into tool errors.
  • Go client compiles/validates each tool’s declared outputSchema at connect time and uses StructuredContent as application data while forwarding Content to the model.
  • Dependency updates to github.com/modelcontextprotocol/go-sdk v1.7.0-pre.3 and github.com/google/jsonschema-go v0.4.3.

Reviewed changes

Copilot reviewed 6 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
weather-server-go/README.md Documents structured output behavior and protocol/client caveat for array-rooted schemas.
weather-server-go/main.go Adds structured output types, error-on-failure behavior, and explicit array-root schema override for get_alerts.
weather-server-go/go.mod Bumps Go SDK and pins jsonschema-go directly.
weather-server-go/go.sum Updates dependency checksums for bumped modules.
mcp-client-go/README.md Documents client-side schema compilation/validation and channel handling.
mcp-client-go/main.go Adds output schema compilation + result validation and separates model-facing Content from app-facing StructuredContent.
mcp-client-go/go.mod Bumps Go SDK and jsonschema-go versions.
mcp-client-go/go.sum Updates dependency checksums for bumped modules.

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

Comment thread weather-server-go/README.md Outdated

## Structured output

Both tools declare an `OutputSchema` and return `StructuredContent`. `get_forecast` returns an object; `get_alerts` returns a top-level JSON array, which protocol revision `2026-07-28` is the first to allow — see [Structured Content](https://modelcontextprotocol.io/specification/draft/server/tools#structured-content) in the spec.
@olaservo
olaservo force-pushed the structured-output-2026-07-28/go branch from b9254b9 to 5d15e97 Compare August 1, 2026 23:04
@olaservo olaservo changed the title Add structured output to the Go weather server and client and update to latest SDK and protocol version Add structured content to the Go weather server and client and update to latest SDK and protocol version Aug 2, 2026
olaservo and others added 5 commits August 9, 2026 15:36
Both tools declare an OutputSchema and return StructuredContent alongside the
text.

get_alerts returns []Alert, 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. The declared schema narrows the inferred ["null","array"] root to
"array": jsonschema-go infers the nullable form because a nil slice is valid
Go, but the handler always builds a slice.

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

The client compiles every declared OutputSchema at connect time and validates
results against it, using jsonschema-go - already an SDK dependency, so no new
one. 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 client no longer treats a missing .env as fatal and checks for
ANTHROPIC_API_KEY after connecting rather than before, matching the Python and
TypeScript clients, so the connection can be exercised without credentials.

Requires go-sdk v1.7.0-pre.3: non-object output schema support (SEP-2106)
landed after v1.6.1 and is in no stable release. Model identifier moves to
claude-sonnet-5; the second call in the tool loop used a hardcoded
ModelClaude3_7SonnetLatest while the first used the package variable, so both
now use the same one.

Note that the SDK sends an array-rooted schema as written on every connection
rather than projecting it down, so an older client rejects the tool list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v1.7.0 shipped on 2026-07-27, so the pin moves off v1.7.0-pre.3.

Removes mcp-client-go.exe and weather-server-go/weather.exe, which were
committed by accident in the previous commit — 14MB of build output that
should never have been in the tree. The tests branch adds the ignore rules
that keep them out.

Also from review: the server README claimed both tools set an OutputSchema.
Only get_alerts does; get_forecast relies on inference from the handler
return type, which is the idiomatic spelling and worth saying so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forecast.Periods reused ForecastPeriod, the struct that decodes the NWS
response, so its camelCase json tags leaked into the declared outputSchema:
temperatureUnit and detailedForecast where the other four quickstarts publish
temperature_unit and detailed_forecast.

Splits out a Period output type, for the same reason Alert is already
separate from AlertProperties: the shape NWS sends and the shape the tool
publishes are different contracts and should not be one struct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
go-sdk defaults ServerCapabilities to {"logging":{}} when ServerOptions is
nil — "for historical reasons", as its own doc comment puts it. This server
does no logging, and logging is deprecated as of 2026-07-28 (SEP-2577), so an
example for that revision should not be advertising it.

Passing an empty Capabilities suppresses the default. The tools capability is
still inferred from the registered tools, so server/discover now reports
{"tools":{"listChanged":true}} rather than logging alongside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec never uses the phrase "structured output". It defines two separate
things under Tool Result: "Structured Content" (the `structuredContent` field)
and "Output Schema" (the `outputSchema` field). Collapsing them into
"structured output" conflates the two.

It is also actively confusing here. In LLM tooling "structured output" means
constrained decoding — making the *model* emit conforming JSON. These clients
call a model API, so a reader could reasonably take the phrase to mean the
tool constrains the model's response, which is the opposite of what is going
on: the tool describes the shape of its own result.

Headings and prose now say "structured content". References to real
identifiers are left alone: the Python SDK's own docs page is called
Structured Output and lives at docs/servers/structured-output.md, and its
decorator parameter is `structured_output`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@olaservo
olaservo force-pushed the structured-output-2026-07-28/go branch from dbae15e to a0ce1f8 Compare August 10, 2026 00:45
@olaservo
olaservo marked this pull request as ready for review August 10, 2026 02:57
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