mcp: list_changed can only be broadcast; a server cannot notify one session (2026-07-28 subscriptions/listen)

Đang mở
#1,480 1 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@huynle-dev đang làm issue này rồi.

Từ ngày 2/11/2026.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
32/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
go
Lĩnh vực
api

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

#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 on AddTool/RemoveTool/AddPrompt/…. After debouncing it calls notifySessions, which sends to every legacy session and every 2026-07-28 session in toolChangeSubscriptions / promptChangeSubscriptions / resourceChangeSubscriptions.
  • The per-session sender notifySubscribedSessions (:810) and the listen-request ID stamped under MetaKeySubscriptionID (injectMetaSubscriptionID, :832) are unexported. ServerSession exports NotifyProgress, NotifyElicitationComplete, Log, … but no list-changed sender.
  • TestSubscriptionsListen_MultipleSessions asserts 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:

  1. AddTool/RemoveTool a 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).
  2. One *mcp.Server per principal from getServer, diffing its tool table: duplicates the catalog per principal just to reach the notifier.
  3. Writing the frame ourselves is impossible: the listen request ID is unreachable (idContextKey and 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.

Ngôn ngữ chính
Go
Star
5.3k
Fork
607
Merge trung bình
1 ngày 17 giờ
Pull request đã merge (30 ngày)
80

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của modelcontextprotocol/go-sdk

Tất cả issue của modelcontextprotocol/go-sdk

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.