mcp: list_changed can only be broadcast; a server cannot notify one session (2026-07-28 subscriptions/listen)
メンテナーはふだん 1 日以内に返信
@huynle-dev がすでに取り組んでいます。
2026年11月2日 から。
評価
調査の方向性
Start with Server.changeAndNotify and notifySessions in mcp/server.go, then notifySubscribedSessions and injectMetaSubscriptionID, to see how list-changed reaches the subscription tables today. Read TestSubscriptionsListen_MultipleSessions to learn the fan-out test pattern. Done means a ServerSession method sends a list-changed notification only to that session's open listen stream, with the listen's ID in _meta, and does nothing when no listen is open. The open questions in the issue need a maintainer decision before implementation.
索引モデルが issue の本文から書いたものです。
説明
#666 asked for a way to send notifications/tools/list_changed to a specific client and was closed as obsolete under 2026-07-28. The transport changed (subscriptions/listen replaced the standalone GET stream), but delivery did not: in v1.8.0 (and on main @ eafb671) a server still cannot send a list-changed notification to one session.
Today
- The only trigger is
Server.changeAndNotify(mcp/server.go:747, v1.8.0), run onAddTool/RemoveTool/AddPrompt/…. After debouncing it callsnotifySessions, which sends to every legacy session and every 2026-07-28 session intoolChangeSubscriptions/promptChangeSubscriptions/resourceChangeSubscriptions. - The per-session sender
notifySubscribedSessions(:810) and the listen-request ID stamped underMetaKeySubscriptionID(injectMetaSubscriptionID,:832) are unexported.ServerSessionexportsNotifyProgress,NotifyElicitationComplete,Log, … but no list-changed sender. TestSubscriptionsListen_MultipleSessionsasserts the fan-out.
Use case
We run a multi-tenant MCP gateway on go-sdk: one stateless *mcp.Server behind several replicas. Each principal sees a different tool/prompt/resource list (registry + policy engine + OAuth scopes). A receiving middleware serves tools/list and tools/call; the SDK tool table stays empty, because per-principal servers with ~1000 tools each are not viable (#1265 has the same constraint).
When a principal's visible set changes, only that principal's open subscriptions/listen streams should get list_changed. Clients cache catalogs, so without it they keep calling tools that are gone and never see new ones.
The SDK already accepts the listen, acks it, records req.Session → requestID, forces SSE and retires the subscription. The missing piece is the last step: send this notification to this session's listen.
Workarounds, each with a problem:
AddTool/RemoveToola dummy tool plus a sending middleware that drops the notification for other principals (the #666 hack): fans out N notifications to drop N−1, and the routing key has to be smuggled through_meta(no ctx propagation, #1265).- One
*mcp.Serverper principal fromgetServer, diffing its tool table: duplicates the catalog per principal just to reach the notifier. - Writing the frame ourselves is impossible: the listen request ID is unreachable (
idContextKeyand the subscription tables are unexported).
Proposal
// NotifyToolListChanged sends notifications/tools/list_changed to this session.
// For a 2026-07-28 session it is delivered on the session's active
// subscriptions/listen stream that opted into toolsListChanged, with that
// listen's request ID under _meta[MetaKeySubscriptionID]. If no such listen is
// open, it does nothing and returns nil. Legacy sessions receive it on the
// standalone stream, as changeAndNotify does today.
func (ss *ServerSession) NotifyToolListChanged(ctx context.Context, params *ToolListChangedParams) error
func (ss *ServerSession) NotifyPromptListChanged(ctx context.Context, params *PromptListChangedParams) error
func (ss *ServerSession) NotifyResourceListChanged(ctx context.Context, params *ResourceListChangedParams) error
Implementation: lock s.mu, look up s.toolChangeSubscriptions[ss], unlock, copy params, injectMetaSubscriptionID, then handleNotify with the caller's ctx. Server.notifySessions can become a loop over these (one delivery path; composes with #1265).
If three methods is too much surface (the concern raised in #666), a single func (ss *ServerSession) NotifyListChanged(ctx context.Context, kind string) error would also work.
Usage:
server.AddReceivingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) {
if method == "subscriptions/listen" {
ss := req.GetSession().(*mcp.ServerSession)
stop := watchPrincipal(ctx, principalFrom(ctx), func() {
_ = ss.NotifyToolListChanged(ctx, &mcp.ToolListChangedParams{})
})
defer stop()
}
return next(ctx, method, req) // SDK registers, acks, blocks until the stream ends
}
})
Open questions: (a) "no open listen" → nil (our preference) or a sentinel error? (b) keep the shouldSendListChangedNotification capability gate (we'd say yes).
Happy to send a PR with tests.
- 主要言語
- Go
- スター
- 5.3k
- フォーク
- 607
- 平均マージ
- 1日 17時間
- マージ済み PR(30日)
- 80
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
modelcontextprotocol/go-sdk のほかの issue
-
Expose generic `SendNotification` on `ServerSession` for custom protocol extensions再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンneeds investigation
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
modelcontextprotocol/go-sdk#745 · コメント 13 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
mcp: a second subscriptions/listen for the same URI silences the first対応中かも @jmrplens が今日担当しました。 オープン
modelcontextprotocol/go-sdk#1489 · コメント 3 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
mcp: ClientSession.CallTool omits Mcp-Param-* headers unless the tool is in the ListTools cache, so a call without a prior tools/list is rejected対応中かも @huynle-dev が今日担当しました。 オープン
難易度 4/5 1〜2日 初心者へのやさしさ 35/100
modelcontextprotocol/go-sdk#1481 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
mcp: Client.Connect falls back to initialize after a 401 on server/discover, running the authorization flow twice対応中かも @kerthans が 12 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 35/100
modelcontextprotocol/go-sdk#1423 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
jsonrpc: decimal and exponent integer IDs still lose precision after #1401対応中かも @skosovsky が 17 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 25/100
modelcontextprotocol/go-sdk#1404 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
modelcontextprotocol/go-sdk の issue をすべて見る
似ている issue
-
bug good first issue help wanted
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
[mesheryctl] design delete fails when passing file or URL as positional argument対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンcomponent/mesheryctl kind/bug language/go
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
meshery/meshery#22772 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
backend security size:S
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
limitlessprojects/Go-Hybrid#3342 ·
メンテナーはふだん 1 日以内に返信
-
status/ready type/bug
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
buildpacks/pack#2733 ·
メンテナーはふだん 1 日以内に返信