Commit 1635915
Changed files (4)
pkg
pkg/mcp/roots.go
@@ -17,4 +17,4 @@ func NewRoot(uri, name string) Root {
URI: uri,
Name: name,
}
-}
\ No newline at end of file
+}
pkg/mcp/server.go
@@ -10,156 +10,25 @@ import (
"strings"
)
-// ServerBuilder helps build immutable MCP servers with a fluent API
-type ServerBuilder struct {
- name string
- version string
- tools []Tool
- prompts []Prompt
- resources []Resource
- roots []Root
-
- // Optional handlers
- initializeHandler func(InitializeRequest) (InitializeResult, error)
- shutdownHandler func() error
-}
-
-// Server represents an immutable MCP server
type Server struct {
name string
version string
capabilities ServerCapabilities
- // Immutable component definitions (handlers embedded in structs)
- toolDefinitions map[string]Tool
- promptDefinitions map[string]Prompt
+ tools map[string]Tool
+ prompts map[string]Prompt
resourceDefinitions map[string]Resource
rootDefinitions map[string]Root
- // Lifecycle handlers
initializeHandler func(InitializeRequest) (InitializeResult, error)
shutdownHandler func() error
}
-// Handler types
type ResourceHandler func(ReadResourceRequest) (ReadResourceResult, error)
-// NewServerBuilder creates a new server builder
-func NewServerBuilder(name, version string) *ServerBuilder {
- return &ServerBuilder{
- name: name,
- version: version,
- tools: []Tool{},
- prompts: []Prompt{},
- resources: []Resource{},
- roots: []Root{},
- }
-}
-
-// AddTool adds a tool to the server
-func (b *ServerBuilder) AddTool(tool Tool) *ServerBuilder {
- b.tools = append(b.tools, tool)
- return b
-}
-
-// AddPrompt adds a prompt to the server
-func (b *ServerBuilder) AddPrompt(prompt Prompt) *ServerBuilder {
- b.prompts = append(b.prompts, prompt)
- return b
-}
-
-// AddResource adds a resource to the server
-func (b *ServerBuilder) AddResource(resource Resource) *ServerBuilder {
- b.resources = append(b.resources, resource)
- return b
-}
-
-// AddRoot adds a root to the server
-func (b *ServerBuilder) AddRoot(root Root) *ServerBuilder {
- b.roots = append(b.roots, root)
- return b
-}
-
-// SetInitializeHandler sets the initialize handler
-func (b *ServerBuilder) SetInitializeHandler(handler func(InitializeRequest) (InitializeResult, error)) *ServerBuilder {
- b.initializeHandler = handler
- return b
-}
-
-// SetShutdownHandler sets the shutdown handler
-func (b *ServerBuilder) SetShutdownHandler(handler func() error) *ServerBuilder {
- b.shutdownHandler = handler
- return b
-}
-
-// Build creates an immutable server with dynamic capabilities
-func (b *ServerBuilder) Build() *Server {
- // Build dynamic capabilities based on what components are present
- capabilities := ServerCapabilities{
- Logging: &LoggingCapability{}, // Always present
- }
-
- if len(b.tools) > 0 {
- capabilities.Tools = &ToolsCapability{}
- }
- if len(b.prompts) > 0 {
- capabilities.Prompts = &PromptsCapability{}
- }
- if len(b.resources) > 0 {
- capabilities.Resources = &ResourcesCapability{}
- }
- if len(b.roots) > 0 {
- capabilities.Roots = &RootsCapability{}
- }
-
- server := &Server{
- name: b.name,
- version: b.version,
- capabilities: capabilities,
- toolDefinitions: make(map[string]Tool),
- promptDefinitions: make(map[string]Prompt),
- resourceDefinitions: make(map[string]Resource),
- rootDefinitions: make(map[string]Root),
- initializeHandler: b.initializeHandler,
- shutdownHandler: b.shutdownHandler,
- }
-
- // Register all components
- for _, tool := range b.tools {
- server.toolDefinitions[tool.Name] = tool
- }
- for _, prompt := range b.prompts {
- server.promptDefinitions[prompt.Name] = prompt
- }
- for _, resource := range b.resources {
- server.resourceDefinitions[resource.URI] = resource
- }
- for _, root := range b.roots {
- server.rootDefinitions[root.URI] = root
- }
-
- return server
-}
-
-
-
-
-
-// SetInitializeHandler sets the initialize handler
-func (s *Server) SetInitializeHandler(handler func(InitializeRequest) (InitializeResult, error)) {
- s.initializeHandler = handler
-}
-
-// SetShutdownHandler sets the shutdown handler
-func (s *Server) SetShutdownHandler(handler func() error) {
- s.shutdownHandler = handler
-}
-
-
-// ListTools returns all registered tools
func (s *Server) ListTools() []Tool {
- tools := make([]Tool, 0, len(s.toolDefinitions))
- for _, tool := range s.toolDefinitions {
+ tools := make([]Tool, 0, len(s.tools))
+ for _, tool := range s.tools {
tools = append(tools, tool)
}
return tools
@@ -167,8 +36,8 @@ func (s *Server) ListTools() []Tool {
// ListPrompts returns all registered prompts
func (s *Server) ListPrompts() []Prompt {
- prompts := make([]Prompt, 0, len(s.promptDefinitions))
- for _, prompt := range s.promptDefinitions {
+ prompts := make([]Prompt, 0, len(s.prompts))
+ for _, prompt := range s.prompts {
prompts = append(prompts, prompt)
}
return prompts
@@ -304,7 +173,7 @@ func (s *Server) handleCallTool(req JSONRPCRequest) JSONRPCResponse {
return s.createErrorResponse(req.ID, InvalidParams, "Invalid params")
}
- tool, exists := s.toolDefinitions[callReq.Name]
+ tool, exists := s.tools[callReq.Name]
if !exists {
return s.createErrorResponse(req.ID, MethodNotFound, "Tool not found")
}
@@ -329,7 +198,7 @@ func (s *Server) handleGetPrompt(req JSONRPCRequest) JSONRPCResponse {
return s.createErrorResponse(req.ID, InvalidParams, "Invalid params")
}
- prompt, exists := s.promptDefinitions[promptReq.Name]
+ prompt, exists := s.prompts[promptReq.Name]
if !exists {
return s.createErrorResponse(req.ID, MethodNotFound, "Prompt not found")
}
@@ -469,4 +338,3 @@ func NewPrompt(name, description string, arguments []PromptArgument, handler Pro
Handler: handler,
}
}
-
pkg/mcp/server_builder.go
@@ -0,0 +1,110 @@
+package mcp
+
+type ServerBuilder struct {
+ name string
+ version string
+ tools []Tool
+ prompts []Prompt
+ resources []Resource
+ roots []Root
+
+ initializeHandler func(InitializeRequest) (InitializeResult, error)
+ shutdownHandler func() error
+}
+
+// NewServerBuilder creates a new server builder
+func NewServerBuilder(name, version string) *ServerBuilder {
+ return &ServerBuilder{
+ name: name,
+ version: version,
+ tools: []Tool{},
+ prompts: []Prompt{},
+ resources: []Resource{},
+ roots: []Root{},
+ }
+}
+
+// AddTool adds a tool to the server
+func (b *ServerBuilder) AddTool(tool Tool) *ServerBuilder {
+ b.tools = append(b.tools, tool)
+ return b
+}
+
+// AddPrompt adds a prompt to the server
+func (b *ServerBuilder) AddPrompt(prompt Prompt) *ServerBuilder {
+ b.prompts = append(b.prompts, prompt)
+ return b
+}
+
+// AddResource adds a resource to the server
+func (b *ServerBuilder) AddResource(resource Resource) *ServerBuilder {
+ b.resources = append(b.resources, resource)
+ return b
+}
+
+// AddRoot adds a root to the server
+func (b *ServerBuilder) AddRoot(root Root) *ServerBuilder {
+ b.roots = append(b.roots, root)
+ return b
+}
+
+// SetInitializeHandler sets the initialize handler
+func (b *ServerBuilder) SetInitializeHandler(handler func(InitializeRequest) (InitializeResult, error)) *ServerBuilder {
+ b.initializeHandler = handler
+ return b
+}
+
+// SetShutdownHandler sets the shutdown handler
+func (b *ServerBuilder) SetShutdownHandler(handler func() error) *ServerBuilder {
+ b.shutdownHandler = handler
+ return b
+}
+
+// Build creates an immutable server with dynamic capabilities
+func (b *ServerBuilder) Build() *Server {
+ // Build dynamic capabilities based on what components are present
+ capabilities := ServerCapabilities{
+ Logging: &LoggingCapability{}, // Always present
+ }
+
+ if len(b.tools) > 0 {
+ capabilities.Tools = &ToolsCapability{}
+ }
+ if len(b.prompts) > 0 {
+ capabilities.Prompts = &PromptsCapability{}
+ }
+ if len(b.resources) > 0 {
+ capabilities.Resources = &ResourcesCapability{}
+ }
+ if len(b.roots) > 0 {
+ capabilities.Roots = &RootsCapability{}
+ }
+
+ server := &Server{
+ name: b.name,
+ version: b.version,
+ capabilities: capabilities,
+ tools: make(map[string]Tool),
+ prompts: make(map[string]Prompt),
+ resourceDefinitions: make(map[string]Resource),
+ rootDefinitions: make(map[string]Root),
+ initializeHandler: b.initializeHandler,
+ shutdownHandler: b.shutdownHandler,
+ }
+
+ // Register all components
+ for _, tool := range b.tools {
+ server.tools[tool.Name] = tool
+ }
+ for _, prompt := range b.prompts {
+ server.prompts[prompt.Name] = prompt
+ }
+ for _, resource := range b.resources {
+ server.resourceDefinitions[resource.URI] = resource
+ }
+ for _, root := range b.roots {
+ server.rootDefinitions[root.URI] = root
+ }
+
+ return server
+}
Makefile
@@ -33,6 +33,12 @@ $(BINDIR)/mcp-%: cmd/%/main.go
test: ## Run all tests
$(GOTEST) -v ./...
+test-smoke: clean build
+ echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}' | ./bin/mcp-filesystem --allowed-directory . | jq '.'
+ echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' | ./bin/mcp-filesystem --allowed-directory . | jq '.'
+ echo '{"jsonrpc": "2.0", "id": 1, "method": "resources/list", "params": {}}' | ./bin/mcp-filesystem --allowed-directory . | jq '.'
+ echo '{"jsonrpc": "2.0", "id": 1, "method": "prompts/list", "params": {}}' | ./bin/mcp-filesystem --allowed-directory . | jq '.'
+
test-coverage: ## Run tests with coverage
$(GOTEST) -cover ./...