Repository navigation
Add Skills extension support to mcpcompat - #440
Conversation
vMCP cannot serve the Skills extension (SEP-2640) on its legacy path: mcpcompat had no way to declare an extension in the initialize result, serve a non-standard method such as skills/list, or send one to a backend. go-sdk v1.8.0 supports all three, so expose them in the shim. The server gains WithExtension and WithCustomMethod, also wired into the stdio bridge. The client gains CallCustomMethod. Params and results pass through as raw JSON objects so a proxy can forward an extension's methods without the shim modeling their shapes. Sessions rehydrated on another replica answer custom methods with 400, because go-sdk only checks custom methods on sessions its own handler connected. This is documented and pinned by a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the generic custom-method pass-through with typed support for the Skills extension (SEP-2640), the one extension vMCP needs. The skills messages are modeled in the mcp package beside the other MCP types, so the shim needs no raw-JSON package or _meta merge logic. The server gains WithSkillsHandlers, which declares the extension and serves skills/list and skills/get, mirroring WithCompletionHandler. The client gains ListSkills and GetSkill, mirroring Complete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the extension's optional resources/directory/read method, so the shim covers all of SEP-2640. WithDirectoryReadHandler serves it and declares directoryRead: true; it requires WithSkillsHandlers, since the setting lives in that extension's declaration. The client gains ReadDirectory. With three methods, the per-method adapters in server and client are folded into one generic adapter each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jhrozek
left a comment
There was a problem hiding this comment.
Review focused on spec cleanliness and regression / behaviour-change potential. Existing users should see no change: everything server-side is gated on skillsDeclared, and the only unconditional change is registerSkillsMethods on client Initialize. Two spec-conformance issues (capability advertisement on the HandleMessage path, and null for empty lists) should be fixed before merge; the rest are low-severity consistency points. go test -race ./mcpcompat/... passes locally.
Also, a minor note: Skill.Frontmatter is documented as verbatim, but it goes through jsonConvert as map[string]any, so integers above 2^53 lose precision and key order is not preserved. Consider softening the comment, or using json.RawMessage if proxies need exact pass-through.
Encode empty skills and directory listings as [] rather than null, as the extension defines both as arrays. Advertise Resources on the stdio bridge when declared, matching the go-sdk path, and do not advertise a misconfigured extension there. Treat a nil handler result as an error and recover handler panics on both serving paths alike. Decode frontmatter numbers as json.Number so none loses precision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review @jhrozek. All points are addressed in b22605c; replies are on each thread. On the frontmatter note from the review body: |
jhrozek
left a comment
There was a problem hiding this comment.
Re-checked against b22605c: all findings from the earlier review are addressed (bridge capability advertisement and validateSkills gating, null vs [] for empty lists, nil-result/panic parity between paths, Frontmatter precision/comment). go test -race ./mcpcompat/... passes. LGTM.
Summary
mcpcompat, which could neither declare the extension, serve its methods, nor call them on a backend. go-sdk v1.8.0 supports extensions and custom methods; the shim did not expose them.mcp/skills.gomodels the extension's messages from the SEP:ListSkillsRequest/Result,GetSkillRequest/Params/Result,ReadDirectoryRequest/Params/Result,Skill,SkillResource, andSkillResources(the spec's "file list or\"dynamic\"" union), plus constants for the extension name, the three methods, thedirectoryReadsetting and theinode/directoryMIME type.server.WithSkillsHandlers(list, get)declaresio.modelcontextprotocol/skillsand serves the two required methods,skills/listandskills/get, mirroringWithCompletionHandler. Both handlers are required; a missing one fails the server build.server.WithDirectoryReadHandler(h)serves the optionalresources/directory/readand declaresdirectoryRead: true. It requiresWithSkillsHandlers, since the setting lives in that extension's declaration; otherwise the build fails. Without it the extension is declared as{}(no optional features).HandleMessagebridge serves all three.client.ListSkills,client.GetSkillandclient.ReadDirectory, mirroringComplete, including the resume path.Type of change
Test plan
task(lint + full test suite with-race) passestask license-checkpassesdirectoryReaddeclared, all three methods round-trip, the handler sees the request context and cursor, unknown skills and non-directories reach the client as-32602, misconfigurations fail the build), the stdio bridge, the client resume path, and JSON tests forSkillResourcesand the SEP'sskills/getandresources/directory/readexamplesSpecial notes for reviewers
WithExtension/WithCustomMethod/CallCustomMethodpassing raw JSON through. It needed a raw-JSON package and_metamerge logic, so it was replaced by modeling the one extension needed.ParamsBase/ResultBase, so the server and client packages each declare small unexported wire structs and convert by JSON round-trip, as for every other method; one generic adapter per package does the conversion. Sharing the structs would need a new package or a go-sdk import inmcp. Once a go-sdk release includes its ownskillspackage (go-sdk#1238, open), these can be swapped for its types without changing the shim's public API.WithSkillsHandlers:connectStreamablesets the unexportedconnection.server); the shim's rehydration uses the publicServer.Connect. This cannot be worked around in the shim;TestSkills_ResumedSessionpins it so it fails once go-sdk is fixed. go-sdk#1238 does not change this.resultTypenorttlMs/cacheScope, which go-sdk stamps only on its own result types.-32601, so client callers should check the declared extension (anddirectoryRead) first. This is in the methods' doc comments.🤖 Generated with Claude Code