mcp

package
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2025 License: MIT Imports: 15 Imported by: 0

README

MCP (Model Context Protocol) Integration

This package provides Model Context Protocol (MCP) support for Genie, allowing it to consume external MCP servers and their tools seamlessly alongside native Genie tools.

Overview

The MCP integration enables Genie to:

  • Read and parse .mcp.json configuration files (compatible with Claude Code format)
  • Act as an MCP client to consume external MCP servers
  • Seamlessly integrate MCP tools with Genie's existing tool system
  • Support all MCP transport types (stdio, SSE, HTTP)

Architecture

MCP Tool Integration Flow

MCP tools work identically to internal tools through Genie's unified tool interface:

1. Tool Interface Implementation

Both internal and MCP tools implement the same Tool interface:

type Tool interface {
    Declaration() *ai.FunctionDeclaration  // Schema for the LLM
    Handler() ai.HandlerFunc               // Execution function
    FormatOutput(result map[string]interface{}) string
}
2. MCP Tool Handler Function

Our MCPTool implements Handler() just like internal tools:

func (t *MCPTool) Handler() ai.HandlerFunc {
    return func(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) {
        // This function bridges Genie's tool system to MCP protocol
        result, err := t.client.CallTool(ctx, t.mcpTool.Name, params)
        if err != nil {
            return nil, err
        }
        
        // Convert MCP response to Genie format
        return map[string]interface{}{
            "content":  contentMaps,
            "isError": result.IsError,
        }, nil
    }
}
3. Integration with Prompt System

When a prompt is loaded, both internal and MCP tools are processed identically in the prompt loader:

// Add tools to prompt
for _, tool := range toolsList {  // toolsList contains BOTH internal and MCP tools
    declaration := tool.Declaration()
    prompt.Functions = append(prompt.Functions, declaration)    // LLM sees the schema
    
    // Wrap handler with events
    originalHandler := tool.Handler()                           // Gets the handler function
    wrappedHandler := l.wrapHandlerWithEvents(declaration.Name, originalHandler)
    prompt.Handlers[declaration.Name] = wrappedHandler         // LLM can call this
}
4. LLM Execution

When the LLM wants to call a tool:

  1. LLM sees the tool in prompt.Functions (same for internal and MCP)
  2. LLM calls the tool by name with parameters
  3. Genie looks up the handler in prompt.Handlers[toolName]
  4. Handler executes:
    • Internal tool: Directly executes the function
    • MCP tool: Sends JSON-RPC request to MCP server, gets response, converts format
5. The Magic Bridge

The MCPTool.Handler() acts as a bridge that:

  • Takes Genie's standard ai.HandlerFunc signature
  • Converts parameters to MCP JSON-RPC format
  • Sends request to external MCP server
  • Receives MCP response
  • Converts response back to Genie's expected format

From the LLM's perspective, there's no difference between internal and MCP tools! They all have the same schema format and handler signature.

Example in Action:
LLM: "I want to echo 'hello'"
Genie: Looks up "echo" in prompt.Handlers
Handler: MCPTool.Handler() function
       → Sends {"method": "tools/call", "params": {"name": "echo", "arguments": {"text": "hello"}}}
       → External MCP server processes request
       → Returns {"content": [{"type": "text", "text": "Echo: hello"}], "isError": false}
       → MCPTool converts to Genie format and returns
LLM: Receives result in standard Genie format

MCP Specification Compliance

This implementation is 100% compliant with the MCP 2024-11-05 specification:

JSON-RPC 2.0 Compliance
  • ✅ All requests include jsonrpc: "2.0"
  • ✅ All requests have non-null id fields
  • ✅ All responses include jsonrpc: "2.0" and matching id
Initialize Protocol
  • Client Request Format:
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "initialize",
      "params": {
        "protocolVersion": "2024-11-05",
        "capabilities": {},
        "clientInfo": {"name": "genie", "version": "1.0.0"}
      }
    }
    
Tools List & Call
  • Tools List: Proper tools/list request/response format
  • Tools Call: Proper tools/call request/response format
  • Content Format: Spec-compliant content arrays with type and text fields
  • Error Handling: Proper isError field always present

Configuration

.mcp.json Format

Place a .mcp.json file in your project root with the following format:

{
  "mcpServers": {
    "server-name": {
      "command": "/path/to/server",
      "args": ["--arg1", "value1"],
      "env": {
        "ENV_VAR": "value"
      }
    },
    "sse-server": {
      "type": "sse",
      "url": "https://example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${API_KEY}"
      }
    }
  }
}
Environment Variable Support

Full support for environment variable expansion using ${VAR:-default} syntax.

Transport Types
  • stdio: Default transport using command execution
  • sse: Server-Sent Events transport
  • http: HTTP transport

Key Components

Files
  • config.go - Configuration parsing and environment variable expansion
  • protocol.go - MCP protocol types and JSON-RPC 2.0 implementation
  • transport.go - Transport layer abstraction (stdio/SSE/HTTP)
  • client.go - MCP client implementation and tool adapter
  • factory.go - Client factory for dependency injection
Testing
  • config_test.go - Configuration parsing tests
  • integration_test.go - Full client-server integration tests
  • test_server.go - Simple MCP server for testing
  • test_server_test.go - Server protocol compliance tests
  • spec_compliance_test.go - Comprehensive MCP specification compliance tests

Usage

Automatic Discovery

When Genie starts, it automatically:

  1. Looks for .mcp.json files in the project root
  2. Connects to configured MCP servers
  3. Discovers available tools
  4. Registers them in the tool registry
  5. Makes them available to the LLM
Manual Integration
// Create MCP client
client, err := mcp.NewMCPClientFromConfig()
if err != nil {
    return err
}

// Connect to servers
ctx := context.Background()
if err := client.ConnectToServers(ctx); err != nil {
    return err
}

// Get tools
tools := client.GetTools()

Testing

Run the comprehensive test suite:

go test ./pkg/mcp -v

This includes:

  • Configuration parsing tests
  • Protocol compliance tests
  • Client-server integration tests
  • MCP specification compliance verification

Integration with Genie

The MCP package integrates seamlessly with Genie through:

  1. Dependency Injection: ProvideMCPClient() in internal/di/wire.go
  2. Tool Registry: NewRegistryWithMCP() combines native and MCP tools
  3. Event System: MCP tools use the same event bus as native tools
  4. Configuration: Automatic discovery of .mcp.json files

This makes MCP tools completely transparent to users - they just appear as additional tools in the system.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CreateTestServerExecutable

func CreateTestServerExecutable(outputPath string) error

CreateTestServerExecutable creates a standalone executable for the test MCP server

func FindConfigFile

func FindConfigFile(projectRoot string) (string, error)

FindConfigFile looks for .mcp.json files in project and user scope

func GetConfigPath

func GetConfigPath(projectRoot string) (string, bool)

GetConfigPath returns the path to the MCP configuration file if it exists

func IsNotification

func IsNotification(data []byte) bool

IsNotification checks if the message is a notification

func IsRequest

func IsRequest(data []byte) bool

IsRequest checks if the message is a request

func IsResponse

func IsResponse(data []byte) bool

IsResponse checks if the message is a response

func ParseMessage

func ParseMessage(data []byte) (interface{}, error)

ParseMessage parses a JSON message into the appropriate type

func WriteTestServerToFile

func WriteTestServerToFile(filename string) error

WriteTestServerToFile writes a simple test server to a file for subprocess execution

Types

type CallToolRequest

type CallToolRequest struct {
	Name      string                 `json:"name"`
	Arguments map[string]interface{} `json:"arguments"`
}

CallToolRequest represents a tool execution request

type CallToolResult

type CallToolResult struct {
	Content []Content `json:"content"`
	IsError bool      `json:"isError"`
}

CallToolResult represents the result of a tool execution

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents an MCP client that can connect to MCP servers

func NewClient

func NewClient(config *Config) *Client

NewClient creates a new MCP client

func NewMCPClientFromConfig

func NewMCPClientFromConfig() (*Client, error)

NewMCPClientFromConfig creates an MCP client by discovering and loading configuration files

func (*Client) CallTool

