GoMCP

Una biblioteca en Go para construir clientes y servidores utilizando el Protocolo de Contexto de Modelo (MCP).

Documentación

GoMCP - Biblioteca Go del Protocolo de Contexto de Modelo

Go Reference Go Report Card

Cumplimiento de la Especificación MCP

Draft Spec: 100% 2024-11-05 Spec: 100% 2025-03-26 Spec: 100%

✅ Cumplimiento total en todas las versiones de la especificación MCP - Consulte COMPLIANCE.md para verificación detallada.

GoMCP es una implementación completa en Go del Protocolo de Contexto de Modelo (MCP), diseñada para facilitar la interacción fluida entre aplicaciones y Modelos de Lenguaje de Gran Escala (LLMs). La biblioteca admite todas las versiones de la especificación con negociación automática y proporciona una API limpia e idiomática tanto para clientes como para servidores.

Tabla de Contenidos

Descripción General

El Protocolo de Contexto de Modelo (MCP) estandariza la comunicación entre aplicaciones y LLMs, permitiendo:

  • Llamada de Herramientas: Ejecutar acciones y funciones a través de LLMs
  • Acceso a Recursos: Proporcionar datos estructurados a LLMs con contexto del espacio de trabajo
  • Renderizado de Prompts: Crear plantillas reutilizables para interacciones con LLMs
  • Muestreo: Generar texto desde LLMs con control sobre los parámetros
  • Gestión de Sesiones: Acceso a contexto enriquecido y raíces del espacio de trabajo para capacidades mejoradas de herramientas

GoMCP proporciona una implementación idiomática en Go que maneja todos los detalles del protocolo mientras ofrece una API limpia y amigable para desarrolladores.

Características Clave

  • Implementación Completa del Protocolo: Soporte total para todas las versiones de la especificación MCP
  • Negociación Automática de Versiones: Compatibilidad fluida entre clientes y servidores
  • Múltiples Opciones de Transporte: Soporte para stdio, HTTP, WebSocket y Eventos Enviados por el Servidor
  • API Segura por Tipos: Aprovecha el sistema de tipos de Go para seguridad y expresividad
  • Gestión de Procesos de Servidor: Iniciar, gestionar y detener automáticamente servidores MCP externos
  • Configuración de Servidores: Cargar definiciones de servidores desde archivos de configuración
  • Arquitectura de Sesiones MCP: Gestión integral de sesiones con extracción de datos consciente del transporte
  • Obtención Automática de Raíces: Descubrimiento automático de raíces del espacio de trabajo siguiendo el protocolo MCP
  • Arquitectura Flexible: Diseño modular para fácil extensión y personalización

Estabilidad de la API

GoMCP v1.5.0 representa una versión estable, lista para producción con APIs bloqueadas. La biblioteca ha alcanzado plena madurez con un conjunto integral de características e implementaciones probadas en batalla.

🔒 Garantía de Bloqueo de API (v1.5.0+)

  • API de Cliente: Todos los métodos de cliente (CallTool, GetResource, GetPrompt, etc.) están bloqueados y estables
  • API de Servidor: Los métodos de registro de servidor (Tool, Resource, Prompt) y los patrones de manejadores están finalizados
  • Capa de Transporte: Todas las implementaciones de transporte siguen interfaces estables y bloqueadas
  • Sistema de Eventos: Los tipos de eventos y patrones de suscripción están estandarizados y bloqueados
  • Gestión de Servidores: Las APIs de ciclo de vida de procesos y gestión de configuración son estables

✅ Cumplimiento Total del Protocolo

  • Soporte Completo de la Especificación MCP: Implementación total de todas las versiones del protocolo (2024-11-05, 2025-03-26, borrador)
  • Negociación Automática de Versiones: Manejo fluido de compatibilidad entre diferentes versiones de la especificación
  • Cumplimiento del Transporte: Todas las capas de transporte implementan correctamente sus respectivas especificaciones MCP
  • Seguridad de Tipos: Tipado fuerte en todo el sistema asegura que los contratos de API se mantengan

