Skip to content

Commit 929019a

Browse files
fix: Gate notifications on capabilities
Servers may report their [tools.listChanged][] capability as false, in which case they indicate that they will not send notifications when available tools change. Honor the spec by not sending notifications/tools/list_changed notifications when capabilities.tools.listChanged is false. [tools.listChanged]: https://modelcontextprotocol.io/specification/2025-03-26/server/tools#capabilities
1 parent 989faf8 commit 929019a

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

server/session.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ func (s *MCPServer) AddSessionTools(sessionID string, tools ...ServerTool) error
246246
// It only makes sense to send tool notifications to initialized sessions --
247247
// if we're not initialized yet the client can't possibly have sent their
248248
// initial tools/list message
249-
if session.Initialized() {
249+
if session.Initialized() && s.capabilities.tools != nil && s.capabilities.tools.listChanged {
250250
// Send notification only to this session
251251
if err := s.SendNotificationToSpecificClient(sessionID, "notifications/tools/list_changed", nil); err != nil {
252252
// Log the error but don't fail the operation
@@ -304,7 +304,7 @@ func (s *MCPServer) DeleteSessionTools(sessionID string, names ...string) error
304304
// It only makes sense to send tool notifications to initialized sessions --
305305
// if we're not initialized yet the client can't possibly have sent their
306306
// initial tools/list message
307-
if session.Initialized() {
307+
if session.Initialized() && s.capabilities.tools != nil && s.capabilities.tools.listChanged {
308308
// Send notification only to this session
309309
if err := s.SendNotificationToSpecificClient(sessionID, "notifications/tools/list_changed", nil); err != nil {
310310
// Log the error but don't fail the operation

server/session_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,3 +802,62 @@ func TestMCPServer_NotificationChannelBlocked(t *testing.T) {
802802
assert.Equal(t, "blocked-session", localErrorSessionID, "Session ID should be captured in the error hook")
803803
assert.Equal(t, "broadcast-message", localErrorMethod, "Method should be captured in the error hook")
804804
}
805+
806+
func TestMCPServer_ToolNotificationsDisabled(t *testing.T) {
807+
// This test verifies that when tool capabilities are disabled, we still
808+
// add/delete tools correctly but don't send notifications about it.
809+
//
810+
// This is important because:
811+
// 1. Tools should still work even if notifications are disabled
812+
// 2. We shouldn't waste resources sending notifications that won't be used
813+
// 3. The client might not be ready to handle tool notifications yet
814+
815+
// Create a server WITHOUT tool capabilities
816+
server := NewMCPServer("test-server", "1.0.0", WithToolCapabilities(false))
817+
ctx := context.Background()
818+
819+
// Create an initialized session
820+
sessionChan := make(chan mcp.JSONRPCNotification, 1)
821+
session := &sessionTestClientWithTools{
822+
sessionID: "session-1",
823+
notificationChannel: sessionChan,
824+
initialized: true,
825+
}
826+
827+
// Register the session
828+
err := server.RegisterSession(ctx, session)
829+
require.NoError(t, err)
830+
831+
// Add a tool
832+
err = server.AddSessionTools(session.SessionID(),
833+
ServerTool{Tool: mcp.NewTool("test-tool")},
834+
)
835+
require.NoError(t, err)
836+
837+
// Verify no notification was sent
838+
select {
839+
case <-sessionChan:
840+
t.Error("Expected no notification to be sent when capabilities.tools.listChanged is false")
841+
default:
842+
// This is the expected case - no notification should be sent
843+
}
844+
845+
// Verify tool was added to session
846+
assert.Len(t, session.GetSessionTools(), 1)
847+
assert.Contains(t, session.GetSessionTools(), "test-tool")
848+
849+
// Delete the tool
850+
err = server.DeleteSessionTools(session.SessionID(), "test-tool")
851+
require.NoError(t, err)
852+
853+
// Verify no notification was sent
854+
select {
855+
case <-sessionChan:
856+
t.Error("Expected no notification to be sent when capabilities.tools.listChanged is false")
857+
default:
858+
// This is the expected case - no notification should be sent
859+
}
860+
861+
// Verify tool was deleted from session
862+
assert.Len(t, session.GetSessionTools(), 0)
863+
}

0 commit comments

Comments
 (0)