func (c *Client) CallTool(ctx context.Context, toolName string, arguments map[string]interface{}) (*CallToolResult, error)

CallTool executes an MCP tool

func (*Client) Close

func (c *Client) Close() error

Close closes all server connections

func (*Client) ConnectToServers

func (c *Client) ConnectToServers(ctx context.Context) error

ConnectToServers connects to all configured MCP servers

func (*Client) GetTools

func (c *Client) GetTools() []tools.Tool

GetTools returns all discovered MCP tools as Genie tools

func (*Client) GetToolsByServer

func (c *Client) GetToolsByServer() map[string][]tools.Tool

GetToolsByServer returns tools grouped by server name

type ClientCapabilities

type ClientCapabilities struct {
	Experimental map[string]interface{} `json:"experimental,omitempty"`
	Sampling     *SamplingCapability    `json:"sampling,omitempty"`
}

ClientCapabilities describes what the client supports

type ClientInfo

type ClientInfo struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

ClientInfo contains information about the client

type Config

type Config struct {
	McpServers map[string]ServerConfig `json:"mcpServers"`
}

Config represents the structure of .mcp.json configuration files

func LoadConfig

func LoadConfig(configPath string) (*Config, error)

LoadConfig loads MCP configuration from a .mcp.json file

func LoadProjectConfig

func LoadProjectConfig(projectRoot string) (*Config, error)

LoadProjectConfig loads MCP configuration from a specific project directory

func LoadUserConfig

func LoadUserConfig() (*Config, error)

LoadUserConfig loads MCP configuration from user-scoped locations

type Connectable

type Connectable interface {
	Connect(ctx context.Context) error
}

Connectable is an optional interface for transports that need explicit connection

type Content

type Content struct {
	Type string `json:"type"`
	Text string `json:"text,omitempty"`
}

Content represents content returned by tools

type Error

type Error struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

Error represents a JSON-RPC 2.0 error

type HTTPTransport

type HTTPTransport struct {
	// contains filtered or unexported fields
}

HTTPTransport implements MCP communication over HTTP

func NewHTTPTransport

func NewHTTPTransport(baseURL string, headers map[string]string) *HTTPTransport

NewHTTPTransport creates a new HTTP transport

func (*HTTPTransport) Close

func (t *HTTPTransport) Close() error

Close closes the HTTP transport

func (*HTTPTransport) Connect

func (t *HTTPTransport) Connect(ctx context.Context) error

Connect establishes the HTTP connection (no-op for HTTP)

func (*HTTPTransport) IsConnected

func (t *HTTPTransport) IsConnected() bool

IsConnected returns true if the HTTP transport is connected

func (*HTTPTransport) Receive

func (t *HTTPTransport) Receive(ctx context.Context) ([]byte, error)

Receive receives a JSON message from HTTP (not applicable for request-response)

func (*HTTPTransport) Send

func (t *HTTPTransport) Send(ctx context.Context, message interface{}) error

Send sends a JSON message over HTTP POST

type InitializeRequest

type InitializeRequest struct {
	ProtocolVersion string             `json:"protocolVersion"`
	Capabilities    ClientCapabilities `json:"capabilities"`
	ClientInfo      ClientInfo         `json:"clientInfo"`
}

InitializeRequest is sent to establish a connection with an MCP server

type InitializeResult

type InitializeResult struct {
	ProtocolVersion string             `json:"protocolVersion"`
	Capabilities    ServerCapabilities `json:"capabilities"`
	ServerInfo      ServerInfo         `json:"serverInfo"`
}

InitializeResult is the response to an initialize request

type ListToolsRequest

type ListToolsRequest struct{}

ListToolsRequest requests the list of available tools

type ListToolsResult

type ListToolsResult struct {
	Tools []Tool `json:"tools"`
}

ListToolsResult contains the list of available tools

type LoggingCapability

type LoggingCapability struct{}

LoggingCapability indicates if the server supports logging

type MCPTool

type MCPTool struct {
	// contains filtered or unexported fields
}

MCPTool wraps an MCP tool to implement Genie's Tool interface

func (*MCPTool) Declaration

func (t *MCPTool) Declaration() *ai.FunctionDeclaration

