mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
Answer initialize in the revision the client asked for
initialize always answered 2026-07-28. The official TypeScript client (SDK 1.30.0) and Python client (SDK 2.2.0) ask for 2025-11-25 and refuse an answer in a revision they did not request and do not know, so neither could connect to /mcp. The MCP lifecycle's version negotiation answers in the requested revision when the server supports it. This door serves 2025-06-18 and 2025-11-25 as written (the same Streamable HTTP transport and tool methods, and no batches), so those are echoed. Anything else is answered in 2026-07-28, as before. With the change both clients connect, list the tools and call one, the Python client in its default auto mode as well. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -145,6 +145,25 @@ const mimeJSON = "application/json"
|
||||
// the HTTP+SSE transport are absent here, so nothing needs a migration window.
|
||||
const mcpProtocolVersion = "2026-07-28"
|
||||
|
||||
// mcpRevisions are the revisions this door answers in. The two before
|
||||
// mcpProtocolVersion define the same Streamable HTTP transport and the same tool
|
||||
// methods, and neither has JSON-RPC batches, so a client that asks for one is
|
||||
// served it as written.
|
||||
var mcpRevisions = map[string]bool{"2025-06-18": true, "2025-11-25": true, mcpProtocolVersion: true}
|
||||
|
||||
// negotiate is the revision an initialize is answered in, by the MCP lifecycle's
|
||||
// version negotiation: the one the client asked for when this door speaks it,
|
||||
// else the newest.
|
||||
func negotiate(params []byte) string {
|
||||
var p struct {
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
}
|
||||
if json.Unmarshal(params, &p) == nil && mcpRevisions[p.ProtocolVersion] {
|
||||
return p.ProtocolVersion
|
||||
}
|
||||
return mcpProtocolVersion
|
||||
}
|
||||
|
||||
// defaultMCPPath is where an app serves its own MCP door unless MCPConfig moves
|
||||
// it, and therefore where a host forwards a composed tools/call.
|
||||
const defaultMCPPath = "/mcp"
|
||||
@@ -409,7 +428,7 @@ func (a *App) MCP(ctx context.Context, f *zapmcp.Frame) *zapmcp.Frame {
|
||||
// own migration path assumes.
|
||||
case "initialize", "server/discover":
|
||||
return f.Answer(mcpJSON(map[string]any{
|
||||
"protocolVersion": mcpProtocolVersion,
|
||||
"protocolVersion": negotiate(f.Params),
|
||||
"capabilities": map[string]any{"tools": map[string]any{"listChanged": false}},
|
||||
"serverInfo": map[string]any{"name": a.mcpName(), "version": a.cfg.OpenAPI.Version},
|
||||
}))
|
||||
|
||||
@@ -42,3 +42,33 @@ func TestDiscoverAnswersLikeInitialize(t *testing.T) {
|
||||
t.Fatalf("server/discover %v != initialize %v", disc["protocolVersion"], in["protocolVersion"])
|
||||
}
|
||||
}
|
||||
|
||||
// A client that asks for a revision this door speaks is answered in it; one that
|
||||
// asks for anything else is answered in the newest. The official TypeScript and
|
||||
// Python clients both refuse an initialize answered in a revision they did not
|
||||
// ask for and do not know.
|
||||
func TestInitializeAnswersTheRevisionTheClientAsked(t *testing.T) {
|
||||
a := New(Config{})
|
||||
for asked, want := range map[string]string{
|
||||
"2025-11-25": "2025-11-25",
|
||||
"2025-06-18": "2025-06-18",
|
||||
"2026-07-28": "2026-07-28",
|
||||
"2024-11-05": mcpProtocolVersion,
|
||||
"": mcpProtocolVersion,
|
||||
} {
|
||||
params, err := json.Marshal(map[string]any{"protocolVersion": asked})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := a.MCP(context.Background(), &zapmcp.Frame{Method: "initialize", Kind: zapmcp.Request, ID: "1", Params: params})
|
||||
var got struct {
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
}
|
||||
if err := json.Unmarshal(f.Result, &got); err != nil {
|
||||
t.Fatalf("asked %q: %v", asked, err)
|
||||
}
|
||||
if got.ProtocolVersion != want {
|
||||
t.Errorf("asked %q: answered %q, want %q", asked, got.ProtocolVersion, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user