gomcp

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 30, 2025 License: MIT Imports: 0 Imported by: 1

README

GoMCP - Go Model Context Protocol Library

Go Reference

gomcp provides a Go implementation of the Model Context Protocol (MCP), enabling communication between language models/agents and external tools or resources via a standardized protocol.

This library facilitates building MCP clients (applications that consume tools/resources) and MCP servers (applications that provide tools/resources). Communication primarily occurs over standard input/output using newline-delimited JSON messages conforming to the JSON-RPC 2.0 specification, although other transports (like SSE) are supported.

Current Status: Alpha - Compliant with MCP Specification v2025-03-26

The core library implements the features defined in the MCP Specification version 2025-03-26:

  • Transport Agnostic Core: Server (server/) and Client (client/) logic is separated from the transport layer.
  • Transports: Implementations for Stdio (transport/stdio/), SSE (transport/sse/), and WebSocket (transport/websocket/) are provided.
  • Protocol Structures: Defines Go structs for all specified MCP methods, notifications, and content types (protocol/).
  • Initialization: Full client/server initialization sequence, including capability exchange.
  • Tooling: tools/list, tools/call methods and handlers.
  • Resources: resources/list, resources/read, resources/subscribe, resources/unsubscribe methods and handlers.
  • Prompts: prompts/list, prompts/get methods and handlers.
  • Logging: logging/set_level method and notifications/message infrastructure.
  • Sampling: sampling/create_message method.
  • Roots: roots/list method and client-side root management.
  • Ping: ping method.
  • Cancellation: $/cancelled notification handling with context.Context integration.
  • Progress: $/progress notification infrastructure and _meta.progressToken support.
  • Notifications: Dynamic triggering for list_changed (tools, resources, prompts, roots) and resources/changed notifications based on library actions and subscriptions.

(Note: While the library provides the mechanisms, the specific logic within server-side handlers like handleReadResource, handleGetPrompt, handleLoggingSetLevel, and triggering NotifyResourceUpdated is application-dependent.)

Installation

go get github.com/localrivet/gomcp

Basic Usage

The core logic resides in the server, client, and protocol packages.

(Note: The usage examples below are simplified. See the examples/ directory for more complete implementations.)

Implementing an MCP Server (using Stdio)
package main

import (
	"context"
	"log"
	"os"
	"os/signal" // For graceful shutdown

	"github.com/localrivet/gomcp/protocol"
	"github.com/localrivet/gomcp/server"
	"github.com/localrivet/gomcp/transport/stdio"
	"github.com/localrivet/gomcp/types" // For logger
)

// Example tool handler
func myToolHandler(ctx context.Context, progressToken *protocol.ProgressToken, arguments map[string]interface{}) (content []protocol.Content, isError bool) {
	log.Printf("Executing myTool with args: %v", arguments)
	// ... tool logic ...
	return []protocol.Content{protocol.TextContent{Type: "text", Text: "Tool executed!"}}, false
}

// Simple server loop for stdio
func runServerLoop(ctx context.Context, srv *server.Server, transport types.Transport) error {
	// Stdio typically represents a single "session"
	session := server.NewStdioSession("stdio-session") // Use helper if available, or mock
	if err := srv.RegisterSession(session); err != nil {
		return fmt.Errorf("failed to register session: %w", err)
	}
	defer srv.UnregisterSession(session.SessionID())

	log.Println("Server listening on stdio...")
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			rawMsg, err := transport.ReceiveWithContext(ctx) // Use context-aware receive
			if err != nil {
				// Handle EOF, pipe closed, context canceled as clean exit
				if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "pipe closed") || errors.Is(err, context.Canceled) {
					log.Printf("Input closed or context cancelled.")
					return nil
				}
				return fmt.Errorf("transport receive error: %w", err)
			}

			response := srv.HandleMessage(ctx, session.SessionID(), rawMsg)

			// For stdio, HandleMessage might return response directly OR send via session.
			// This example assumes direct return or ignores session send for simplicity.
			// A real implementation might need the mockSession pattern from tests if session.SendResponse is used.
			if response != nil {
				respBytes, err := json.Marshal(response)
				if err != nil {
					log.Printf("ERROR: server failed to marshal response: %v", err)
					continue
				}
				if err := transport.Send(respBytes); err != nil {
					// Handle EOF/pipe closed during send
					if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "pipe closed") {
						log.Printf("Output closed during send.")
						return nil
					}
					return fmt.Errorf("transport send error: %w", err)
				}
			}
		}
	}
}