Declaration converts MCP tool schema to Genie's function declaration

func (*MCPTool) FormatOutput

func (t *MCPTool) FormatOutput(result map[string]interface{}) string

FormatOutput formats the tool result for display

func (*MCPTool) Handler

func (t *MCPTool) Handler() ai.HandlerFunc

Handler returns the execution handler for the MCP tool

type Notification

type Notification struct {
	Jsonrpc string      `json:"jsonrpc"`
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
}

Notification represents a JSON-RPC 2.0 notification (no response expected)

func NewNotification

func NewNotification(method string, params interface{}) *Notification

NewNotification creates a new JSON-RPC 2.0 notification

type PromptsCapability

type PromptsCapability struct {
	ListChanged bool `json:"listChanged,omitempty"`
}

PromptsCapability indicates if the server supports prompts

type Request

type Request struct {
	Jsonrpc string      `json:"jsonrpc"`
	ID      interface{} `json:"id,omitempty"`
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
}

Request represents a JSON-RPC 2.0 request

func NewRequest

func NewRequest(id interface{}, method string, params interface{}) *Request

NewRequest creates a new JSON-RPC 2.0 request

type ResourcesCapability

type ResourcesCapability struct {
	Subscribe   bool `json:"subscribe,omitempty"`
	ListChanged bool `json:"listChanged,omitempty"`
}

ResourcesCapability indicates if the server supports resources

type Response

type Response struct {
	Jsonrpc string      `json:"jsonrpc"`
	ID      interface{} `json:"id,omitempty"`
	Result  interface{} `json:"result,omitempty"`
	Error   *Error      `json:"error,omitempty"`
}

Response represents a JSON-RPC 2.0 response

func NewErrorResponse

func NewErrorResponse(id interface{}, code int, message string, data interface{}) *Response

NewErrorResponse creates a new JSON-RPC 2.0 error response

func NewResponse

func NewResponse(id interface{}, result interface{}) *Response

NewResponse creates a new JSON-RPC 2.0 response

type SSETransport

type SSETransport struct {
	// contains filtered or unexported fields
}

SSETransport implements MCP communication over Server-Sent Events

func NewSSETransport

func NewSSETransport(url string, headers map[string]string) *SSETransport

NewSSETransport creates a new SSE transport

func (*SSETransport) Close

func (t *SSETransport) Close() error

Close closes the SSE transport

func (*SSETransport) Connect

func (t *SSETransport) Connect(ctx context.Context) error

Connect establishes the SSE connection

func (*SSETransport) IsConnected

func (t *SSETransport) IsConnected() bool

IsConnected returns true if the SSE transport is connected

func (*SSETransport) Receive

func (t *SSETransport) Receive(ctx context.Context) ([]byte, error)

Receive receives a message from SSE stream

func (*SSETransport) Send

func (t *SSETransport) Send(ctx context.Context, message interface{}) error

Send sends a message over SSE (typically not used for SSE)

type SamplingCapability

type SamplingCapability struct{}

SamplingCapability indicates if the client supports sampling

type ServerCapabilities

type ServerCapabilities struct {
	Experimental map[string]interface{} `json:"experimental,omitempty"`
	Logging      *LoggingCapability     `json:"logging,omitempty"`
	Prompts      *PromptsCapability     `json:"prompts,omitempty"`
	Resources    *ResourcesCapability   `json:"resources,omitempty"`
	Tools        *ToolsCapability       `json:"tools,omitempty"`
}

ServerCapabilities describes what the server supports

type ServerConfig