✅ Listo para Producción

  • Pruebas Integrales: Amplia cobertura de pruebas en todos los componentes principales
  • Manejo de Errores: Manejo robusto de errores con códigos y mensajes de error MCP apropiados
  • Rendimiento: Optimizado para cargas de trabajo de producción con gestión eficiente de recursos
  • Documentación Completa: Documentación completa de API y ejemplos de uso

🚀 Desarrollo Futuro

Con el bloqueo de API de v1.5.0, las versiones futuras se centrarán en:

  • Características Aditivas: Nueva funcionalidad que extiende pero no rompe las APIs existentes
  • Optimizaciones de Rendimiento: Mejoras internas que mantienen la compatibilidad de API
  • Documentación Mejorada: Ejemplos ampliados y guías de integración
  • Nuevas Opciones de Transporte: Implementaciones adicionales de transporte usando la interfaz de transporte estable

Compromiso: Las APIs de v1.5.0 están bloqueadas y no cambiarán. Cualquier mejora futura será aditiva y mantendrá compatibilidad total hacia atrás. GoMCP está listo para despliegues empresariales de producción.

Instalación

go get github.com/localrivet/gomcp

Inicio Rápido

Ejemplo de Cliente

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

Nota: Este ejemplo utiliza transporte stdio, lo que significa que el cliente espera comunicarse con un servidor MCP a través de stdin/stdout. Para un ejemplo completo que gestiona automáticamente el proceso del servidor, consulte la sección "Cliente con Gestión Automática de Servidores" a continuación.

Cliente con Gestión Automática de Servidores

Qué hace esto: Este ejemplo demuestra la potente característica de gestión automática de servidores de GoMCP. En lugar de iniciar y detener manualmente los procesos del servidor MCP, el cliente puede automáticamente:

  • Iniciar procesos de servidor bajo demanda usando comandos del sistema
  • Establecer conexiones a esos servidores a través de stdio/tuberías
  • Inyección de variables de entorno para configuración (claves API, etc.)
  • Limpieza automática - los procesos del servidor se terminan cuando el cliente se cierra
  • Gestión del ciclo de vida del proceso - maneja el inicio del servidor, verificaciones de salud y apagado

Por qué esto importa: Este patrón elimina la complejidad operativa de gestionar servidores MCP. Puede distribuir un solo binario que automáticamente active los servidores MCP requeridos, haciendo el despliegue e integración mucho más simples. Es especialmente útil para:

  • Entornos de desarrollo - iniciar automáticamente servicios dependientes
  • Tuberías CI/CD - activar servidores para pruebas sin configuración manual
  • Aplicaciones de escritorio - incrustar servidores MCP sin requerir instalación separada
  • Arquitecturas de microservicios - gestionar dependencias de servidores declarativamente
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)
}

Detalles Clave de Implementación:

  • La sintaxis ${ANTHROPIC_API_KEY} inyecta automáticamente variables de entorno del proceso actual
  • Los procesos del servidor se comunican a través de tuberías stdio para IPC seguro y de alto rendimiento
  • El cliente espera la inicialización del servidor antes de aceptar solicitudes
  • Apagado elegante asegura que los servidores se terminen correctamente, previniendo procesos huérfanos
  • Múltiples servidores pueden gestionarse simultáneamente con diferentes configuraciones

Ejemplo de Servidor

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

Ejemplo Avanzado de Servidor

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

Conceptos Fundamentales

Clientes y Servidores

  • client.Client: Interfaz para comunicarse con servidores MCP. Maneja la negociación del protocolo, la gestión de solicitudes/respuestas y el ciclo de vida del servidor.
  • server.Server: Componente central para implementar servidores MCP. Proporciona métodos de registro para herramientas, recursos y prompts con generación automática de esquemas.

Herramientas

Las herramientas exponen funciones que los LLMs pueden llamar para realizar acciones. GoMCP soporta:

  • Parámetros seguros por tipos usando definiciones de estructuras en línea
  • Generación automática de esquemas a partir de etiquetas de estructuras Go
  • Manejo de errores con respuestas de error MCP apropiadas
  • Soporte de cancelación para operaciones de larga duración

Lado del Servidor (Implementación):

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