func main() {
	log.SetOutput(os.Stderr)
	log.SetFlags(log.Ltime | log.Lshortfile)
	log.Println("Starting My MCP Server...")

	// Create server core
	srv := server.NewServer("MyGoMCPServer", server.ServerOptions{
		// Logger: provide custom logger if needed
	})

	// Register tools
	myTool := protocol.Tool{
		Name:        "my_tool",
		Description: "A simple example tool",
		InputSchema: protocol.ToolInputSchema{Type: "object"}, // Define schema as needed
	}
	err := srv.RegisterTool(myTool, myToolHandler)
	if err != nil {
		log.Fatalf("Failed to register tool: %v", err)
	}

	// Create stdio transport
	transport := stdio.NewStdioTransport()

	// Run the server's message handling loop
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()
	err = runServerLoop(ctx, srv, transport) // Pass context, server, transport
	if err != nil && !errors.Is(err, context.Canceled) {
		log.Fatalf("Server loop exited with error: %v", err)
	}

	log.Println("Server finished.")
}

// Note: Need a simple StdioSession implementation for runServerLoop
type stdioSession struct { id string }
func NewStdioSession(id string) *stdioSession { return &stdioSession{id: id} }
func (s *stdioSession) SessionID() string { return s.id }
func (s *stdioSession) SendNotification(notification protocol.JSONRPCNotification) error { return fmt.Errorf("stdio transport does not support server-to-client notifications") }
func (s *stdioSession) SendResponse(response protocol.JSONRPCResponse) error { return fmt.Errorf("stdio transport does not support async server-to-client responses via session") }
func (s *stdioSession) Close() error { return nil }
func (s *stdioSession) Initialize() {}
func (s *stdioSession) Initialized() bool { return true } // Assume initialized for stdio simplicity
var _ server.ClientSession = (*stdioSession)(nil)

// Need these imports for the example:
import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
)

Implementing an MCP Client (using SSE)
package main

import (
	"context"
	"log"
	"os"
	"time"

	"github.com/localrivet/gomcp/client"   // Use client package
	"github.com/localrivet/gomcp/protocol" // Use protocol package
)

func main() {
	log.SetOutput(os.Stderr)
	log.SetFlags(log.Ltime | log.Lshortfile)
	log.Println("Starting My MCP Client...")

	// Create client instance, providing server URL
	clt, err := client.NewClient("MyGoMCPClient", client.ClientOptions{
		ServerBaseURL: "http://127.0.0.1:8080", // Adjust if server runs elsewhere
		// Logger: provide custom logger if needed
	})
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	// Connect and perform initialization (use context for timeout/cancellation)
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	err = clt.Connect(ctx)
	if err != nil {
		log.Fatalf("Client failed to connect: %v", err)
	}
	defer clt.Close() // Ensure connection is closed eventually

	serverInfo := clt.ServerInfo() // Get server info after connection
	log.Printf("Client connected successfully to server: %s (Version: %s)", serverInfo.Name, serverInfo.Version)

	// Example: List tools
	listParams := protocol.ListToolsRequestParams{}
	toolsResult, err := clt.ListTools(ctx, listParams) // Pass context
	if err != nil {
		log.Printf("Error listing tools: %v", err)
	} else {
		log.Printf("Available tools: %d", len(toolsResult.Tools))
		for _, tool := range toolsResult.Tools {
			log.Printf("  - %s: %s", tool.Name, tool.Description)
		}
	}

	// Example: Call a tool (assuming 'my_tool' exists)
	callParams := protocol.CallToolParams{
		Name:      "my_tool",
		Arguments: map[string]interface{}{"input": "hello"},
	}
	callResult, err := clt.CallTool(ctx, callParams, nil) // Pass context, nil progress token
	if err != nil {
		log.Printf("Error calling tool 'my_tool': %v", err)
	} else {
		log.Printf("Tool 'my_tool' result: %+v", callResult)
	}

	// Ping is handled via standard request/response, no special client method needed

	log.Println("Client finished.")
}

