Skip to content

andrejsstepanovs/mcpp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MCPP - Model Context Protocol Client Library

Go Version License

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.

Features

  • 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

Installation

go get github.com/andrejsstepanovs/mcpp

Quick Start

Basic Client Usage

package 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)
}

Manager Usage (Advanced Multi-Server Management)

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)
    }
}

Programmatic Configuration

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...
}

Configuration

Configuration File Format

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
  }
}

Configuration Options

Server Configuration

  • transport: Transport mechanism ("stdio" currently supported)
  • command: Command to execute for the MCP server
  • args: 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)

Global Configuration

  • defaultTimeout: Default timeout for all operations
  • maxSessions: Maximum number of concurrent sessions
  • healthCheck: Enable health checking (default: true)
  • healthCheckInterval: How often to perform health checks
  • sessionIdleTimeout: When to close idle sessions

Logging Configuration

  • 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 MB
  • maxBackups: Maximum number of backup files
  • maxAge: Maximum age of log files in days

Error Handling

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)
        }
    }
}

Error Types

  • ErrorTypeConnection: Network or connection-related errors
  • ErrorTypeProtocol: MCP protocol violations or communication errors
  • ErrorTypeConfiguration: Configuration validation or loading errors
  • ErrorTypeTimeout: Operation timeout errors
  • ErrorTypeValidation: Input validation errors

Health Monitoring

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)
}

Tool Name Format

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)

Logging

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

Best Practices

1. Configuration Management

// Load configuration with validation
config, err := mcpp.LoadConfigFromFileEnhanced("config.json")
if err != nil {
    log.Fatal("Configuration error:", err)
}

// Apply defaults before use
config.ApplyDefaults()

2. Resource Management

// 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()

3. Error Handling

// 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)
}

4. Health Monitoring

// 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)
        }
    }
}()

Testing

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-server

The test server provides various tools for testing different scenarios including error conditions, timeouts, and edge cases.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass: go test ./...
  5. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For questions, issues, or contributions, please visit the GitHub repository.

About

!!! WIP !!! - Work in progress - MCP server (stdio) implementation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors