GoMCP

by localrivet

Not rated
GitHub

About

A Go library for building clients and servers using the Model Context Protocol (MCP).

Details

Author
localrivet
Categories
Developer Tools, API

Setup

Install GoMCP in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/localrivet/gomcp

Follow the installation instructions in the repository README, then restart your MCP client.

GoMCP - Go Model Context Protocol Library

✅ Full compliance across all MCP specification versions- SeeCOMPLIANCE.mdfor detailed verification.

GoMCP is a complete Go implementation of the Model Context Protocol (MCP), designed to facilitate seamless interaction between applications and Large Language Models (LLMs). The library supports all specification versions with automatic negotiation and provides a clean, idiomatic API for both clients and servers.

- Overview
-
Key Features
-
API Stability
-
Installation
-
Quickstart

- Client Example
-
Client with Automatic Server Management
-
Server Example
-
Advanced Server Example

- Clients and Servers
-
Tools
-
Resources
-
Prompts
-
Batch Operations
-
Event System
-
Transports
-
Server Management
-
Session Management

The Model Context Protocol (MCP) standardizes communication between applications and LLMs, enabling:

- Tool Calling: Execute actions and functions through LLMs
- Resource Access: Provide structured data to LLMs with workspace context
- Prompt Rendering: Create reusable templates for LLM interactions
- Sampling: Generate text from LLMs with control over parameters
- Session Management: Rich context and workspace root access for enhanced tool capabilities

GoMCP provides an idiomatic Go implementation that handles all the protocol details while offering a clean, developer-friendly API.

- Complete Protocol Implementation: Full support for all MCP specification versions
- Automatic Version Negotiation: Seamless compatibility between clients and servers
- Multiple Transport Options: Support for stdio, HTTP, WebSocket, and Server-Sent Events
- Type-Safe API: Leverages Go's type system for safety and expressiveness
- Server Process Management: Automatically start, manage, and stop external MCP servers
- Server Configuration: Load server definitions from configuration files
- MCP Session Architecture: Comprehensive session management with transport-aware data extraction
- Automated Root Fetching: Automatic workspace root discovery following MCP protocol
- Flexible Architecture: Modular design for easy extension and customization

GoMCP v1.5.0 represents a stable, production-ready release with locked APIs.The library has reached full maturity with a comprehensive feature set and battle-tested implementations.

- Client API: All client methods (CallTool,GetResource,GetPrompt, etc.) arelocked and stable
- Server API: Server registration methods (Tool,Resource,Prompt) and handler patterns arefinalized
- Transport Layer: All transport implementations followstable, locked interfaces
- Event System: Event types and subscription patterns arestandardized and locked
- Server Management: Process lifecycle and configuration management APIs arestable

- Complete MCP Specification Support: Full implementation of all protocol versions (2024-11-05, 2025-03-26, draft)
- Automatic Version Negotiation: Seamless compatibility handling between different specification versions
- Transport Compliance: All transport layers properly implement their respective MCP specifications
- Type Safety: Strong typing throughout ensures API contracts are maintained

- Comprehensive Testing: Extensive test coverage across all major components
- Error Handling: Robust error handling with proper MCP error codes and messages
- Performance: Optimized for production workloads with efficient resource management
- Complete Documentation: Full API documentation and usage examples

With v1.5.0's API lock, future releases will focus on:

- Additive Features: New functionality that extends but doesn't break existing APIs
- Performance Optimizations: Internal improvements that maintain API compatibility
- Enhanced Documentation: Expanded examples and integration guides
- New Transport Options: Additional transport implementations using the stable transport interface

Commitment: The v1.5.0 APIs are locked in and will not change. Any future enhancements will be additive and maintain full backward compatibility. GoMCP is ready for enterprise production deployments.

