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

オープン
#1,506 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
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日 11時間
マージ済み PR(30日)
82

環境構築

Codespaces で開く

このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

modelcontextprotocol/go-sdk のほかの issue

modelcontextprotocol/go-sdk の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。