MCPP is a comprehensive Go library that implements a Model Context Protocol (MCP) client system. It provides a unified interface for connecting to and managing multiple MCP servers through different transport mechanisms, with built-in session management, error handling, and tool integration.
- Multi-Server Management: Connect to and manage multiple MCP servers simultaneously
- Session Pooling: Automatic session creation, reuse, and cleanup with health monitoring
- Transport Abstraction: Currently supports stdio transport with extensible architecture
- Tool Integration: Seamless integration with LiteLLM for AI model interactions
- Structured Error Handling: Comprehensive error types with context and retry policies
- Configurable Logging: Structured logging with multiple levels and formats
- Health Monitoring: Built-in health checks and connection monitoring
- Flexible Configuration: Support for file-based and programmatic configuration
go get github.com/andrejsstepanovs/mcpppackage main
import (
"context"
"fmt"
"log"
"github.com/andrejsstepanovs/mcpp"
)
func main() {
// Create client from configuration file
client, err := mcpp.NewClientFromFile("config.json", nil)
if err != nil {
log.Fatal(err)
}
// Connect to all configured servers
ctx := context.Background()
if err := client.Connect(ctx); err != nil {
log.Fatal(err)
}
defer client.Disconnect()
// List available tools
tools, err := client.ListTools(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Available tools: %d\n", len(tools))
for _, tool := range tools {
fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
}
// Call a tool
result, err := client.CallTool(ctx, "server1-echo", map[string]any{
"message": "Hello, MCP!",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tool result: %+v\n", result)
}package main
import (
"context"
"fmt"
"log"
"github.com/andrejsstepanovs/mcpp"
)
func main() {
// Create manager from configuration file
manager, err := mcpp.NewManagerFromFile("config.json", nil)
if err != nil {
log.Fatal(err)
}
// Start all servers
ctx := context.Background()
if err := manager.Start(ctx); err != nil {
log.Fatal(err)
}
defer manager.Stop()
// Add a new server at runtime
serverConfig := mcpp.ServerConfig{
Transport: "stdio",
Command: "python",
Args: []string{"my_mcp_server.py"},
}
if err := manager.AddServer("dynamic_server", serverConfig); err != nil {
log.Fatal(err)
}
// List all servers
servers := manager.ListServers()
fmt.Printf("Managed servers: %v\n", servers)
// Call tool on specific server
result, err := manager.CallTool(ctx, "dynamic_server", "my_tool", map[string]any{
"param": "value",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tool result: %+v\n", result)
// Get server statistics
stats := manager.GetServerStats()
for serverName, stat := range stats {
fmt.Printf("Server %s: %d tool calls, %d errors\n",
serverName, stat.ToolCallCount, stat.ErrorCount)
}
}package main
import (
"context"
"log"
"time"
"github.com/andrejsstepanovs/mcpp"
)
func main() {
// Create configuration programmatically
config := &mcpp.Config{
Servers: map[string]mcpp.ServerConfig{
"my_server": {
Transport: "stdio",
Command: "python",
Args: []string{"server.py"},
Env: map[string]string{
"DEBUG": "1",
},
Timeout: func() *time.Duration {
d := 30 * time.Second
return &d
}(),
},
},
Global: mcpp.GlobalConfig{
DefaultTimeout: 30 * time.Second,
MaxSessions: 5,
HealthCheck: true,
},
Logging: mcpp.LoggingConfig{
Level: "info",
Format: "json",
Output: "stdout",
},
}
// Create client with programmatic config
client := mcpp.NewClient(config, nil)
ctx := context.Background()
if err := client.Connect(ctx); err != nil {
log.Fatal(err)
}
defer client.Disconnect()
// Use the client...
}Create a JSON configuration file (e.g., config.json):
{
"servers": {
"my_server": {
"transport": "stdio",
"command": "python",
"args": ["my_mcp_server.py"],
"env": {
"DEBUG": "1"
},
"timeout": "30s",
"disabled": false
},
"another_server": {
"transport": "stdio",
"command": "node",
"args": ["server.js"],
"workingDir": "/path/to/server",
"timeout": "45s"
}
},
"global": {
"defaultTimeout": "30s",
"maxSessions": 10,
"healthCheck": true,
"healthCheckInterval": "30s",
"sessionIdleTimeout": "5m"
},
"logging": {
"level": "info",
"format": "text",
"output": "stderr",
"maxSize": 100,
"maxBackups": 3,
"maxAge": 28
}
}transport: Transport mechanism ("stdio" currently supported)command: Command to execute for the MCP serverargs: Command line arguments (optional)env: Environment variables (optional)timeout: Connection timeout (optional, inherits from global)disabled: Whether to skip this server (optional, default: false)workingDir: Working directory for command execution (optional)maxRetries: Maximum retry attempts (optional)
defaultTimeout: Default timeout for all operationsmaxSessions: Maximum number of concurrent sessionshealthCheck: Enable health checking (default: true)healthCheckInterval: How often to perform health checkssessionIdleTimeout: When to close idle sessions
level: Log level ("debug", "info", "warn", "error")format: Log format ("text" or "json")output: Output destination ("stdout", "stderr", or file path)maxSize: Maximum log file size in MBmaxBackups: Maximum number of backup filesmaxAge: Maximum age of log files in days
MCPP provides structured error handling with detailed context:
// Handle specific error types
if err != nil {
if mcpErr, ok := err.(*mcpp.MCPError); ok {
switch mcpErr.Type {
case mcpp.ErrorTypeConnection:
fmt.Printf("Connection error: %s\n", mcpErr.Message)
case mcpp.ErrorTypeTimeout:
fmt.Printf("Timeout error: %s\n", mcpErr.Message)
case mcpp.ErrorTypeValidation:
fmt.Printf("Validation error: %s\n", mcpErr.Message)
default:
fmt.Printf("Other error: %s\n", mcpErr.Message)
}
// Access error context
if context := mcpErr.Context; context != nil {
fmt.Printf("Error context: %+v\n", context)
}
}
}ErrorTypeConnection: Network or connection-related errorsErrorTypeProtocol: MCP protocol violations or communication errorsErrorTypeConfiguration: Configuration validation or loading errorsErrorTypeTimeout: Operation timeout errorsErrorTypeValidation: Input validation errors
Monitor the health of your MCP connections:
// Check overall client health
if err := client.Health(ctx); err != nil {
fmt.Printf("Health check failed: %v\n", err)
}
// Get detailed statistics
if statsClient, ok := client.(*mcpp.DefaultClient); ok {
stats := statsClient.GetStats()
for serverName, stat := range stats {
fmt.Printf("Server %s: Connected=%v, Calls=%d, Errors=%d\n",
serverName, stat.Connected, stat.ToolCallCount, stat.ErrorCount)
}
}
// Manager health checks
healthErrors := manager.HealthCheck(ctx)
for serverName, err := range healthErrors {
fmt.Printf("Server %s health issue: %v\n", serverName, err)
}Tools are automatically namespaced with their server name:
- Original tool:
echo - Namespaced tool:
my_server-echo
When using the Manager interface, you can call tools directly by server and tool name:
// Client interface (namespaced)
result, err := client.CallTool(ctx, "my_server-echo", args)
// Manager interface (explicit server)
result, err := manager.CallTool(ctx, "my_server", "echo", args)Configure structured logging to monitor your MCP operations:
// Create custom logger
logger := mcpp.NewDefaultLogger()
// Use with client
client := mcpp.NewClient(config, logger)
// Logger will output structured information about:
// - Connection establishment and teardown
// - Tool calls and their duration
// - Error conditions and retries
// - Health check results// Load configuration with validation
config, err := mcpp.LoadConfigFromFileEnhanced("config.json")
if err != nil {
log.Fatal("Configuration error:", err)
}
// Apply defaults before use
config.ApplyDefaults()// Always clean up resources
defer func() {
if err := client.Disconnect(); err != nil {
log.Printf("Cleanup error: %v", err)
}
}()
// Use context for timeouts
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()// Handle errors gracefully with retries
for attempts := 0; attempts < 3; attempts++ {
if err := client.Connect(ctx); err == nil {
break
}
time.Sleep(time.Duration(attempts+1) * time.Second)
}// Regular health checks
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
go func() {
for range ticker.C {
if err := client.Health(ctx); err != nil {
log.Printf("Health check failed: %v", err)
}
}
}()The library includes a comprehensive test server for integration testing:
# Build and run the test server
cd tools
go build -tags testserver -o test-server .
./test-serverThe test server provides various tools for testing different scenarios including error conditions, timeouts, and edge cases.
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
go test ./... - Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
For questions, issues, or contributions, please visit the GitHub repository.