Lado del Cliente (Uso):

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

Recursos

Los recursos proporcionan datos estructurados a los LLMs en varios formatos:

  • Recursos estáticos con URIs fijos (por ejemplo, /config, /status)
  • Recursos con plantillas con parámetros de ruta (por ejemplo, /files/{path*}, /users/{id})
  • Recursos dinámicos que pueden aceptar parámetros adicionales de los cuerpos de solicitud
  • Múltiples tipos de contenido incluyendo texto, imágenes, enlaces y datos binarios

Lado del Servidor (Implementación):

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

Lado del Cliente (Uso):

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

Los prompts definen plantillas de mensajes reutilizables para interacciones con LLMs:

  • Variables de plantilla usando sintaxis {{variable}}
  • Múltiples tipos de mensajes (Usuario, Asistente, Sistema)
  • Conversaciones basadas en roles para patrones de interacción complejos
  • Validación de argumentos para parámetros de plantilla

Lado del Servidor (Implementación):

// 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."),
)

Lado del Cliente (Uso):

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

Operaciones por Lotes

GoMCP soporta operaciones por lotes JSON-RPC para rendimiento mejorado:

  • Reducción de viajes de ida y vuelta de red enviando múltiples solicitudes a la vez
  • Mantenimiento del orden de solicitudes en las respuestas
  • Tipos de solicitudes mixtas (herramientas, recursos, prompts) en un solo lote
  • Manejo de fallos parciales con errores de respuesta individuales
  • Interfaz fluida de construcción para fácil construcción de lotes

Lado del Cliente (Uso):

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

Sistema de Eventos

GoMCP proporciona un sistema integral de eventos que le permite monitorear y reaccionar a diversas actividades dentro de su servidor o cliente MCP. El sistema de eventos utiliza una arquitectura segura por tipos basada en canales para máximo rendimiento y confiabilidad.

Beneficios Clave:

  • Monitoreo en tiempo real de operaciones del servidor e interacciones del cliente
  • Manejo de eventos seguro por tipos con estructuras de eventos fuertemente tipadas
  • Arquitectura basada en canales para procesamiento de eventos de alto rendimiento y no bloqueante
  • Cobertura integral de todos los eventos del ciclo de vida del servidor y operaciones
  • Integración fácil con sistemas de registro, métricas y monitoreo

Tipos de Eventos Disponibles

GoMCP emite eventos para todas las operaciones principales y cambios de ciclo de vida:

Eventos del Ciclo de Vida del Servidor:

  • server.initialized - El servidor ha iniciado y está listo para aceptar solicitudes
  • server.shutdown - El servidor se está apagando

Eventos de Conexión del Cliente:

  • client.connected - Un cliente se conectó al servidor
  • client.disconnected - Un cliente se desconectó del servidor
  • client.initializing - El cliente está comenzando a conectarse (lado del cliente)
  • client.initialized - El cliente se conectó exitosamente (lado del cliente)
  • client.error - La operación del cliente falló (lado del cliente)

Eventos de Registro:

  • tool.registered - Una herramienta fue registrada con el servidor
  • resource.registered - Un recurso fue registrado con el servidor

Eventos de Operación:

  • tool.executed - Una herramienta fue ejecutada (operación exitosa)
  • resource.accessed - Un recurso fue accedido
  • prompt.executed - Un prompt fue ejecutado
  • request.failed - Cualquier solicitud MCP falló

Uso de Eventos en el Lado del Servidor

Configuración de Suscripciones a Eventos:

package main

import (
    "context"
    "log/slog"
    "os"
    "github.com/localrivet/gomcp/events"
    "github.com/localrivet/gomcp/server"
)

