You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Expose generic SendNotification on ServerSession for custom protocol extensions #745
I am implementing an MCP Server for the Gemini CLI IDE Integration. The specification requires the server to send custom notifications to the client to signal specific events.
Specifically, I need to send:
ide/diffAccepted
ide/diffRejected
ide/contextUpdate
The Problem
Currently, the mcp.ServerSession struct only exposes specific notification helpers (like NotifyProgress and Log). There is no public API to send a generic/custom JSON-RPC notification.
I attempted to use session.Notify(ctx, method, params), but:
The conn field on ServerSession is unexported.
The mcp.Connection interface's Write method requires *mcp.JSONRPCMessage, which is an unexported struct.
Proposed Solution
Please expose a generic notification method on ServerSession, similar to:
While the MCP spec does not explicitly allow custom notifications, it doesn't explicitly exclude them either.
I confirmed by reading the linked doc that the IDE plugin must talk to the IDE over an HTTP connection using MCP, and that it does specify some custom notifications.
Some issues that make this more complicated than the simple one-liner in the top post:
All other notifications go through sending middleware. These should too.
The sending mechanism recognizes a notification because its method starts with "notifications/". These do not.
A server may support many sessions. Presumbably the notification should be sent to them all. (Even if there's ever only one for the IDE; we need to think more generally.)
Let's discuss the design here first. We should start with how we're going to recognize these custom notifications. I'd rather not have a registration mechanism. Maybe we can design a convention.
I don't think we should skip the middleware. It may be performing accounting, logging, PII scrubbing, and other operations that should apply to all outgoing messages.
One simple idea: if method begins with the segment x-notifications, that segment is removed and the remainder is sent as a notification. We would skip everything else in the function until L107.
Per @jba's request on #898, moving the discussion here. This builds on the constraints @jba named:
Per-session only (no broadcast), and
Full integration with middleware pipeline.
Goal
Allow MCP server and client to send and receive custom JSON-RPC notification methods in both directions, e.g. notifications/claude/channel, ide/diffAccepted:
Routed through AddSendingMiddleware and AddReceivingMiddleware
Symmetric across Client and Server
Per-session in scope
Send side
##844 implements send side via the x-notifications/ prefix convention @jbaproposed above. ServerSession.SendNotification(ctx, method, params) and ClientSession.SendNotification(ctx, method, params) wrap the payload, prepend x-notifications/ internally so the method-validity check in defaultSendingMethodHandler accepts it, and strip the prefix before writing to the wire.
Thus I propose we simply adopt the changes provided in #844 for send.
Receive side
Using x-notifications/ will not work on receive: the wire delivers whatever method the peer sent, and there is no validity check to bypass, just an unknown-method drop. We need a registration API for handlers, analogous to AddTool().
Primary API: typed per-method registration
// On the server.
mcp.AddNotificationHandler[Paramsany](
s*mcp.Server,
methodstring,
handlerfunc(ctx context.Context, ss*mcp.ServerSession, p*Params) error,
)
// Symmetric on the client.
mcp.AddNotificationHandler[Paramsany](
c*mcp.Client,
methodstring,
handlerfunc(ctx context.Context, cs*mcp.ClientSession, p*Params) error,
)
Behavior:
Registration happens before Server.Connect() / Client.Connect(), like AddTool().
Inbound dispatch checks the spec method registry first, then falls through to the custom registry.
The handler runs through AddReceivingMiddleware exactly like a spec method handler.
Params are unmarshalled into *Params automatically.
Optional secondary API: catch-all handler
For protocol bridges or dynamic-method use cases:
typeNotificationHandlerfunc(
ctx context.Context,
ss*mcp.ServerSession,
methodstring,
raw json.RawMessage) error// On ServerOptions and ClientOptions.NotificationHandlerNotificationHandler
If both a typed per-method handler and a catch-all are registered, the typed one wins for that method. The catch-all sees only methods with no typed registration. The catch-all also runs through receiving middleware.
I would prefer to only implement the typed primary API and add the catch-all later, if a real use case appears, but I would be happy to implement both if required.
Internals
New field on Server and Client: customNotificationHandlers map[string]methodInfo, keyed by raw method name.
The methodInfo for each entry: newRequest constructs a typed *Params, dispatch calls the user handler.
In the inbound dispatch path, check spec methodInfos first, then the custom registry, then the catch-all if set, then drop with the existing unknown-method behavior.
All paths invoke AddReceivingMiddleware consistently, so accounting, logging, and PII scrubbing apply uniformly.
Registration timing. Pre-Connect only (like AddTool()), or also runtime registration on a live session? Pre-Connect is simpler; runtime supports plugin-style use cases.
Typed vs raw on the primary API. The above assumes typed via generics, matching AddTool's shape. Alternative is func(ctx, ss, json.RawMessage) error with user doing unmarshalling. Preference?
Method-name validation on registration. Accept any string, or require vendor-prefix such as {vendor}/... or notifications/{vendor}/...? The spec does not require either.
Symmetry of the x- prefix. Is the prefix purely a send-side internal artifact, or do you want it on receive too, e.g. as a registration-time convention?
Registration timing: Why must you add tools before you connect? In fact the spec has a specific feature, change notifications, to support that.
Typed vs. raw API: I think typed is fine for now.
Accept any string, but fail if it matches an existing method (including a previously registered custom one). That will mean, by the way, that the order in which registrations are checked won't matter.
The x- prefix should not be visible to the user.
The NotificationHandler must take the request object so it can access meta and possibly other fields, like ToolHandlerFor.
I will let Jonathan do the main review, since he already engaged in the topic, but a few thoughts from my side:
Re registration timing: I believe currently all notification handlers are specified in the ServerOptions (or ClientOptions) which are passed to Server/Client constructors. These options are not copied, so I assume they could be swapped while the Server/Client is already running, but the access is not synchronized so it could cause some data races.
If a generic solution works well, we could consider replacing all statically typed handlers with it in the next major version, simplifying the API.
+1 to keeping x- prefix internal and failing on existing method names (+ also validating normal JSON RPC method name requirements, if there are any).
Dynamic registration with Server.mu / Client.mu protection, modeled on AddTool().
Open question
Should Server.RemoveNotificationHandler(method) error (and the client equivalent) be in scope for this PR, or scoped to a follow-up? Dynamic registration implies the need for that but you did not explicitly request it.
Next step
Unless you have other concerns to address, I can create a PR once the open question is answered.
Activity
jba commentedon Jan 10, 2026
While the MCP spec does not explicitly allow custom notifications, it doesn't explicitly exclude them either.
I confirmed by reading the linked doc that the IDE plugin must talk to the IDE over an HTTP connection using MCP, and that it does specify some custom notifications.
So I recommend we do this.
jba commentedon Jan 10, 2026
Some issues that make this more complicated than the simple one-liner in the top post:
vaijab commentedon Jan 10, 2026
@jba thanks for looking into this. Shall I raise a PR with the changes or you prefer that maintainers do it?
jba commentedon Jan 10, 2026
A PR would be great. I don't quite see how to do it myself, frankly.
jba commentedon Jan 10, 2026
Let's discuss the design here first. We should start with how we're going to recognize these custom notifications. I'd rather not have a registration mechanism. Maybe we can design a convention.
findleyr commentedon Jan 10, 2026
@jba I think we can just expose a method to send an arbitrary notification. No registry needed.
jba commentedon Jan 11, 2026
@findleyr It should go through the middleware, which means it will eventually get to https://github.com/modelcontextprotocol/go-sdk/blob/main/mcp/shared.go#L91, which expects all methods to be known and recognizes notifications by method name.
I don't think we should skip the middleware. It may be performing accounting, logging, PII scrubbing, and other operations that should apply to all outgoing messages.
One simple idea: if
methodbegins with the segmentx-notifications, that segment is removed and the remainder is sent as a notification. We would skip everything else in the function until L107.feat: implement generic SendNotification method for Issue modelcontex…
mcp: support custom notifications via SendNotification
mcp: support custom notifications via SendNotification
mikeschinkel commentedon Apr 27, 2026
Design sketch: custom notifications, send + receive
Per @jba's request on #898, moving the discussion here. This builds on the constraints @jba named:
Goal
Allow MCP server and client to send and receive custom JSON-RPC notification methods in both directions, e.g.
notifications/claude/channel,ide/diffAccepted:AddSendingMiddlewareandAddReceivingMiddlewareClientandServerSend side
##844 implements send side via the
x-notifications/prefix convention @jba proposed above.ServerSession.SendNotification(ctx, method, params)andClientSession.SendNotification(ctx, method, params)wrap the payload, prependx-notifications/internally so the method-validity check indefaultSendingMethodHandleraccepts it, and strip the prefix before writing to the wire.Thus I propose we simply adopt the changes provided in #844 for send.
Receive side
Using
x-notifications/will not work on receive: the wire delivers whatever method the peer sent, and there is no validity check to bypass, just an unknown-method drop. We need a registration API for handlers, analogous toAddTool().Primary API: typed per-method registration
Behavior:
Server.Connect()/Client.Connect(), likeAddTool().AddReceivingMiddlewareexactly like a spec method handler.*Paramsautomatically.Optional secondary API: catch-all handler
For protocol bridges or dynamic-method use cases:
If both a typed per-method handler and a catch-all are registered, the typed one wins for that method. The catch-all sees only methods with no typed registration. The catch-all also runs through receiving middleware.
I would prefer to only implement the typed primary API and add the catch-all later, if a real use case appears, but I would be happy to implement both if required.
Internals
ServerandClient:customNotificationHandlers map[string]methodInfo, keyed by raw method name.methodInfofor each entry:newRequestconstructs a typed*Params,dispatchcalls the user handler.methodInfosfirst, then the custom registry, then the catch-all if set, then drop with the existing unknown-method behavior.AddReceivingMiddlewareconsistently, so accounting, logging, and PII scrubbing apply uniformly.Decisions assumed
Open questions
Connectonly (likeAddTool()), or also runtime registration on a live session? Pre-Connectis simpler; runtime supports plugin-style use cases.AddTool's shape. Alternative isfunc(ctx, ss, json.RawMessage) errorwith user doing unmarshalling. Preference?{vendor}/...ornotifications/{vendor}/...? The spec does not require either.x-prefix. Is the prefix purely a send-side internal artifact, or do you want it on receive too, e.g. as a registration-time convention?Motivating use cases
use-case](https://github.com/mikeschinkel/endless/blob/0b41d35bc2112129cc4bb28a55ccd
50439786d4e/cmd/endless-channel/main.go#L125-L128)): Server pushes
notifications/claude/channelto the client. Send direction only in the current implementation.SendNotificationonServerSessionfor custom protocol extensions #745 origin): IDE sendside/diffAccepted,ide/diffRejected, andide/contextUpdatenotifications that need to be received.Claude Channels needs send; Gemini's IDE companion needs receive; a use case for
each direction.
Plan
If this design is acceptable:
Looking forward to input on my characterization of requirements, my assumptions, and the open questions.
jba commentedon Apr 27, 2026
Nice.
Registration timing: Why must you add tools before you connect? In fact the spec has a specific feature, change notifications, to support that.
Typed vs. raw API: I think typed is fine for now.
Accept any string, but fail if it matches an existing method (including a previously registered custom one). That will mean, by the way, that the order in which registrations are checked won't matter.
The x- prefix should not be visible to the user.
The NotificationHandler must take the request object so it can access meta and possibly other fields, like ToolHandlerFor.
maciej-kisiel commentedon Apr 27, 2026
I will let Jonathan do the main review, since he already engaged in the topic, but a few thoughts from my side:
Re registration timing: I believe currently all notification handlers are specified in the
ServerOptions(orClientOptions) which are passed toServer/Clientconstructors. These options are not copied, so I assume they could be swapped while theServer/Clientis already running, but the access is not synchronized so it could cause some data races.If a generic solution works well, we could consider replacing all statically typed handlers with it in the next major version, simplifying the API.
+1 to keeping x- prefix internal and failing on existing method names (+ also validating normal JSON RPC method name requirements, if there are any).
mikeschinkel commentedon Apr 28, 2026
Thank you @jba and @maciej-kisiel.
Acknowledging your requests, including mirroring
ToolHandlerFor's shape and reusing the existingServerRequest[P]/ClientRequest[P]types.Decisions resolved
These are what I assume have been resolved; please let me know otherwise:
x-prefix internal and invisible to users.rpc.prefix. Single flat namespace; dispatch order is irrelevant.ToolHandlerFor(reusingServerRequest[P]/ClientRequest[P]).Server.mu/Client.muprotection, modeled onAddTool().Open question
Server.RemoveNotificationHandler(method) error(and the client equivalent) be in scope for this PR, or scoped to a follow-up? Dynamic registration implies the need for that but you did not explicitly request it.Next step
Unless you have other concerns to address, I can create a PR once the open question is answered.
jba commentedon Apr 28, 2026
LGTM.
Let's add removal. Following RemoveTools, the signature should be
There is no error return.
dominic1988-lgtm commentedon May 10, 2026
Looking forward to this function avaliable
mcp: support custom notifications
mcp: support custom notifications
feat(mcp): support custom notifications for protocol extensions