type ServerConfig struct {
	// For stdio servers
	Command string            `json:"command,omitempty"`
	Args    []string          `json:"args,omitempty"`
	Env     map[string]string `json:"env,omitempty"`

	// For SSE/HTTP servers
	Type    string            `json:"type,omitempty"`
	URL     string            `json:"url,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
}

ServerConfig defines the configuration for an MCP server

func (ServerConfig) GetTransportType

func (sc ServerConfig) GetTransportType() TransportType

GetTransportType returns the transport type for this server config

func (ServerConfig) Validate

func (sc ServerConfig) Validate() error

Validate checks if the server configuration is valid

type ServerConnection

type ServerConnection struct {
	// contains filtered or unexported fields
}

ServerConnection represents a connection to an MCP server

type ServerInfo

type ServerInfo struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

ServerInfo contains information about the server

type StdioTransport

type StdioTransport struct {
	// contains filtered or unexported fields
}

StdioTransport implements MCP communication over stdio

func NewStdioTransport

func NewStdioTransport(command string, args []string, env []string) *StdioTransport

NewStdioTransport creates a new stdio transport

func (*StdioTransport) Close

func (t *StdioTransport) Close() error

Close closes the stdio transport

func (*StdioTransport) Connect

func (t *StdioTransport) Connect(ctx context.Context) error

Connect establishes the stdio connection

func (*StdioTransport) IsConnected

func (t *StdioTransport) IsConnected() bool

IsConnected returns true if the stdio transport is connected

func (*StdioTransport) Receive

func (t *StdioTransport) Receive(ctx context.Context) ([]byte, error)

Receive receives a JSON message from stdout

func (*StdioTransport) Send

func (t *StdioTransport) Send(ctx context.Context, message interface{}) error

Send sends a JSON message over stdin

type TestMCPServer

type TestMCPServer struct {
	// contains filtered or unexported fields
}

TestMCPServer is a simple MCP server implementation for testing

func NewTestMCPServer

func NewTestMCPServer() *TestMCPServer

NewTestMCPServer creates a new test MCP server

func (*TestMCPServer) Run

func (s *TestMCPServer) Run()

Run starts the test MCP server using stdio

type Tool

type Tool struct {
	Name        string     `json:"name"`
	Description string     `json:"description,omitempty"`
	InputSchema ToolSchema `json:"inputSchema"`
}

Tool represents an MCP tool definition

type ToolSchema

type ToolSchema struct {
	Type                 string                        `json:"type"`
	Properties           map[string]ToolSchemaProperty `json:"properties,omitempty"`
	Required             []string                      `json:"required,omitempty"`
	AdditionalProperties interface{}                   `json:"additionalProperties,omitempty"`
}

ToolSchema represents the JSON schema for tool input

type ToolSchemaProperty

type ToolSchemaProperty struct {
	Type        string                        `json:"type,omitempty"`
	Description string                        `json:"description,omitempty"`
	Enum        []string                      `json:"enum,omitempty"`
	Default     interface{}                   `json:"default,omitempty"`
	MinLength   *int                          `json:"minLength,omitempty"`
	MaxLength   *int                          `json:"maxLength,omitempty"`
	Minimum     *float64                      `json:"minimum,omitempty"`
	Maximum     *float64                      `json:"maximum,omitempty"`
	Items       *ToolSchemaProperty           `json:"items,omitempty"`      // For array types
	Properties  map[string]ToolSchemaProperty `json:"properties,omitempty"` // For object types
}

ToolSchemaProperty represents a property in a tool schema

type ToolsCapability

type ToolsCapability struct {
	ListChanged bool `json:"listChanged,omitempty"`
}

ToolsCapability indicates if the server supports tools

type Transport

type Transport interface {
	// Send sends a message to the server
	Send(ctx context.Context, message interface{}) error

	// Receive receives a message from the server
	Receive(ctx context.Context) ([]byte, error)

	// Close closes the transport connection
	Close() error

	// IsConnected returns true if the transport is connected
	IsConnected() bool
}

Transport defines the interface for MCP communication transports

type TransportFactory

type TransportFactory struct{}

TransportFactory creates transports based on server configuration

func NewTransportFactory

func NewTransportFactory() *TransportFactory

NewTransportFactory creates a new transport factory

func (*TransportFactory) CreateTransport

func (f *TransportFactory) CreateTransport(config ServerConfig) (Transport, error)

CreateTransport creates a transport based on the server configuration

type TransportType

type TransportType string

TransportType represents the type of transport for an MCP server

const (
	TransportStdio TransportType = "stdio"
	TransportSSE   TransportType = "sse"
	TransportHTTP  TransportType = "http"
)

Jump to

Keyboard shortcuts

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