func main() {
    // Create server
    srv := server.NewServer("my-server")
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

    // Subscribe to server lifecycle events
    events.Subscribe[events.ServerInitializedEvent](srv.Events(), events.TopicServerInitialized,
        func(ctx context.Context, evt events.ServerInitializedEvent) error {
            logger.Info("🚀 Server initialized",
                "name", evt.ServerName,
                "version", evt.ProtocolVersion,
                "tools", evt.ToolCount,
                "resources", evt.ResourceCount)
            return nil
        })

    events.Subscribe[events.ServerShutdownEvent](srv.Events(), events.TopicServerShutdown,
        func(ctx context.Context, evt events.ServerShutdownEvent) error {
            logger.Info("🛑 Server shutting down",
                "name", evt.ServerName,
                "graceful", evt.GracefulExit,
                "reason", evt.Reason)
            return nil
        })

    // Subscribe to client connection events
    events.Subscribe[events.ClientConnectedEvent](srv.Events(), events.TopicClientConnected,
        func(ctx context.Context, evt events.ClientConnectedEvent) error {
            logger.Info("🔌 Client connected",
                "sessionId", evt.SessionID,
                "clientName", evt.ClientInfo.Name,
                "clientVersion", evt.ClientInfo.Version,
                "protocolVersion", evt.ProtocolVersion)
            return nil
        })

    events.Subscribe[events.ClientDisconnectedEvent](srv.Events(), events.TopicClientDisconnected,
        func(ctx context.Context, evt events.ClientDisconnectedEvent) error {
            logger.Info("🔌 Client disconnected",
                "sessionId", evt.SessionID,
                "duration", time.Since(evt.ConnectedAt))
            return nil
        })

    // Subscribe to tool events
    events.Subscribe[events.ToolRegisteredEvent](srv.Events(), events.TopicToolRegistered,
        func(ctx context.Context, evt events.ToolRegisteredEvent) error {
            logger.Info("🔧 Tool registered",
                "name", evt.ToolName,
                "description", evt.Description)
            return nil
        })

    events.Subscribe[events.ToolExecutedEvent](srv.Events(), events.TopicToolExecuted,
        func(ctx context.Context, evt events.ToolExecutedEvent) error {
            logger.Info("⚡ Tool executed",
                "method", evt.Method,
                "success", len(evt.ResponseJSON) > 0)
            return nil
        })

    // Subscribe to resource events
    events.Subscribe[events.ResourceRegisteredEvent](srv.Events(), events.TopicResourceRegistered,
        func(ctx context.Context, evt events.ResourceRegisteredEvent) error {
            logger.Info("📄 Resource registered",
                "uri", evt.URI,
                "name", evt.Name,
                "mimeType", evt.MimeType)
            return nil
        })

    events.Subscribe[events.ResourceAccessedEvent](srv.Events(), events.TopicResourceAccessed,
        func(ctx context.Context, evt events.ResourceAccessedEvent) error {
            logger.Info("📖 Resource accessed",
                "uri", evt.URI,
                "success", evt.Success,
                "responseSize", evt.ResponseSize)
            return nil
        })

    // Subscribe to error events  
    events.Subscribe[events.RequestFailedEvent](srv.Events(), events.TopicRequestFailed,
        func(ctx context.Context, evt events.RequestFailedEvent) error {
            logger.Error("❌ Request failed",
                "method", evt.Method,
                "error", evt.Error)
            return nil
        })

    // Register tools and resources
    srv.Tool("greet", "Say hello", func(ctx *server.Context, args struct {
        Name string `json:"name"`
    }) (interface{}, error) {
        return fmt.Sprintf("Hello, %s!", args.Name), nil
    })

    srv.Resource("/status", "Server status", func(ctx *server.Context, args *struct{}) (interface{}, error) {
        return map[string]interface{}{
            "status": "healthy",
            "uptime": time.Since(startTime),
        }, nil
    })

    // Start server
    srv.AsStdio().Run()
}

Uso de Eventos en el Lado del Cliente

Monitoreo de Operaciones del Cliente:

package main

import (
    "context"
    "log/slog" 
    "github.com/localrivet/gomcp/client"
    "github.com/localrivet/gomcp/events"
)

