Repository navigation
feat!: generate MCP servers on the official Go MCP SDK (#89) - #99
Conversation
Migrate generated code, cmd/mcp-vibe, and the e2e test from github.com/mark3labs/mcp-go to github.com/modelcontextprotocol/go-sdk v1.8.0 (requires Go 1.25): - go.mod: go 1.25, require github.com/modelcontextprotocol/go-sdk v1.8.0, mark3labs/mcp-go removed. - Generator: each RPC's tool now carries the request message's JSON Schema (from #88) embedded as a json.RawMessage, and is registered with mcp.AddTool[json.RawMessage, any] so the SDK validates arguments against that schema before the handler runs. The handler decodes arguments with protojson.Unmarshal, calls the gRPC client, and returns the response as protojson text; gRPC errors are returned as Go errors, which the SDK turns into isError results (and already include the gRPC status code via status.Error's Error() string). - Examples regenerated (make generate); golden file updated to match. - cmd/mcp-vibe: mcp.NewServer + mcpServer.Run(ctx, &mcp.StdioTransport{}). - examples/gen/example/v1/e2e_test.go: swapped the MCP-client helper to mcp.NewInMemoryTransports() + mcp.NewClient().Connect() + ClientSession.CallTool/.ListTools; resolved the KNOWN BUG markers now that the schema matches the handler and nested/repeated fields decode correctly via protojson; added invalid-argument and unknown-field cases asserting isError: true without reaching the backend. BREAKING CHANGE: generated MCP servers now depend on github.com/modelcontextprotocol/go-sdk/mcp instead of github.com/mark3labs/mcp-go. NewXxxMCPServer takes a *mcp.Server (not *server.MCPServer). RegisterTool's handler signature changed from mcp.CallToolRequest) (*mcp.CallToolResult, error) to mcp.ToolHandlerFor[json.RawMessage, any] (ctx, *mcp.CallToolRequest, json.RawMessage) (*mcp.CallToolResult, any, error). XxxTool() now returns *mcp.Tool instead of mcp.Tool. Tool input schemas are now the top-level request-message JSON Schema (previously nested under a "<RequestMessage>" property and not actually validated). Go 1.25 is now required.
|
🔍 Review (Claude, on behalf of @erikist): small fixes, then merge. The migration itself is correct. In the factory image:
Please fix:
Separately, I filed #100 for two comment-escaping bugs this PR exposes: a backslash in a comment breaks generation (already true on |
Catches a schema swap between tools (e.g. SetVibe <-> SetVibeDetails) in the e2e test itself, instead of relying solely on the golden test. Per review feedback on #99.
|
🤖 Factory (dispatched 2026-10-08T18:49:08Z): Addressed the review feedback from the PR comment at 2026-10-08T18:48:12Z ("🔍 Review (Claude, on behalf of @erikist): small fixes, then merge."):
Also noted the reviewer's informational note that #100 was filed (non-blocking, out of scope for this PR) for the two comment-escaping bugs — added a line in the PR body pointing to it so it's not lost. Commits pushed: Checks run after the change: Self-review: ran the No open questions — all three points were concrete and actionable. |
|
🔍 Re-review (Claude, on behalf of @erikist): all three points are fixed.
CI is green. Squash-merging with that |
Address PR review feedback from @erikist (via Claude): - Say the README's wiring snippets are checked by `go vet`/`go test` (not `go build`, which skips `_test.go` files) in both the README and example_test.go's comments. - Keep the README snippets word-for-word in sync with example_test.go: use `stdioServer`/`httpServer` and `panic` in both, and drop the file comment's inaccurate claim that it also backs the "How tool arguments map to the request message" section. - Argument-mapping section: document that float/double fields also accept the strings "NaN", "Infinity" and "-Infinity", and that a gRPC error becomes a tool error whose text includes the gRPC status code. - Upgrade notes: add that protojson omits zero-valued fields from results, per #99's breaking-change list.
Closes #89
Summary
Migrates generated MCP servers from
github.com/mark3labs/mcp-goto theofficial
github.com/modelcontextprotocol/go-sdk(v1.8.0, requires Go1.25), per the project's v0.3.0 milestone (#44).
go.mod:go 1.25.0(whatgo mod tidysettles on given go-sdk'sown
go 1.25.0floor), requiresgithub.com/modelcontextprotocol/go-sdk v1.8.0.github.com/mark3labs/mcp-goremoved fromgo.mod/go.sum.cmd/protoc-gen-go-mcp/mcp.go): for each non-streamingRPC:
XxxTool()now returns&mcp.Tool{Name, Description, InputSchema},with
InputSchemathe request message's JSON Schema (built by theschema builder from feat: generate JSON Schema for tool inputs from proto descriptors (protojson-compatible) #88) embedded as a
json.RawMessageliteral.RegisterToolnow registers tools withmcp.AddTool[json.RawMessage, any](server, tool, handler), so the SDKvalidates arguments against the schema before the handler runs
(confirmed: wrong-type and unknown-field arguments both produce
isError: truewithout reaching the handler/backend — see testevidence below).
XxxHandlernow decodesargs json.RawMessagewithprotojson.Unmarshal, calls the gRPC client, and returns the responseas
protojson.Marshal-ed text. A gRPC error is returned as a plain Goerror; the SDK turns it into anisErrorresult. The error messagealready includes the gRPC status code, because
status.Error(...)'sown
Error()string is"rpc error: code = <Code> desc = <msg>"(verified in
TestCallSetVibe_GRPCError), so no extra formatting wasneeded in the generated handler.
(
generateFieldAssignment,kindToCastType,specialCastTypes,etc.) that the old generator used to hand-build request messages from
map[string]any—protojson.Unmarshalreplaces all of it, whichalso fixes the int32/repeated-scalar/repeated-message decoding bugs
the pre-migration e2e test tracked as
KNOWN BUGs.make generate; the golden file(
examples/gen/example/v1/example_mcp.pb.go) andcmd/protoc-gen-go-mcp/golden_test.goare in sync (byte-for-byte).cmd/mcp-vibe: now builds anmcp.NewServer(&mcp.Implementation{...}, nil)and serves it withmcpServer.Run(ctx, &mcp.StdioTransport{}).(
examples/gen/example/v1/e2e_test.go): the MCP-client harness nowuses
mcp.NewInMemoryTransports()+mcp.NewClient(...).Connect(...)+ClientSession.CallTool/ListTools. AllKNOWN BUGmarkers fromtest: end-to-end test of a generated MCP server (gRPC backend + MCP client) #87/feat: generate JSON Schema for tool inputs from proto descriptors (protojson-compatible) #88/feat!: generate MCP servers on the official Go MCP SDK #89 are resolved and replaced with direct assertions (the
schema now matches the handler, and int32 / repeated-scalar /
repeated-message fields all decode correctly via
protojson). AddedTestCallSetVibe_InvalidArgumentTypeandTestCallSetVibe_UnknownField,asserting
isError: trueand zero backend calls.README.md: updated the Go prerequisite to 1.25 and the codesnippets that showed the old
mcp.Tool/mcp.CallToolRequestshapes.Decisions made
allow; every remaining difference is called out in the
BREAKING CHANGE:note below, per the issue's "list any change in the PR body"instruction.
go.mod'sgodirective ended up as1.25.0rather than plain1.25,because
github.com/modelcontextprotocol/go-sdk's owngo.modpinsgo 1.25.0andgo mod tidypropagates that; this satisfies the issue's"go 1.25" requirement and is what a clean
go mod tidyproduces.github.com/modelcontextprotocol/go-sdk's available versionsvia the module proxy (
proxy.golang.org/.../@v/list) before pickingv1.8.0: it is the latest non-prerelease tag (v1.8.0-pre.*tags alsoexist but are pre-releases), matching what the issue specifies.
Test evidence
Smoke test (
mcp-vibebuilt and run over stdio, driven with rawJSON-RPC lines):
initialize,tools/list, and atools/callforSetVibeall succeed —Also ran
go mod verify(all modules verified) and confirmedgrep -rn mark3labs .(excludingCHANGELOG.md) only matchesdocs/GOAL.md'shistorical description of the migration, not any code.
Self-review
Ran the
reviewersubagent against this branch. It flagged onesuspected blocker (a seemingly-corrupted
go.sumline forgolang.org/x/oauth2) which turned out to be a false positive from itsown tool output rendering —
go mod verify,go build,go vet, andgo testall pass cleanly against the committedgo.sum, and re-runninggo mod tidyreproduces it byte-for-byte. No other blocking or in-scopeissues were found; its only other notes were non-blocking
(
go.mod's1.25.0vs1.25— addressed above) and an out-of-scopeobservation about
.github/workflows/build.ymlGo versions, which thisissue does not ask to change.
Notes for reviewers
BREAKING CHANGE: generated MCP servers now import
github.com/modelcontextprotocol/go-sdk/mcpinstead ofgithub.com/mark3labs/mcp-go. Users of generated code must update to theofficial SDK and adjust to the following shape changes:
New<Service>MCPServernow takes a*mcp.Server(not*server.MCPServer), and the generated struct's exportedMCPServerfield is now typed
*mcp.Server(was*server.MCPServer).RegisterTool's handler parameter type changed fromfunc(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error)tomcp.ToolHandlerFor[json.RawMessage, any](i.e.func(ctx, *mcp.CallToolRequest, json.RawMessage) (*mcp.CallToolResult, any, error)), andRegisterTool's tool parameter is now*mcp.Tool(wasmcp.Tool).XxxTool()now returns*mcp.Toolinstead ofmcp.Tool.Each tool's
InputSchemais now the top-level request-messageJSON Schema (previously nested under a property named after the
request message, and not actually enforced since mark3labs didn't
validate against it).
Tool call responses are now protojson, so they use lowerCamel keys
(e.g.
previous_vibe→previousVibe) and omit zero-valued fields,instead of the old reflection-based encoding.
Tool call arguments must now use lowerCamel field names: the schema
sets
additionalProperties: false, so a snake_case argument name isrejected as an unknown field (
isError: true) rather than silentlyaccepted or coerced.
Go 1.25 is now required to build this module.
Out of scope, left untouched per
docs/GOAL.md: streaming RPCs, arraysof messages as tool inputs (Support message type arrays #39 — now actually works via
protojson,but no explicit support/tests were added beyond what the e2e test
already exercises), release binaries, vendoring, MCP resources/prompts.
Did not touch
.github/workflows/**,CHANGELOG.md,release-please-config.json, or.release-please-manifest.json.Filed by the reviewer, not a blocker for this PR: fix: escape proto comments correctly in generated descriptions and schemas #100, tracking
two comment-escaping bugs this PR exposes (a backslash in a comment
breaks generation — already true on
main— and quotes in schemadescriptions are double-escaped).
Factory-authored