mcp-go

module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 29, 2024 License: MIT

README

MCP-Go

A Go implementation of the Model Context Protocol (MCP), enabling seamless integration between LLM applications and external data sources and tools.

About MCP

The Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Learn more at modelcontextprotocol.io and view the specification at spec.modelcontextprotocol.io.

Installation

go get github.com/mark3labs/mcp-go

Creating a Server

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

// CalculationError represents an error during calculation
type CalculationError struct {
	Message string
}

func (e CalculationError) Error() string {
	return e.Message
}

// Calculator implements basic arithmetic operations
type Calculator struct {
	server *server.DefaultServer
}

// NewCalculator creates a new calculator server
func NewCalculator() *Calculator {
	s := server.NewDefaultServer("calculator", "1.0.0")
	calc := &Calculator{server: s}

	// Register calculator tools
	s.HandleCallTool(calc.handleToolCall)
	s.HandleListTools(calc.handleListTools)

	return calc
}

func (c *Calculator) handleListTools(
	ctx context.Context,
	cursor *string,
) (*mcp.ListToolsResult, error) {
	return &mcp.ListToolsResult{
		Tools: []mcp.Tool{
			{
				Name:        "add",
				Description: "Add two numbers",
				InputSchema: mcp.ToolInputSchema{
					Type: "object",
					Properties: map[string]interface{}{
						"a": map[string]interface{}{
							"type":        "number",
							"description": "First number",
						},
						"b": map[string]interface{}{
							"type":        "number",
							"description": "Second number",
						},
					},
				},
			},
			{
				Name:        "subtract",
				Description: "Subtract two numbers",
				InputSchema: mcp.ToolInputSchema{
					Type: "object",
					Properties: map[string]interface{}{
						"a": map[string]interface{}{
							"type":        "number",
							"description": "First number",
						},
						"b": map[string]interface{}{
							"type":        "number",
							"description": "Second number",
						},
					},
				},
			},
			{
				Name:        "multiply",
				Description: "Multiply two numbers",
				InputSchema: mcp.ToolInputSchema{
					Type: "object",
					Properties: map[string]interface{}{
						"a": map[string]interface{}{
							"type":        "number",
							"description": "First number",
						},
						"b": map[string]interface{}{
							"type":        "number",
							"description": "Second number",
						},
					},
				},
			},
			{
				Name:        "divide",
				Description: "Divide two numbers",
				InputSchema: mcp.ToolInputSchema{
					Type: "object",
					Properties: map[string]interface{}{
						"a": map[string]interface{}{
							"type":        "number",
							"description": "First number (dividend)",
						},
						"b": map[string]interface{}{
							"type":        "number",
							"description": "Second number (divisor)",
						},
					},
				},
			},
		},
	}, nil
}

func (c *Calculator) handleToolCall(
	ctx context.Context,
	name string,
	args map[string]interface{},
) (*mcp.CallToolResult, error) {
	// Extract arguments
	a, ok := args["a"].(float64)
	if !ok {
		return nil, &CalculationError{Message: "parameter 'a' must be a number"}
	}
	b, ok := args["b"].(float64)
	if !ok {
		return nil, &CalculationError{Message: "parameter 'b' must be a number"}
	}

	var result float64

	switch name {
	case "add":
		result = a + b
	case "subtract":
		result = a - b
	case "multiply":
		result = a * b
	case "divide":
		if b == 0 {
			return nil, &CalculationError{Message: "division by zero"}
		}
		result = a / b
	default:
		return nil, fmt.Errorf("unknown tool: %s", name)
	}

	// Create response
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("%.2f", result),
			},
		},
	}, nil
}

func (c *Calculator) Serve() error {
	return server.ServeStdio(c.server)
}

func main() {
	calc := NewCalculator()

	if err := calc.Serve(); err != nil {
		log.Printf("Server error: %v\n", err)
		os.Exit(1)
	}
}

Creating a Client

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"
)

func main() {
	// Create a new client instance
	// Using npx to run the filesystem server with /tmp as the only allowed directory
	c, err := client.NewStdioMCPClient(
		"npx",
		"-y",
		"@modelcontextprotocol/server-filesystem",
		"/tmp",
	)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	defer c.Close()

	// Create context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Initialize the client
	fmt.Println("Initializing client...")
	initResult, err := c.Initialize(
		ctx,
		mcp.ClientCapabilities{},
		mcp.Implementation{
			Name:    "example-client",
			Version: "1.0.0",
		},
		"1.0",
	)
	if err != nil {
		log.Fatalf("Failed to initialize: %v", err)
	}
	fmt.Printf(
		"Initialized with server: %s %s\n\n",
		initResult.ServerInfo.Name,
		initResult.ServerInfo.Version,
	)

	// List Tools
	fmt.Println("Listing available tools...")
	tools, err := c.ListTools(ctx, nil)
	if err != nil {
		log.Fatalf("Failed to list tools: %v", err)
	}
	for _, tool := range tools.Tools {
		fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
	}
	fmt.Println()

	// List allowed directories
	fmt.Println("Listing allowed directories...")
	result, err := c.CallTool(ctx, "list_allowed_directories", nil)
	if err != nil {
		log.Fatalf("Failed to list allowed directories: %v", err)
	}
	printToolResult(result)
	fmt.Println()

	// List /tmp
	fmt.Println("Listing /tmp directory...")
	result, err = c.CallTool(ctx, "list_directory", map[string]interface{}{
		"path": "/tmp",
	})
	if err != nil {
		log.Fatalf("Failed to list directory: %v", err)
	}
	printToolResult(result)
	fmt.Println()

	// Create mcp directory
	fmt.Println("Creating /tmp/mcp directory...")
	result, err = c.CallTool(ctx, "create_directory", map[string]interface{}{
		"path": "/tmp/mcp",
	})
	if err != nil {
		log.Fatalf("Failed to create directory: %v", err)
	}
	printToolResult(result)
	fmt.Println()

	// Create hello.txt
	fmt.Println("Creating /tmp/mcp/hello.txt...")
	result, err = c.CallTool(ctx, "write_file", map[string]interface{}{
		"path":    "/tmp/mcp/hello.txt",
		"content": "Hello World",
	})
	if err != nil {
		log.Fatalf("Failed to create file: %v", err)
	}
	printToolResult(result)
	fmt.Println()

	// Verify file contents
	fmt.Println("Reading /tmp/mcp/hello.txt...")
	result, err = c.CallTool(ctx, "read_file", map[string]interface{}{
		"path": "/tmp/mcp/hello.txt",
	})
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}
	printToolResult(result)

	// Get file info
	fmt.Println("Getting info for /tmp/mcp/hello.txt...")
	result, err = c.CallTool(ctx, "get_file_info", map[string]interface{}{
		"path": "/tmp/mcp/hello.txt",
	})
	if err != nil {
		log.Fatalf("Failed to read file: %v", err)
	}
	printToolResult(result)
}

// Helper function to print tool results
func printToolResult(result *mcp.CallToolResult) {
	for _, content := range result.Content {
		if textContent, ok := content.(mcp.TextContent); ok {
			fmt.Println(textContent.Text)
		} else {
			jsonBytes, _ := json.MarshalIndent(content, "", "  ")
			fmt.Println(string(jsonBytes))
		}
	}
}

Contributing

I'm not an expert and this is my first Go library, so contributions are very welcome! Whether it's:

  • Improving the code quality
  • Adding features
  • Fixing bugs
  • Writing documentation
  • Adding examples

Feel free to open issues and PRs. Let's make this library better together.

License

MIT License

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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