func main() {
    // Create client
    c, err := client.NewClient("my-client")
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }
    defer c.Close()

    logger := slog.Default()

    // Subscribe to client lifecycle events
    events.Subscribe[events.ClientInitializingEvent](c.Events(), events.TopicClientInitializing,
        func(ctx context.Context, evt events.ClientInitializingEvent) error {
            logger.Info("🔄 Client connecting", "url", evt.URL)
            return nil
        })

    events.Subscribe[events.ClientInitializedEvent](c.Events(), events.TopicClientInitialized,
        func(ctx context.Context, evt events.ClientInitializedEvent) error {
            logger.Info("✅ Client connected", "url", evt.URL)
            return nil
        })

    events.Subscribe[events.ClientErrorEvent](c.Events(), events.TopicClientError,
        func(ctx context.Context, evt events.ClientErrorEvent) error {
            logger.Error("❌ Client error", "error", evt.Error)
            return nil
        })

    events.Subscribe[events.ClientDisconnectedEvent](c.Events(), events.TopicClientDisconnected,
        func(ctx context.Context, evt events.ClientDisconnectedEvent) error {
            logger.Info("🔌 Client disconnected", "url", evt.URL)
            return nil
        })

    // Subscribe to operation events
    events.Subscribe[events.ToolExecutedEvent](c.Events(), events.TopicToolExecuted,
        func(ctx context.Context, evt events.ToolExecutedEvent) error {
            logger.Info("⚡ Tool called", "method", evt.Method)
            return nil
        })

    events.Subscribe[events.RequestFailedEvent](c.Events(), events.TopicRequestFailed,
        func(ctx context.Context, evt events.RequestFailedEvent) error {
            logger.Error("❌ Request failed", "method", evt.Method, "error", evt.Error)
            return nil
        })

    // Connect to server and perform operations
    err = c.ConnectStdio("./my-mcp-server")
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }

    // Tool calls and other operations will now emit events
    result, err := c.CallTool("greet", map[string]interface{}{"name": "World"})
    if err != nil {
        log.Fatalf("Tool call failed: %v", err)
    }
    
    fmt.Printf("Result: %v\n", result)
}

Patrones de Integración del Sistema de Eventos

Recopilación de Métricas:

type MetricsCollector struct {
    toolCallCount     int64
    errorCount        int64
    connectedClients  int64
}

func (m *MetricsCollector) SetupEventSubscriptions(srv server.Server) {
    // Track tool executions
    events.Subscribe[events.ToolExecutedEvent](srv.Events(), events.TopicToolExecuted,
        func(ctx context.Context, evt events.ToolExecutedEvent) error {
            atomic.AddInt64(&m.toolCallCount, 1)
            return nil
        })

    // Track errors
    events.Subscribe[events.RequestFailedEvent](srv.Events(), events.TopicRequestFailed,
        func(ctx context.Context, evt events.RequestFailedEvent) error {
            atomic.AddInt64(&m.errorCount, 1)
            return nil
        })

    // Track connections
    events.Subscribe[events.ClientConnectedEvent](srv.Events(), events.TopicClientConnected,
        func(ctx context.Context, evt events.ClientConnectedEvent) error {
            atomic.AddInt64(&m.connectedClients, 1)
            return nil
        })

    events.Subscribe[events.ClientDisconnectedEvent](srv.Events(), events.TopicClientDisconnected,
        func(ctx context.Context, evt events.ClientDisconnectedEvent) error {
            atomic.AddInt64(&m.connectedClients, -1)
            return nil
        })
}

Registro de Auditoría:

type AuditLogger struct {
    logger *slog.Logger
}

func (a *AuditLogger) SetupAuditSubscriptions(srv server.Server) {
    // Log all tool executions for security audit
    events.Subscribe[events.ToolExecutedEvent](srv.Events(), events.TopicToolExecuted,
        func(ctx context.Context, evt events.ToolExecutedEvent) error {
            a.logger.Info("AUDIT: Tool executed",
                "method", evt.Method,
                "requestJSON", evt.RequestJSON,
                "responseJSON", evt.ResponseJSON,
                "timestamp", time.Now())
            return nil
        })

    // Log all failed requests
    events.Subscribe[events.RequestFailedEvent](srv.Events(), events.TopicRequestFailed,
        func(ctx context.Context, evt events.RequestFailedEvent) error {
            a.logger.Warn("AUDIT: Request failed",
                "method", evt.Method,
                "error", evt.Error,
                "requestJSON", evt.RequestJSON,
                "timestamp", time.Now())
            return nil
        })
}