Examples

The examples/ directory contains various client/server pairs demonstrating specific features and transports. Each example is a self-contained Go module.

Running Examples:

Most examples follow a similar pattern. To run an example:

  1. Navigate to the example's directory (e.g., cd examples/basic/server).
  2. Run the server using go run ..
  3. In another terminal, navigate to the corresponding client directory (e.g., cd examples/basic/client).
  4. Run the client using go run ..

Example Categories:

  • examples/basic/: Demonstrates simple stdio communication.
  • examples/http/: Shows integration with various Go HTTP frameworks/routers (Chi, Echo, Fiber, Gin, Go-Zero, Gorilla/Mux, HttpRouter, Beego, Iris, Net/HTTP) using the SSE transport. Run the server from examples/http/<framework>/server/ and use a generic SSE client (like the one in examples/cmd/gomcp-client configured for SSE) or a browser-based client.
  • examples/websocket/: Demonstrates the WebSocket transport. Run the server from examples/websocket/server/ and use a generic WebSocket client (like examples/cmd/gomcp-client configured for WebSocket).
  • examples/configuration/: Shows how to load server configuration from JSON, YAML, or TOML files. Run the specific server (e.g., cd examples/configuration/json/server && go run .) which loads the corresponding config file (e.g., examples/configuration/json/config.json).
  • examples/auth/: Example demonstrating authentication concepts (details TBD).
  • examples/billing/: Example demonstrating billing/quota concepts (details TBD).
  • examples/rate-limit/: Example demonstrating rate limiting (details TBD).
  • examples/kitchen-sink/: A more complex example combining multiple features (details TBD).
  • examples/cmd/: Contains generic command-line client and server implementations that can be configured for different transports.

(Check the specific README within each example directory for more detailed instructions if available.)

Documentation

More detailed documentation can be found in the GitHub Pages site (powered by the /docs directory). (Needs update)

Go package documentation is available via:

  • pkg.go.dev
  • Running godoc -http=:6060 locally and navigating to github.com/localrivet/gomcp.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License.

Documentation

Overview

Package gomcp provides the core implementation for the Model Context Protocol (MCP) in Go. It defines message structures, transport mechanisms (currently stdio), and basic client/server logic for establishing connections via the MCP handshake.

DEPRECATED: This root package is deprecated. Use the subpackages (types, protocol, transport, server, client) instead. This file will be removed in a future version.

Directories

Path Synopsis
Package client provides the MCP client implementation.
Package client provides the MCP client implementation.
Package protocol defines the structures and constants for the Model Context Protocol (MCP).
Package protocol defines the structures and constants for the Model Context Protocol (MCP).
Package server provides the MCP server implementation.
Package server provides the MCP server implementation.
transport
sse
Package sse provides MCP server implementation over Server-Sent Events (SSE) using a hybrid approach (SSE for server->client, HTTP POST for client->server).
Package sse provides MCP server implementation over Server-Sent Events (SSE) using a hybrid approach (SSE for server->client, HTTP POST for client->server).
stdio
Package stdio provides a Transport implementation that uses standard input/output.
Package stdio provides a Transport implementation that uses standard input/output.
tcp
Package tcp provides a types.Transport implementation using TCP sockets.
Package tcp provides a types.Transport implementation using TCP sockets.
websocket
Package websocket provides a types.Transport implementation using WebSockets.
Package websocket provides a types.Transport implementation using WebSockets.
Package types defines core interfaces and common types used across the GoMCP library.
Package types defines core interfaces and common types used across the GoMCP library.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL