Proposal: support for sending `tools/list_changed` notifications manually
Is your feature request related to a problem? Please describe.
At the moment in this library it seems to only be possible to send a tools/list_changed notifications when one of two events are triggered: AddTools and RemoveTools. In the GitHub MCP Server we would like to extend this so that these notifications can be sent when other conditions occur such as a mismatch in the version of the MCP server in a session and the client. At this point in time, this does not seem to be possible.
Describe the solution you'd like
For context, we create a new mcp.Server instance per request, since each user may have different tools depending on factors such as their license, the client the token was minted for, if they are a staff user, or if they have a feature toggled on. Because we construct a server for each request, we never add new tools to a server, they are simply there before the server is started.
That being said, we do need a way of notifying clients that new tools are available when they are released. We're looking at doing this via this notification when the available tools differ from what is stored in their session (which is handled outside the scope of the MCP server's internal sessions.)
Describe alternatives you've considered
-
Forcing tool registration changes: We considered artificially adding/removing a dummy tool to trigger the notification, but this is hacky and pollutes the tool list unnecessarily.
-
We've also looked at intercepting requests and return a 400, invalidating a MCP session and theoretically having the client reinitialise their connection thereby getting a new tool list. In practice this is not well supported, while this works in VSCode for example, in Claude Code it results in a persistent broken connections until the agent runtime is restarted.
cc @omgitsads
https://github.com/modelcontextprotocol/go-sdk/compare/main...omgitsads:go-sdk:notify-tool-list-changed and a calling it with
svr := req.GetSession().(*mcp.ServerSession)
err := svr.NotifyToolListChanged(ctx, &mcp.ToolListChangedParams{})
seemed to be sufficient to get the notification on the tool stream when @mattdholloway and I were pairing on this, so just seems like a case of exposing a lever to control this in the same way there is NotifyProgress for progress notifications.
Do any of the other SDKs make this possible?
Does anyone else want this feature?
Note that after #649 is finished, adding and immediately removing a tool will send only one notification instead of two, so it will be functionally equivalent to what you propose as seen from the client.
Our concern with adding this is that we'd have to add it for prompts and resources too for symmetry, and now we've grown our API for what seems like a niche case.
We did also try simply expiring all active sessions, forcing a client renegotiation but unfortunately a large number of client applications just brick the server for the rest of the active session in the user sense.
Do any of the other SDKs make this possible?
mark3labs/mcp-go supports sending these manually with SendNotificationToClient https://github.com/mark3labs/mcp-go/blob/670a95ab91bc3a4a08c3618a56f6555cd17c2307/server/session.go#L311, plus other similar methods, which is what we were using over there.
Our concern with adding this is that we'd have to add it for prompts and resources too for symmetry, and now we've grown our API for what seems like a niche case.
The use case of having a per request MCP server is something that is talked about in the design document.
For example, if the user wants to create a distinct server for each new connection, they can do so in the getServer factory passed to transport handlers.
It does seem like a bit of a hack to add/remove a blank tool to trigger a notification to happen when we release a new tool and would be a similar hack for prompts and resources.
Especially when we remove tools, without this we'd just be relying on end users restarting their MCP servers at some point in the future, and until that point users that haven't would get errors that just get routed to the LLM for any tools that no longer exist.
If we accepted something like #639, we could say that calling SetTools always sends a notification, and so you could force this by setting the current list, even if it didn't change. That's slightly less hacky.
I really want to avoid a scenario where the SDK is supporting use-cases that don't fit into its core design; I'd prefer if we could update the SDK so that the use case is supported. I'd actually be happy to add more API to tool management, provided it serves real need. We just need to be sure we understand the need.
I think I don't understand something here: if each user has their own server (which makes sense given your description), why can't you just maintain the list of tools on that server, which will naturally notify the client of changes as necessary? Why would the client not know about the current set of tools?
When you deploy a new server version the tools available might well change (example, we consolidated some issues tools recently into a single issue read tool). Backwards compatibility and non-breaking changes isn't a requirement. There are options for that, but the behavior of advertising the server has been updated seems reasonable given the current spec.
Aha, I think there's a key piece missing here: the transport has StreamableHTTPOptions.Stateless set, so that from the client's perspective the connection is persistent? Otherwise, wouldn't the session break when you deploy a new server?
Makes sense, but naturally we have to work around this for our purposes as we already run a stateful server, and not having SDK support for us means hacking around/forking internally for our remote server, which is fine if we have to, but it's better to have an official solution 😅.
We have been having to hack around it for now, as we do use a distributed session store and we just have to put session handling, so we do set a session header.
Also important context is that even in a stateless server tools list changed notifications can occur via the POST request of tool calls, and so a server can still naturally send tool updates, and a redeployed stateless server would still have this problem potentially where the client just doesn't know, but when they next make a tool call, it should still be possible to tell them tools have changed at that point. s
@SamMorrowDrums what I'm not understanding is how you deploy a new version of your server without breaking existing sessions, considering that the new server process will not have knowledge of the previous sessions.
I guess this is an artifact of the fact that you're using a distributed session store? How do you make this work without #148?
@findleyr So we're working within the constraints of what was proposed here: https://github.com/modelcontextprotocol/go-sdk/issues/148#issuecomment-3188725198. We run the server with StreamableHTTPOptions.Stateless as false, and manage our state using Mcp-Session-Id outside the scope of the MCP server. We are currently only "stateful" in that we want to attribute all subsequent calls after an initialize call to a specific user & client.
As @SamMorrowDrums mentioned, even in a stateless server with no Mcp-Session-Id header, they client only ever calls tools/list immediately after the initial initialize handshake, unless instructed to do otherwise. That is what we're trying to do here, to tell the client that there are now changes to the available tools and they should refetch.
@omgitsads that comment related to running with Stateless: true and a non-trivial Mcp-Session-Id header. If Stateless is false, then the SDK enforces the initialization lifecycle, and so any request for a session that doesn't exist on the server process would result in an error. I'm sorry to keep asking for clarification; there's still something I don't understand and the details matter here.
If the session is truly stateful, meaning there is an in-memory session object tracking the session lifecycle, and a 1:1 communication channel between client and server process, then the only way to change the set of tools would be through an Add/RemoveTool call, which would trigger a notification.
I can only think of one case where this wouldn't work: if this notification is sent outside an active request from the client, when there is no standalone SSE stream, because the client will never get the message. For that reason, I do think it makes sense to expose a method to allow server authors to send/resend this notification.
Another option would be to buffer up notifications until there's a connection to send them to, but that seems strictly less useful as it provides no guarantees of delivery, or signal when delivery failed.
Again, I'm sorry to ask so many questions, but we just want to make sure that we're not accepting this proposal simply to work around another deficiency of the SDK.
Oops! My apologies, I meant to say that we are running with Stateless: false (I was thinking "Stateful" 🙃).
I can do a small demo repo showing what we're doing to help show what's going on, but we've essentially got
- a HTTP middleware that intercepts requests with the
Mcp-Session-Idto populate the session information in the context, with things like the Clientmcp.Implementationandmcp.ClientCapabilities. - A
GetSessionIdfunc that creates a session ID - A MCP Middleware that intercepts
Initializecalls to create a new session record in Redis, based on thatGetSessionIdresult ininitReq.Session.ID()
I can only think of one case where this wouldn't work: if this notification is sent outside an active request from the client, when there is no standalone SSE stream, because the client will never get the message. For that reason, I do think it makes sense to expose a method to allow server authors to send/resend this notification.
Ideally the GET / stream would be where we are sending this notification, because then the notifications would be proactive vs reactive (i.e. on a tool call stream a user would have to first call a tool to get the notification, vs getting one when the client reestablishes the GET / stream after a deploy)
As it stands right now, we can't do this because the SSE stream is disabled for Stateless: true requests, but I do wonder if we could route requests there using the EventStore interface, if it were available 🤔
And no worries about the questions, I understand and it's not entirely clear (especially with my mistakes)! i'm happy to answer as many as you have 😄
Oops! My apologies, I meant to say that we are running with
Stateless: false(I was thinking "Stateful" 🙃). ... As it stands right now, we can't do this because the SSE stream is disabled forStateless: truerequests
I'm confused by these two statements.
🤦 sorry once again, been a busy week getting the Go SDK migration across the finish line.
We are running with Stateless: true, i.e not having Go SDK manage any sessions, relying on an existing Mcp-Session-Id header being persisted through a request & generating new session IDs with the GetSessionID func.
And specifically all that session handling is done in our own code, and as it stands we currently reject the persistent GET request, which as per the spec is optional, but we were looking at revisiting that if we have the ability to warn end users that tools have changed after a deploy for example.