mcp: custom methods rejected on a StreamableServerTransport connected with Server.Connect

未关闭
#1,506 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
62/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
go

调研方向

Start with the POST pre-check in mcp/streamable.go:1695 and compare how connectStreamable sets c.server and toolLookup with the path used by Server.Connect. The report says custom methods fail on directly connected StreamableServerTransport sessions while standard methods succeed; trace that setup and add a regression test based on the supplied reproduction. Done means the custom method reaches its handler and the request returns HTTP 200.

由索引模型根据 Issue 内容生成。

描述

Checklist
  • I searched the existing issues, and this has not been reported before.
  • This is a real problem that I ran into myself, using the latest release of the SDK.
  • I ran into it in a production system, not in a toy example, a benchmark, or an automated or AI-driven scan of the code.
  • I (a human) examined the relevant SDK code, and wrote or reviewed every line of this report. It is not unreviewed AI output.
  • This report is brief and concise, and sticks to the facts needed to understand the problem.
What happened?

A method registered with AddReceivingCustomMethod fails with HTTP 400 (JSON RPC not handled: "acme/search" unsupported) on a StreamableServerTransport connected with Server.Connect and served through its ServeHTTP. Standard methods on the same session succeed, and the custom method works through StreamableHTTPHandler.

The POST pre-check falls back to the standard-only serverMethodInfos when c.server is nil (mcp/streamable.go:1695). c.server is set only in connectStreamable (streamable.go:603), which Server.Connect does not call. This is the Streamable HTTP counterpart of #1291 (fixed for SSE in #1292).

Expected: the custom method reaches its handler. A possible fix is for Server.Connect to set the connection's server (and toolLookup, which has the same gap) as connectStreamable does.

We hit this serving the Skills extension (SEP-2640): skills/list, skills/get and resources/directory/read are custom methods, so they fail this way on a resumed session.

How to reproduce
package repro

import (
        "context"
        "io"
        "net/http"
        "net/http/httptest"
        "strings"
        "testing"

        "github.com/modelcontextprotocol/go-sdk/mcp"
)

type searchParams struct {
        mcp.ParamsBase
        Query string `json:"query"`
}

type searchResult struct {
        mcp.ResultBase
        Hits []string `json:"hits"`
}

func TestCustomMethodOnDirectlyConnectedSession(t *testing.T) {
        s := mcp.NewServer(&mcp.Implementation{Name: "s", Version: "1"}, nil)
        if err := mcp.AddReceivingCustomMethod(s, "acme/search",
                func(context.Context, *mcp.ServerSession, *searchParams) (*searchResult, error) {
                        return &searchResult{Hits: []string{"hit"}}, nil
                }); err != nil {
                t.Fatal(err)
        }

        // Connect a session directly, e.g. to resume one from a shared store.
        tr := &mcp.StreamableServerTransport{SessionID: "s1"}
        state := &mcp.ServerSessionState{
                InitializeParams:  &mcp.InitializeParams{ProtocolVersion: "2025-11-25"},
                InitializedParams: &mcp.InitializedParams{},
        }
        ss, err := s.Connect(context.Background(), tr, &mcp.ServerSessionOptions{State: state})
        if err != nil {
                t.Fatal(err)
        }
        defer ss.Close()
        ts := httptest.NewServer(tr)
        defer ts.Close()

        req, _ := http.NewRequest(http.MethodPost, ts.URL,
                strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"acme/search","params":{"query":"x"}}`))
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Accept", "application/json, text/event-stream")
        req.Header.Set("Mcp-Session-Id", "s1")
        req.Header.Set("Mcp-Protocol-Version", "2025-11-25")
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
                t.Fatal(err)
        }
        defer resp.Body.Close()
        body, _ := io.ReadAll(resp.Body)
        if resp.StatusCode != http.StatusOK {
                t.Fatalf("got HTTP %d: %s", resp.StatusCode, body)
        }
}


Output: `got HTTP 400: JSON RPC not handled: "acme/search" unsupported`. The same request with method `tools/list` returns 200, and calling `acme/search` through `NewStreamableHTTPHandler` with `CallCustomMethod` succeeds.
Paste everything between the outer four-backtick fence, including the inner  fence, so GitHub renders it as code.
Where did you hit this?

ToolHive's multi-replica Virtual MCP Server, which serves MCP through go-sdk and resumes a session on another replica by connecting a StreamableServerTransport with Server.Connect.

Go MCP SDK version

v1.8.0; also reproduced on main at caa354d.

Go version

go version go1.27.1 darwin/arm64

Logs
acme/search: 400 JSON RPC not handled: "acme/search" unsupported
主要语言
Go
星标
5.3k
派生
607
平均合并
1 天 7 小时
30 天内合并 PR
81

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

modelcontextprotocol/go-sdk 的其他 Issue

查看 modelcontextprotocol/go-sdk 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。