diff --git a/server/server.go b/server/server.go index 64ed9529d7e1c24a4789e598c4a78abfd6d5c23a..78868668debe566aed6406402a8613224b079fc1 100644 --- a/server/server.go +++ b/server/server.go @@ -163,9 +163,13 @@ type toolCapabilities struct { // WithResourceCapabilities configures resource-related server capabilities func WithResourceCapabilities(subscribe, listChanged bool) ServerOption { return func(s *MCPServer) { - s.capabilities.resources = &resourceCapabilities{ - subscribe: subscribe, - listChanged: listChanged, + if !subscribe && !listChanged { + s.capabilities.resources = nil + } else { + s.capabilities.resources = &resourceCapabilities{ + subscribe: subscribe, + listChanged: listChanged, + } } } } @@ -173,8 +177,12 @@ func WithResourceCapabilities(subscribe, listChanged bool) ServerOption { // WithPromptCapabilities configures prompt-related server capabilities func WithPromptCapabilities(listChanged bool) ServerOption { return func(s *MCPServer) { - s.capabilities.prompts = &promptCapabilities{ - listChanged: listChanged, + if !listChanged { + s.capabilities.prompts = nil + } else { + s.capabilities.prompts = &promptCapabilities{ + listChanged: listChanged, + } } } } @@ -182,8 +190,12 @@ func WithPromptCapabilities(listChanged bool) ServerOption { // WithToolCapabilities configures tool-related server capabilities func WithToolCapabilities(listChanged bool) ServerOption { return func(s *MCPServer) { - s.capabilities.tools = &toolCapabilities{ - listChanged: listChanged, + if !listChanged { + s.capabilities.tools = nil + } else { + s.capabilities.tools = &toolCapabilities{ + listChanged: listChanged, + } } } } @@ -211,9 +223,9 @@ func NewMCPServer( notificationHandlers: make(map[string]NotificationHandlerFunc), notifications: make(chan ServerNotification, 100), capabilities: serverCapabilities{ - tools: &toolCapabilities{}, - resources: &resourceCapabilities{}, - prompts: &promptCapabilities{}, + tools: nil, + resources: nil, + prompts: nil, logging: false, }, } @@ -325,7 +337,7 @@ func (s *MCPServer) HandleMessage( } return s.handleListResourceTemplates(ctx, baseMessage.ID, request) case "resources/read": - if !s.capabilities.resources.listChanged { + if s.capabilities.resources == nil { return createErrorResponse( baseMessage.ID, mcp.METHOD_NOT_FOUND, @@ -359,7 +371,7 @@ func (s *MCPServer) HandleMessage( } return s.handleListPrompts(ctx, baseMessage.ID, request) case "prompts/get": - if !s.capabilities.prompts.listChanged { + if s.capabilities.prompts == nil { return createErrorResponse( baseMessage.ID, mcp.METHOD_NOT_FOUND, @@ -376,7 +388,7 @@ func (s *MCPServer) HandleMessage( } return s.handleGetPrompt(ctx, baseMessage.ID, request) case "tools/list": - if len(s.tools) == 0 { + if s.capabilities.tools == nil { return createErrorResponse( baseMessage.ID, mcp.METHOD_NOT_FOUND, @@ -393,7 +405,7 @@ func (s *MCPServer) HandleMessage( } return s.handleListTools(ctx, baseMessage.ID, request) case "tools/call": - if !s.capabilities.tools.listChanged || len(s.tools) == 0 { + if s.capabilities.tools == nil { return createErrorResponse( baseMessage.ID, mcp.METHOD_NOT_FOUND, @@ -424,7 +436,7 @@ func (s *MCPServer) AddResource( handler ResourceHandlerFunc, ) { if s.capabilities.resources == nil { - panic("Resource capabilities not enabled") + s.capabilities.resources = &resourceCapabilities{} } s.mu.Lock() defer s.mu.Unlock() @@ -440,7 +452,7 @@ func (s *MCPServer) AddResourceTemplate( handler ResourceTemplateHandlerFunc, ) { if s.capabilities.resources == nil { - panic("Resource capabilities not enabled") + s.capabilities.resources = &resourceCapabilities{} } s.mu.Lock() defer s.mu.Unlock() @@ -453,7 +465,7 @@ func (s *MCPServer) AddResourceTemplate( // AddPrompt registers a new prompt handler with the given name func (s *MCPServer) AddPrompt(prompt mcp.Prompt, handler PromptHandlerFunc) { if s.capabilities.prompts == nil { - panic("Prompt capabilities not enabled") + s.capabilities.prompts = &promptCapabilities{} } s.mu.Lock() defer s.mu.Unlock() @@ -468,6 +480,9 @@ func (s *MCPServer) AddTool(tool mcp.Tool, handler ToolHandlerFunc) { // AddTools registers multiple tools at once func (s *MCPServer) AddTools(tools ...ServerTool) { + if s.capabilities.tools == nil { + s.capabilities.tools = &toolCapabilities{} + } s.mu.Lock() for _, entry := range tools { s.tools[entry.Tool.Name] = entry @@ -525,24 +540,33 @@ func (s *MCPServer) handleInitialize( ) mcp.JSONRPCMessage { capabilities := mcp.ServerCapabilities{} - capabilities.Resources = &struct { - Subscribe bool `json:"subscribe,omitempty"` - ListChanged bool `json:"listChanged,omitempty"` - }{ - Subscribe: s.capabilities.resources.subscribe, - ListChanged: s.capabilities.resources.listChanged, + // Only add resource capabilities if they're configured + if s.capabilities.resources != nil { + capabilities.Resources = &struct { + Subscribe bool `json:"subscribe,omitempty"` + ListChanged bool `json:"listChanged,omitempty"` + }{ + Subscribe: s.capabilities.resources.subscribe, + ListChanged: s.capabilities.resources.listChanged, + } } - capabilities.Prompts = &struct { - ListChanged bool `json:"listChanged,omitempty"` - }{ - ListChanged: s.capabilities.prompts.listChanged, + // Only add prompt capabilities if they're configured + if s.capabilities.prompts != nil { + capabilities.Prompts = &struct { + ListChanged bool `json:"listChanged,omitempty"` + }{ + ListChanged: s.capabilities.prompts.listChanged, + } } - capabilities.Tools = &struct { - ListChanged bool `json:"listChanged,omitempty"` - }{ - ListChanged: s.capabilities.tools.listChanged, + // Only add tool capabilities if they're configured + if s.capabilities.tools != nil { + capabilities.Tools = &struct { + ListChanged bool `json:"listChanged,omitempty"` + }{ + ListChanged: s.capabilities.tools.listChanged, + } } if s.capabilities.logging { diff --git a/server/server_test.go b/server/server_test.go index 4574456ac14f038633375762fa0e5a20fce9d41a..579434321ce637a00676356adbe9749427b5464f 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -40,13 +40,9 @@ func TestMCPServer_Capabilities(t *testing.T) { ) assert.Equal(t, "test-server", initResult.ServerInfo.Name) assert.Equal(t, "1.0.0", initResult.ServerInfo.Version) - assert.NotNil(t, initResult.Capabilities.Resources) - assert.False(t, initResult.Capabilities.Resources.Subscribe) - assert.False(t, initResult.Capabilities.Resources.ListChanged) - assert.NotNil(t, initResult.Capabilities.Prompts) - assert.False(t, initResult.Capabilities.Prompts.ListChanged) - assert.NotNil(t, initResult.Capabilities.Tools) - assert.False(t, initResult.Capabilities.Tools.ListChanged) + assert.Nil(t, initResult.Capabilities.Resources) + assert.Nil(t, initResult.Capabilities.Prompts) + assert.Nil(t, initResult.Capabilities.Tools) assert.Nil(t, initResult.Capabilities.Logging) }, }, @@ -118,8 +114,8 @@ func TestMCPServer_Capabilities(t *testing.T) { assert.NotNil(t, initResult.Capabilities.Prompts) assert.True(t, initResult.Capabilities.Prompts.ListChanged) - assert.NotNil(t, initResult.Capabilities.Tools) - assert.False(t, initResult.Capabilities.Tools.ListChanged) + // Tools capability should be nil when WithToolCapabilities(false) is used + assert.Nil(t, initResult.Capabilities.Tools) assert.NotNil(t, initResult.Capabilities.Logging) }, @@ -210,7 +206,15 @@ func TestMCPServer_Tools(t *testing.T) { assert.Equal(t, "notifications/tools/list_changed", notifications[0].Notification.Method) // One for DeleteTools assert.Equal(t, "notifications/tools/list_changed", notifications[1].Notification.Method) - assert.Equal(t, "Tools not supported", toolsList.(mcp.JSONRPCError).Error.Message) + + // Expect a successful response with an empty list of tools + resp, ok := toolsList.(mcp.JSONRPCResponse) + assert.True(t, ok, "Expected JSONRPCResponse, got %T", toolsList) + + result, ok := resp.Result.(mcp.ListToolsResult) + assert.True(t, ok, "Expected ListToolsResult, got %T", resp.Result) + + assert.Empty(t, result.Tools, "Expected empty tools list") }, }, }