go-mcp-host

MCP (Model Context Protocol) Quick Reference

What is MCP?

Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to Large Language Models (LLMs). It enables AI applications like Claude, IDEs, and other tools to connect to external data sources and tools through a unified interface.

Official Documentation: https://modelcontextprotocol.io/docs/learn/architecture


Core Concepts

Architecture

┌─────────────────┐
│   MCP Host      │  (Our go-mcp-host service)
│  (AI App)       │
└────────┬────────┘
         │ manages
         ▼
┌─────────────────┐
│   MCP Client    │  (1:1 with each server)
│   (per server)  │
└────────┬────────┘
         │ connects to
         ▼
┌─────────────────┐
│   MCP Server    │  (Filesystem, Sentry, etc.)
│   (provides     │
│    context)     │
└─────────────────┘

Participants

  1. MCP Host: The AI application (our Go service) that coordinates one or multiple MCP clients
  2. MCP Client: Component that maintains a 1:1 connection to an MCP server
  3. MCP Server: Program that provides context (tools, resources, prompts) to clients

Key Point: Our go-mcp-host is an MCP Host that creates MCP Clients to connect to external MCP Servers.


Protocol Layers

1. Transport Layer (How to Connect)

Two transport mechanisms:

Stdio Transport (Local Servers)

// Example: Launch filesystem server via stdio
cmd := exec.Command("npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
cmd.Start()

HTTP Transport (Remote Servers)

// Example: Connect to remote server
client := NewHTTPMCPClient("https://api.example.com/mcp", bearerToken)

2. Data Layer (What to Exchange)

Uses JSON-RPC 2.0 for all communication.

Message Types

Request (expects response):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [...]
  }
}

Notification (no response):

{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

MCP Primitives

Server Primitives (What Servers Provide)

1. Tools

Executable functions that AI can invoke to perform actions.

Methods:

Example Tool:

{
  "name": "read_file",
  "description": "Read contents of a file",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": { "type": "string" }
    },
    "required": ["path"]
  }
}

Use Case: File operations, API calls, database queries, web searches

2. Resources

Data sources that provide contextual information to AI.

Methods:

Example Resource:

{
  "uri": "file:///path/to/schema.sql",
  "name": "Database Schema",
  "description": "PostgreSQL database schema",
  "mimeType": "text/plain"
}

Use Case: File contents, database schemas, API documentation, configuration data

3. Prompts

Reusable templates for structuring LLM interactions.

Methods:

Example Prompt:

{
  "name": "code_review",
  "description": "Review code changes",
  "arguments": [
    {
      "name": "language",
      "description": "Programming language",
      "required": true
    }
  ]
}

Use Case: System prompts, few-shot examples, templated instructions

Client Primitives (What Clients Can Expose)

1. Sampling

Allows servers to request LLM completions from the client.

Method: sampling/createMessage

Use Case: Server needs AI assistance but wants to stay model-agnostic

2. Roots

Allows servers to request file system roots from the client.

Method: roots/list

Use Case: File system servers need to know what directories to access


Lifecycle Management

Connection Initialization

  1. Client connects to server (via stdio or HTTP)
  2. Initialize exchange: Negotiate capabilities
  3. Client sends initialized notification
  4. Connection ready for use
// 1. Client  Server: Initialize request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {}
    },
    "clientInfo": {
      "name": "go-mcp-host",
      "version": "1.0.0"
    }
  }
}

// 2. Server  Client: Initialize response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true }
    },
    "serverInfo": {
      "name": "filesystem-server",
      "version": "1.0.0"
    }
  }
}

// 3. Client  Server: Initialized notification
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

Capability Negotiation

Both client and server declare what they support:

Client Capabilities:

Server Capabilities:

Notifications

Servers can notify clients about changes:

Client should respond by re-fetching the list.


Typical Workflow in Our Agent

1. User Sends Message

User: "What files are in my project directory?"

2. Agent Gathers Context

// List all available tools from all connected MCP servers
tools := mcpManager.ListAllTools(ctx, conversationID)
// Tools: [list_directory, read_file, write_file, ...]

// Check for relevant resources
resources := mcpManager.ListAllResources(ctx, conversationID)
// Resources: [project_structure.md, README.md, ...]

3. Agent Builds LLM Prompt

// Convert MCP tools to OpenAI function format
llmTools := []Tool{
  {
    Name: "list_directory",
    Description: "List contents of a directory",
    Parameters: {...}
  },
  ...
}

// Build messages
messages := []Message{
  {Role: "system", Content: "You are a helpful assistant with access to filesystem tools."},
  {Role: "user", Content: "What files are in my project directory?"},
}

// Send to LLM
response := ollamaClient.Chat(ctx, messages, llmTools)

4. LLM Decides to Use Tool

{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_1",
      "type": "function",
      "function": {
        "name": "list_directory",
        "arguments": "{\"path\": \"/project\"}"
      }
    }
  ]
}

5. Agent Executes Tool via MCP

// Find which MCP server provides this tool
mcpClient := mcpManager.GetClientForTool("list_directory")

// Execute tool via MCP protocol
result := mcpClient.CallTool(ctx, "list_directory", map[string]interface{}{
  "path": "/project",
})
// Result: {content: [{type: "text", text: "file1.go\nfile2.go\n..."}]}

6. Agent Returns Results to LLM

// Add tool result to conversation
messages = append(messages, Message{
  Role: "tool",
  ToolCallID: "call_1",
  Content: "file1.go\nfile2.go\nREADME.md\n...",
})

// Ask LLM to synthesize response
finalResponse := ollamaClient.Chat(ctx, messages, llmTools)

7. LLM Responds to User

Assistant: "Your project directory contains the following files:
- file1.go
- file2.go  
- README.md
..."

Implementation Checklist for go-mcp-host

Phase 1: MCP Client Basics

Phase 2: Full MCP Support

Phase 3: Session Management

Phase 4: Integration with Agent


Example MCP Servers

Official Servers

  1. Filesystem Server: Read/write local files
    npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/dir
    
  2. PostgreSQL Server: Query databases
    npx -y @modelcontextprotocol/server-postgres postgresql://localhost/dbname
    
  3. Puppeteer Server: Web automation
    npx -y @modelcontextprotocol/server-puppeteer
    
  4. Sentry Server: Access Sentry issues
    Remote HTTP server: https://mcp.sentry.io
    

Community Servers

Registry: https://github.com/modelcontextprotocol/servers


Key Differences from Other Protocols

MCP vs Function Calling

MCP vs RAG

MCP vs LangChain Tools


Best Practices

1. Connection Management

2. Error Handling

3. Security

4. Performance

5. User Experience


Debugging Tips

1. Enable Debug Logging

export GO_SVC_TEMPLATE_DEBUG=true

2. Test MCP Server Separately

Use the official MCP Inspector tool:

npx @modelcontextprotocol/inspector [server-command]

3. Check JSON-RPC Messages

Log all messages sent/received for debugging:

logging.LogDebugf("MCP Request: %s", string(requestJSON))
logging.LogDebugf("MCP Response: %s", string(responseJSON))

4. Common Issues


Resources