package main import ( "log" "github.com/localrivet/gomcp/client" ) func main() { // Create a new client with stdio transport c, err := client.NewClient("stdio:///", client.WithStdio(), client.WithProtocolVersion("2025-03-26"), client.WithProtocolNegotiation(true), ) if err != nil { log.Fatalf("Failed to create client: %v", err) } defer c.Close() // Call a tool on the MCP server result, err := c.CallTool("say_hello", map[string]interface{}{ "name": "World", }) if err != nil { log.Fatalf("Tool call failed: %v", err) } log.Printf("Result: %v", result) }

Note: This example uses stdio transport, which means the client expects to communicate with an MCP server via stdin/stdout. For a complete working example that automatically manages the server process, see the "Client with Automatic Server Management" section below.

What this does:This example demonstrates GoMCP's powerful automatic server management feature. Instead of manually starting and stopping MCP server processes, the client can automatically:

- Launch server processeson demand using system commands
- Establish connectionsto those servers via stdio/pipes
- Environment variable injectionfor configuration (API keys, etc.)
- Automatic cleanup- server processes are terminated when the client closes
- Process lifecycle management- handles server startup, health checks, and shutdown

Why this matters:This pattern eliminates the operational complexity of managing MCP servers. You can distribute a single binary that automatically spins up the required MCP servers, making deployment and integration much simpler. It's especially useful for:

- Development environments- automatically start dependent services
- CI/CD pipelines- spin up servers for testing without manual setup
- Desktop applications- embed MCP servers without requiring separate installation
- Microservice architectures- manage server dependencies declaratively

package main import ( "log" "github.com/localrivet/gomcp/client" ) func main() { // Define server configuration config := client.ServerConfig{ MCPServers: map[string]client.ServerDefinition{ "govibe": { Command: "govibe", Args: []string{}, Env: map[string]string{ "ANTHROPIC_API_KEY": "${ANTHROPIC_API_KEY}", }, }, }, } // Create a client with automatic server management c, err := client.NewClient("my-client", client.WithServers(config, "govibe"), ) if err != nil { log.Fatalf("Failed to create client: %v", err) } defer c.Close() // Automatically stops the server process // Call a tool on the managed server result, err := c.CallTool("add_task", map[string]interface{}{ "prompt": "Create a login page with authentication", }) if err != nil { log.Fatalf("Tool call failed: %v", err) } log.Printf("Task created: %v", result) // Add project roots for the server to access err = c.AddRoot("/path/to/project", "project-root") if err != nil { log.Fatalf("Failed to add root: %v", err) } // Get a resource that might use the project context resource, err := c.GetResource("/project/files/src/main.go") if err != nil { log.Fatalf("Resource request failed: %v", err) } log.Printf("Resource content: %v", resource) }

- The${ANTHROPIC_API_KEY}syntax automatically injects environment variables from the current process
- Server processes communicate viastdio pipesfor secure, high-performance IPC
- The client waits for server initialization before accepting requests
- Graceful shutdownensures servers are properly terminated, preventing orphaned processes
- Multiple servers can be managed simultaneously with different configurations

package main import ( "fmt" "log/slog" "os" "github.com/localrivet/gomcp/server" ) func main() { // Create a logger logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) // Create a new server srv := server.NewServer("example-server", server.WithLogger(logger), ).AsStdio() // Register a tool with inline struct srv.Tool("say_hello", "Greet someone", func(ctx server.Context, args struct { Name string json:"name" }) (interface{}, error) { return map[string]interface{}{ "message": fmt.Sprintf("Hello, %s!", args.Name), }, nil }) // Start the server if err := srv.Run(); err != nil { log.Fatalf("Failed to run server: %v", err) } }
package main import ( "fmt" "log/slog" "os" "path/filepath" "strings" "github.com/localrivet/gomcp/server" "github.com/localrivet/gomcp/transport/sse" ) func main() { // Create a logger logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) // Create a new server with comprehensive functionality srv := server.NewServer("advanced-server", server.WithLogger(logger), ).AsStdio() // Register multiple tools with different parameter types srv.Tool("calculator", "Perform mathematical calculations", func(ctx server.Context, args struct { Operation string json:"operation" A float64 json:"a" B float64 json:"b" }) (interface{}, error) { switch args.Operation { case "add": return map[string]interface{}{"result": args.A + args.B}, nil case "multiply": return map[string]interface{}{"result": args.A  args.B}, nil case "divide": if args.B == 0 { return nil, fmt.Errorf("division by zero") } return map[string]interface{}{"result": args.A / args.B}, nil default: return nil, fmt.Errorf("unsupported operation: %s", args.Operation) } }) srv.Tool("create_file", "Create a file with content", func(ctx server.Context, args struct { Path string json:"path" Content string json:"content" }) (interface{}, error) { // In a real implementation, you'd validate paths and permissions return map[string]interface{}{ "message": fmt.Sprintf("File created at %s with %d bytes", args.Path, len(args.Content)), "path": args.Path, "size": len(args.Content), }, nil }) // Register resources with different patterns srv.Resource("/config", "Get server configuration", func(ctx server.Context, args struct{}) (interface{}, error) { return map[string]interface{}{ "version": "1.0.0", "environment": "development", "features": []string{"tools", "resources", "prompts"}, }, nil }) // Templated resource for file access srv.Resource("/files/{path}", "Access file system resources", func(ctx server.Context, args struct { Path string path:"path" }) (interface{}, error) { // Extract file extension for content type detection ext := strings.ToLower(filepath.Ext(args.Path)) return map[string]interface{}{ "path": args.Path, "extension": ext, "type": getFileType(ext), "content": fmt.Sprintf("Mock content for file: %s", args.Path), }, nil }) // User profile resource with parameters srv.Resource("/users/{id}", "Get user profile information", func(ctx server.Context, args struct { ID string path:"id" IncludePosts bool json:"include_posts" }) (interface{}, error) { user := map[string]interface{}{ "id": args.ID, "name": fmt.Sprintf("User %s", args.ID), "email": fmt.Sprintf("user%s@example.com", args.ID), "active": true, } if args.IncludePosts { user["posts"] = []map[string]interface{}{ {"id": 1, "title": "Hello World", "content": "First post"}, {"id": 2, "title": "Second Post", "content": "Another post"}, } } return user, nil }) // Register prompts for different use cases srv.Prompt("code_review", "Provide code review assistance", server.User("Please review this {{language}} code for best practices, potential bugs, and improvements:\n\n
{{language}}\n{{code}}\n`"), server.Assistant("I'll analyze your {{language}} code and provide detailed feedback on best practices, potential issues, and suggested improvements."), ) srv.Prompt("email_template", "Generate professional email content", server.Assistant("I'll help you create a professional email."), server.User("Write a {{tone}} email to {{recipient}} about {{subject}}. Include these key points: {{key_points}}"), ) srv.Prompt("documentation", "Generate technical documentation", server.User("Create documentation for this {{type}} with the following details:\n\nName: {{name}}\nPurpose: {{purpose}}\nParameters: {{parameters}}\nExample: {{example}}"), server.Assistant("I'll create comprehensive technical documentation following best practices for clarity and completeness."), ) // Start the server if err := srv.Run(); err != nil { logger.Error("Failed to run server", "error", err) os.Exit(1) } } // Helper function to determine file type from extension func getFileType(ext string) string { switch ext { case ".go": return "go_source" case ".js", ".ts": return "javascript" case ".py": return "python" case ".md": return "markdown" case ".json": return "json" case ".yaml", ".yml": return "yaml" default: return "text" } }

- client.Client: Interface for communicating with MCP servers. Handles protocol negotiation, request/response management, and server lifecycle.
- server.Server: Core component for implementing MCP servers. Provides registration methods for tools, resources, and prompts with automatic schema generation.

Tools expose functions that LLMs can call to perform actions. GoMCP supports:

- Type-safe parametersusing inline struct definitions
- Automatic schema generationfrom Go struct tags
- Error handlingwith proper MCP error responses
- Cancellation supportfor long-running operations


// Simple tool with inline struct srv.Tool("say_hello", "Greet someone", func(ctx
server.Context, args struct { Name string json:"name" }) (interface{}, error) { return map[string]interface{}{ "message": fmt.Sprintf("Hello, %s!", args.Name), }, nil }) // Tool with complex parameters srv.Tool("calculate", "Perform calculations", func(ctx server.Context, args struct { Operation string json:"operation" A float64 json:"a" B float64 json:"b" }) (interface{}, error) { switch args.Operation { case "add": return map[string]interface{}{"result": args.A + args.B}, nil case "multiply": return map[string]interface{}{"result": args.A args.B}, nil default: return nil, fmt.Errorf("unsupported operation: %s", args.Operation) } })


// Call a simple tool result, err := client.CallTool("say_hello", map[string]interface{}{ "name": "Alice", }) if err != nil { log.Fatalf("Tool call failed: %v", err) } fmt.Printf("Result: %v\n", result) // Call a tool with complex parameters calcResult, err := client.CallTool("calculate", map[string]interface{}{ "operation": "add", "a": 10.5, "b": 20.3, }) if err != nil { log.Fatalf("Calculation failed: %v", err) } fmt.Printf("Calculation result: %v\n", calcResult)

Resources provide structured data to LLMs in various formats:

- Static resourceswith fixed URIs (e.g.,/config,/status)
- Templated resourceswith path parameters (e.g.,/files/{path},/users/{id})
- Dynamic resourcesthat can accept additional parameters from request bodies
- Multiple content typesincluding text, images, links, and binary data


// Static resource srv.Resource("/config", "Get configuration", func(ctx
server.Context, args struct{}) (interface{}, error) { return map[string]interface{}{ "version": "1.0.0", "environment": "production", }, nil }) // Templated resource with path parameters srv.Resource("/files/{path}", "Access files", func(ctx server.Context, args struct { Path string path:"path" }) (interface{}, error) { return map[string]interface{}{ "path": args.Path, "content": fmt.Sprintf("Content of %s", args.Path), }, nil }) // Resource with additional parameters srv.Resource("/users/{id}", "Get user info", func(ctx server.Context, args struct { ID string path:"id" IncludePosts bool json:"include_posts" }) (interface{}, error) { user := map[string]interface{}{ "id": args.ID, "name": fmt.Sprintf("User %s", args.ID), } if args.IncludePosts { user["posts"] = []string{"Post 1", "Post 2"} } return user, nil })


// Get a static resource config, err := client.GetResource("/config") if err != nil { log.Fatalf("Failed to get config: %v", err) } fmt.Printf("Config: %v\n", config) // Get a templated resource fileContent, err := client.GetResource("/files/src/main.go") if err != nil { log.Fatalf("Failed to get file: %v", err) } fmt.Printf("File content: %v\n", fileContent) // Get a resource with additional parameters user, err := client.GetResource("/users/123", client.WithResourceParams(map[string]interface{}{ "include_posts": true, })) if err != nil { log.Fatalf("Failed to get user: %v", err) } fmt.Printf("User: %v\n", user)

Prompts define reusable message templates for LLM interactions:

- Template variablesusing{{variable}}syntax
- Multiple message types(User, Assistant, System)
- Role-based conversationsfor complex interaction patterns
- Argument validationfor template parameters


// Simple prompt template srv.Prompt("email_template", "Generate emails", server.User("Write a {{tone}} email to {{recipient}} about {{subject}}"), ) // Multi-message conversation prompt srv.Prompt("code_review", "Code review assistant", server.Assistant("I'll help you review code for best practices and bugs."), server.User("Please review this {{language}} code:\n\n`{{language}}\n{{code}}\n`"), ) // Complex prompt with multiple variables srv.Prompt("documentation", "Generate docs", server.User("Create {{type}} documentation for:\nName: {{name}}\nPurpose: {{purpose}}\nExample: {{example}}"), server.Assistant("I'll create comprehensive {{type}} documentation."), )


// Get a simple prompt emailPrompt, err := client.GetPrompt("email_template", map[string]interface{}{ "tone": "professional", "recipient": "team", "subject": "project update", }) if err != nil { log.Fatalf("Failed to get prompt: %v", err) } fmt.Printf("Email prompt: %v\n", emailPrompt) // Get a complex prompt with multiple variables docPrompt, err := client.GetPrompt("documentation", map[string]interface{}{ "type": "API", "name": "UserService", "purpose": "Manage user accounts", "example": "userService.CreateUser()", }) if err != nil { log.Fatalf("Failed to get doc prompt: %v", err) } fmt.Printf("Documentation prompt: %v\n", docPrompt)

GoMCP supports JSON-RPC batch operations for improved performance:

- Reduced network round-tripsby sending multiple requests at once
- Maintained request orderingin responses
- Mixed request types(tools, resources, prompts) in a single batch
- Partial failure handlingwith individual response errors
- Fluent builder interfacefor easy batch construction


// Create a batch request batch := client.NewBatch(). CallTool("say_hello", map[string]interface{}{"name": "Alice"}). GetResource("/config"). GetPrompt("email_template", map[string]interface{}{ "tone": "professional", "recipient": "team", "subject": "project update", }) // Execute the batch results, err := c.ExecuteBatch(batch) if err != nil { log.Fatalf("Batch execution failed: %v", err) } // Process results for i, result := range results { if result.Error != nil { log.Printf("Request %d failed: %v", i, result.Error) } else { log.Printf("Request %d result: %v", i, result.Result) } }

GoMCP provides a comprehensive event system that allows you to monitor and react to various activities within your MCP server or client. The event system uses a type-safe, channel-based architecture for maximum performance and reliability.

- Real-time monitoringof server operations and client interactions
- Type-safe event handlingwith strongly-typed event structs
- Channel-based architecturefor high-performance, non-blocking event processing
- Comprehensive coverageof all server lifecycle and operation events
- Easy integrationwith logging, metrics, and monitoring systems

GoMCP emits events for all major operations and lifecycle changes:

- server.initialized- Server has started and is ready to accept requests
- server.shutdown- Server is shutting down

- client.connected- A client connected to the server
- client.disconnected- A client disconnected from the server
- client.initializing- Client is starting to connect (client-side)
- client.initialized- Client successfully connected (client-side)
- client.error- Client operation failed (client-side)

- tool.registered- A tool was registered with the server
- resource.registered- A resource was registered with the server

- tool.executed- A tool was executed (successful operation)
- resource.accessed- A resource was accessed
- prompt.executed- A prompt was executed
- request.failed- Any MCP request failed

All events include comprehensive metadata and follow consistent patterns:

Server Eventsinclude server name, timestamps, and operational metricsClient Eventsinclude session IDs, protocol versions, and connection details
Operation Eventsinclude method names, actual JSON payloads, and execution resultsError Eventsinclude detailed error information and context for debugging

For a complete working example with all event types, seeexamples/events_integration/main.go.

GoMCP supports multiple transport layers for different use cases:


import ( "fmt" "log/slog" "os" "path/filepath" "strings" "github.com/localrivet/gomcp/server" "github.com/localrivet/gomcp/transport/sse" ) // Stdio transport (for CLI tools and LLM integration) srv := server.NewServer("my-server").AsStdio() // HTTP transport srv := server.NewServer("my-server").AsHTTP(":8080") // WebSocket transport srv := server.NewServer("my-server").AsWebSocket(":8080", "/mcp") // SSE transport with single MCP endpoint (Streamable HTTP per 2025-03-26 spec) // WARNING: Current implementation uses deprecated pattern - needs update srv := server.NewServer("my-server").AsSSE(":8080") // Custom MCP endpoint path srv := server.NewServer("my-server").AsSSE(":8080", sse.SSE.WithMCPEndpoint("/mcp"), ) // Unix socket transport srv := server.NewServer("my-server").AsUnix("/tmp/mcp.sock") // UDP transport srv := server.NewServer("my-server").AsUDP(":8080") // MQTT transport srv := server.NewServer("my-server").AsMQTT("mqtt://localhost:1883", "mcp/requests", "mcp/responses") // NATS transport srv := server.NewServer("my-server").AsNATS("nats://localhost:4222", "mcp.requests", "mcp.responses") // gRPC transport srv := server.NewServer("my-server").AsGRPC(":9090")


// Connect via stdio (for connecting to CLI tools) client, err := client.NewClient("my-client", client.WithStdioTransport("./my-mcp-server"), ) // Connect via HTTP client, err := client.NewClient("my-client", client.WithHTTPTransport("http://localhost:8080"), ) // Connect via WebSocket client, err := client.NewClient("my-client", client.WithWebSocketTransport("ws://localhost:8080/mcp"), ) // Connect via SSE (hybrid: SSE for receiving + HTTP POST for sending) // The client connects to the base URL; endpoints are discovered automatically client, err := client.NewClient("my-client", client.WithSSE("http://localhost:8080"), ) // Connect via Unix socket client, err := client.NewClient("my-client", client.WithUnixTransport("/tmp/mcp.sock"), ) // Connect via UDP client, err := client.NewClient("my-client", client.WithUDPTransport("localhost:8080"), ) // Connect via MQTT client, err := client.NewClient("my-client", client.WithMQTTTransport("mqtt://localhost:1883", "mcp/responses", "mcp/requests"), ) // Connect via NATS client, err := client.NewClient("my-client", client.WithNATSTransport("nats://localhost:4222", "mcp.responses", "mcp.requests"), ) // Connect via gRPC client, err := client.NewClient("my-client", client.WithGRPCTransport("localhost:9090"), )

Note on SSE Transport:The SSE transport correctly implements the MCP "Streamable HTTP" specification (2025-03-26):
- Single MCP Endpoint: Uses one endpoint that handles both GET (for SSE) and POST (for messages)
- Client Requests: Sent via HTTP POST to the MCP endpoint
- Server Responses: Can be immediate JSON responses or SSE streams
- Server-Initiated Messages: Sent via GET-initiated SSE streams
- Session Management: Optional session IDs viaMcp-Session-Idheader
- Backward Compatibility: Supports legacy 2024-11-05 pattern with automatic fallback

GoMCP provides automatic management of external MCP server processes:

- Automatic process startupfrom configuration files or programmatic definitions
- Environment variable injectionwith${VAR}syntax
- Graceful shutdownand cleanup when clients disconnect
- Multi-server supportfor complex architectures
- Health monitoringand connection management


// Define server configuration config := client.ServerConfig{ MCPServers: map
[string]client.ServerDefinition{ "file-server": { Command: "python", Args: []string{"-m", "mcp_server_files"}, Env: map[string]string{ "FILES_ROOT": "/workspace", "LOG_LEVEL": "info", }, }, "database-server": { Command: "./db-mcp-server", Args: []string{"--config", "config.json"}, Env: map[string]string{ "DATABASE_URL": "${DATABASE_URL}", "API_KEY": "${DB_API_KEY}", }, WorkingDirectory: "/opt/db-server", }, "ai-tools": { Command: "ai-mcp-tools", Args: []string{"--model", "gpt-4"}, Env: map[string]string{ "OPENAI_API_KEY": "${OPENAI_API_KEY}", "ANTHROPIC_API_KEY": "${ANTHROPIC_API_KEY}", }, }, }, } // Create client with automatic server management client, err := client.NewClient("orchestrator", client.WithServers(config, "file-server"), // Start file-server ) if err != nil { log.Fatalf("Failed to create client: %v", err) } defer client.Close() // Automatically stops managed servers // Start additional servers on demand err = client.StartServer("database-server") if err != nil { log.Fatalf("Failed to start database server: %v", err) } // Use multiple servers fileResult, err := client.CallTool("list_files", map[string]interface{}{ "path": "/workspace/src", }) // Switch to database server context or use a different client instance dbClient, err := client.NewClient("db-client", client.WithServers(config, "database-server"), ) defer dbClient.Close() queryResult, err := dbClient.CallTool("execute_query", map[string]interface{}{ "sql": "SELECT FROM users LIMIT 10", }) // Load configuration from file configFromFile, err := client.LoadServerConfig("mcp-servers.json") if err != nil { log.Fatalf("Failed to load config: %v", err) } // Create client with all servers from config multiClient, err := client.NewClient("multi-server", client.WithServersFromConfig(configFromFile, "file-server", "ai-tools"), ) defer multiClient.Close() // Health monitoring status := client.GetServerStatus("file-server") if !status.Running { log.Printf("File server is not running: %v", status.Error) err := client.RestartServer("file-server") if err != nil { log.Fatalf("Failed to restart server: %v", err) } }

Configuration File Example (mcp-servers.json):


{ "mcpServers": { "file-server": { "command": "python", "args": ["-m", "mcp_server_files"], "env": { "FILES_ROOT": "/workspace", "LOG_LEVEL": "info" } }, "database-server": { "command": "./db-mcp-server", "args": ["--config", "config.json"], "env": { "DATABASE_URL": "${DATABASE_URL}", "API_KEY": "${DB_API_KEY}" }, "workingDirectory": "/opt/db-server" } } }

When using server registries with multiple MCP servers, it's important to follow proper cleanup patterns to avoid race conditions:

✅ Correct Pattern: Use registry.StopAll()


registry := client.NewServerRegistry( client.WithRegistryLogger(logger), ) // IMPORTANT: Use defer registry.StopAll() for proper cleanup defer func() { if err := registry.StopAll(); err != nil { log.Printf("Warning: Error during server shutdown: %v", err) } }() err := registry.LoadConfig(configFile) // ... use the servers ...

❌ Incorrect Pattern: Manual client.Close() before registry.StopAll()


// DON'T DO THIS - creates race condition defer func() { client.Close() // ❌ Kills connection/process immediately registry.StopAll() // ❌ Tries to wait for already-killed process }()

- client.Close()immediately terminates the MCP connection and may kill the server process
- registry.StopAll()then tries to gracefully shut down processes that are already dead
- This results in "Failed to wait for server process error='signal: killed'" errors

- Always useregistry.StopAll()- it handles all client cleanup internally
- Use defer blocksfor guaranteed cleanup on program exit
- Don't mix manual client.Close() with registry.StopAll()
- Handle cleanup errors gracefully- processes may have already exited

GoMCP v1.5.5 introduces comprehensive session management with the MCP Session Architecture, providing rich context and automated workspace discovery:


// Tool handlers receive session context automatically srv.Tool("analyze_project", "Analyze project structure", func(ctx
server.Context, args struct { AnalysisType string json:"analysis_type" }) (interface{}, error) { // Access session environment (from transport headers/process env) env := ctx.Session.Env() apiKey := env["ANTHROPIC_API_KEY"] // Access workspace roots (from clientInfo + automated roots/list) roots := ctx.Session.Roots() primaryRoot := "" if len(roots) > 0 { primaryRoot = roots[0] } // Access client capabilities caps := ctx.Session.Capabilities() return map[string]interface{}{ "primary_workspace": primaryRoot, "all_roots": roots, "has_api_access": apiKey != "", "supports_sampling": caps.Sampling.Supported, "analysis_type": args.AnalysisType, }, nil })

GoMCP automatically extracts session data from the transport layer per MCP specification:

- stdio: Environment from process environment variables
- HTTP: Environment from request headers (X-Env-pattern)
- WebSocket: Environment from connection headers
- SSE: Environment from initial request headers

The server automatically detects when clients support therootscapability and:
- Initial Extraction: Extracts workspace roots fromclientInfo.rootsduring initialization
- Capability Detection: Detects if client advertisesrootscapability
- Automated Fetching: Sendsroots/listrequests afternotifications/initialized
- Response Handling: Processesroots/listresponses with proper request tracking
- Context Integration: Makes roots available viactx.Session.Roots()

Full compliance across all three MCP protocol versions:

- 2024-11-05: Basic root extraction and capability detection
- 2025-03-26: Enhanced session management with audio support detection
- draft: Latest features with full session architecture

EnhancedClientInfoprovides comprehensive session data:


type ClientInfo struct { Name string json:"name" Version string json:"version" SamplingSupported bool json:"sampling_supported,omitempty" SamplingCaps
SamplingCapabilities json:"sampling_caps,omitempty" ProtocolVersion string json:"protocol_version,omitempty" Env map[string]string json:"env,omitempty" // NEW: Environment data from transport Roots []string json:"roots,omitempty" // NEW: Workspace roots from init + roots/list }
```

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.