Monitoreo de Salud:

type HealthMonitor struct {
    lastToolExecution time.Time
    errorRate         float64
    isHealthy         bool
}

func (h *HealthMonitor) SetupHealthSubscriptions(srv server.Server) {
    events.Subscribe[events.ToolExecutedEvent](srv.Events(), events.TopicToolExecuted,
        func(ctx context.Context, evt events.ToolExecutedEvent) error {
            h.lastToolExecution = time.Now()
            h.updateHealthStatus()
            return nil
        })

    events.Subscribe[events.RequestFailedEvent](srv.Events(), events.TopicRequestFailed,
        func(ctx context.Context, evt events.RequestFailedEvent) error {
            h.calculateErrorRate()
            h.updateHealthStatus()
            return nil
        })
}

func (h *HealthMonitor) updateHealthStatus() {
    h.isHealthy = time.Since(h.lastToolExecution) < 5*time.Minute && h.errorRate < 0.1
}

Referencia de Estructura de Eventos

Todos los eventos incluyen metadatos integrales y siguen patrones consistentes:

Eventos del Servidor incluyen nombre del servidor, marcas de tiempo y métricas operativas Eventos del Cliente incluyen IDs de sesión, versiones de protocolo y detalles de conexión Eventos de Operación incluyen nombres de métodos, cargas útiles JSON reales y resultados de ejecución Eventos de Error incluyen información detallada de errores y contexto para depuración

Para un ejemplo completo de trabajo con todos los tipos de eventos, consulte examples/events_integration/main.go.

Transportes

GoMCP admite múltiples capas de transporte para diferentes casos de uso:

  • stdio: Herramientas CLI e integración directa con LLM
  • WebSocket: Comunicación bidireccional para aplicaciones web
  • Server-Sent Events (SSE): Patrón híbrido con SSE para servidor a cliente y HTTP POST para cliente a servidor
  • HTTP: Interfaces RESTful simples
  • Unix Socket: Comunicación interproceso de alto rendimiento
  • UDP: Comunicación de baja sobrecarga y alto rendimiento
  • MQTT: Mensajería de publicación/suscripción para aplicaciones IoT
  • NATS: Mensajería nativa de la nube y de alto rendimiento
  • gRPC: Comunicación servicio a servicio con tipado fuerte

Lado del servidor (Implementación):

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

Lado del cliente (Uso):

// 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"),
)

Nota sobre el transporte SSE: El transporte SSE implementa correctamente la especificación MCP "Streamable HTTP" (2025-03-26):

  1. Endpoint MCP único: Utiliza un solo endpoint que maneja tanto GET (para SSE) como POST (para mensajes)
  2. Solicitudes del cliente: Se envían mediante HTTP POST al endpoint MCP
  3. Respuestas del servidor: Pueden ser respuestas JSON inmediatas o flujos SSE
  4. Mensajes iniciados por el servidor: Se envían mediante flujos SSE iniciados por GET
  5. Gestión de sesiones: IDs de sesión opcionales mediante el encabezado Mcp-Session-Id
  6. Compatibilidad hacia atrás: Admite el patrón heredado 2024-11-05 con respaldo automático

Gestión de servidores

GoMCP proporciona gestión automática de procesos de servidores MCP externos:

  • Inicio automático de procesos desde archivos de configuración o definiciones programáticas
  • Inyección de variables de entorno con sintaxis ${VAR}
  • Apagado ordenado y limpieza cuando los clientes se desconectan
  • Soporte multi-servidor para arquitecturas complejas
  • Monitoreo de salud y gestión de conexiones

Lado del cliente (Uso):

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

Ejemplo de archivo de configuración (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"
    }
  }
}

Patrones de limpieza adecuados

Al usar registros de servidores con múltiples servidores MCP, es importante seguir patrones de limpieza adecuados para evitar condiciones de carrera:

✅ Patrón correcto: Usar 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 ...

❌ Patrón incorrecto: client.Close() manual antes de 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
}()

Por qué esto crea una condición de carrera:

  1. client.Close() termina inmediatamente la conexión MCP y puede matar el proceso del servidor
  2. registry.StopAll() luego intenta apagar ordenadamente procesos que ya están muertos
  3. Esto resulta en errores "Failed to wait for server process error='signal: killed'"

Mejores prácticas

  • Siempre use registry.StopAll() - maneja toda la limpieza de clientes internamente
  • Use bloques defer para garantizar la limpieza al salir del programa
  • No mezcle client.Close() manual con registry.StopAll()
  • Maneje los errores de limpieza con elegancia - los procesos pueden haber terminado ya

Gestión de sesiones

GoMCP v1.5.5 introduce una gestión integral de sesiones con la Arquitectura de Sesiones MCP, proporcionando contexto enriquecido y descubrimiento automatizado del espacio de trabajo:

Acceso a sesiones en el lado del servidor

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

Datos de sesión conscientes del transporte

GoMCP extrae automáticamente los datos de sesión de la capa de transporte según la especificación MCP:

  • stdio: Entorno de las variables de entorno del proceso
  • HTTP: Entorno de los encabezados de solicitud (patrón X-Env-*)
  • WebSocket: Entorno de los encabezados de conexión
  • SSE: Entorno de los encabezados de solicitud inicial

Descubrimiento automatizado de raíces del espacio de trabajo

El servidor detecta automáticamente cuando los clientes admiten la capacidad roots y:

  1. Extracción inicial: Extrae las raíces del espacio de trabajo de clientInfo.roots durante la inicialización
  2. Detección de capacidades: Detecta si el cliente anuncia la capacidad roots
  3. Obtención automatizada: Envía solicitudes roots/list después de notifications/initialized
  4. Manejo de respuestas: Procesa las respuestas roots/list con seguimiento adecuado de solicitudes
  5. Integración de contexto: Hace que las raíces estén disponibles mediante ctx.Session.Roots()

Cumplimiento del protocolo MCP

Cumplimiento total en las tres versiones del protocolo MCP:

  • 2024-11-05: Extracción básica de raíces y detección de capacidades
  • 2025-03-26: Gestión de sesiones mejorada con detección de soporte de audio
  • draft: Últimas características con arquitectura de sesiones completa

Estructura ClientInfo

El ClientInfo mejorado proporciona datos de sesión integrales:

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
}

Métodos de conveniencia de sesión

La interfaz ClientSession proporciona acceso fácil a los datos de sesión:

// In tool handlers
func MyTool(ctx *server.Context, args MyArgs) (interface{}, error) {
    session := ctx.Session
    
    // Get environment variables (from transport)
    env := session.Env()
    dbUrl := env["DATABASE_URL"]
    
    // Get workspace roots (from init + automated roots/list)
    roots := session.Roots()
    
    // Get client capabilities  
    caps := session.Capabilities()
    supportsSampling := caps.Sampling.Supported
    supportsAudio := caps.Audio.Supported
    
    // Use session data in tool logic...
}

Beneficios

  • Configuración cero: Extracción automática de datos de sesión sin configuración manual
  • Cumplimiento MCP: Sigue la especificación oficial de MCP para el manejo de sesiones
  • Independiente del transporte: Funciona de manera consistente en todos los tipos de transporte
  • Compatibilidad hacia atrás: Sin cambios disruptivos para los manejadores de herramientas existentes

Ejemplos

El directorio examples/ contiene ejemplos completos que demuestran varias características:

  • examples/minimal/: Ejemplos básicos de cliente y servidor
  • examples/sampling/: Ejemplos de generación de texto mediante la API de muestreo
  • examples/server_config/: Ejemplos de gestión y configuración de servidores
  • examples/server/: Varios patrones de implementación de servidores

Documentación

  • GoDoc: Documentación de referencia de la API
  • docs/: Documentación y guías adicionales
    • docs/examples/: Guías detalladas de características
    • docs/getting-started/: Guías de inicio rápido
    • docs/api-reference/: Documentación detallada de la API

Contribuciones

¡Las contribuciones son bienvenidas! No dude en enviar un Pull Request.

Licencia

Este proyecto está licenciado bajo la Licencia MIT - consulte el archivo LICENSE para más detalles.