MCP in Go: Connecting Agents to Memory Stores
Learn how to build MCP clients in Go and connect your AI agents to persistent memory stores like CodeMem.
Why Go for MCP Clients?
Go has become the language of choice for building robust infrastructure tools. Its excellent concurrency primitives, simple deployment (single binary), and strong standard library make it perfect for building MCP (Model Context Protocol) clients that connect AI agents to external services.
Whether you're building a custom AI agent, a development tool, or integrating memory capabilities into your existing Go application, understanding how to implement MCP clients opens up powerful possibilities.
Understanding MCP Architecture
MCP uses JSON-RPC 2.0 over HTTP for server-client communication. An MCP client needs to:
- Discover available tools from the server
- Send tool invocation requests with proper parameters
- Handle responses and errors gracefully
Let's build a Go client that connects to CodeMem's MCP server and manages memories programmatically.
Setting Up the Project
First, create a new Go module and define the core types:
package codemem
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
// MCPRequest represents a JSON-RPC 2.0 request
type MCPRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params,omitempty"`
}
// MCPResponse represents a JSON-RPC 2.0 response
type MCPResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result json.RawMessage `json:"result,omitempty"`
Error *MCPError `json:"error,omitempty"`
}
// MCPError represents an error in the response
type MCPError struct {
Code int `json:"code"`
Message string `json:"message"`
} Building the Client
Now let's create a reusable client struct with methods for interacting with CodeMem:
// Client handles communication with CodeMem MCP server
type Client struct {
baseURL string
apiKey string
httpClient *http.Client
requestID int
}
// NewClient creates a new CodeMem MCP client
func NewClient(apiKey string) *Client {
return &Client{
baseURL: "https://app.codemem.dev/mcp",
apiKey: apiKey,
httpClient: &http.Client{Timeout: 30 * time.Second},
requestID: 0,
}
}
// call sends an MCP request and returns the response
func (c *Client) call(method string, params interface{}) (*MCPResponse, error) {
c.requestID++
req := MCPRequest{
JSONRPC: "2.0",
ID: c.requestID,
Method: method,
Params: params,
}
body, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("marshal request: %w", err)
}
httpReq, err := http.NewRequest("POST", c.baseURL, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+c.apiKey)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("send request: %w", err)
}
defer resp.Body.Close()
var mcpResp MCPResponse
if err := json.NewDecoder(resp.Body).Decode(&mcpResp); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
if mcpResp.Error != nil {
return nil, fmt.Errorf("mcp error %d: %s",
mcpResp.Error.Code, mcpResp.Error.Message)
}
return &mcpResp, nil
} Implementing Memory Operations
With the base client ready, let's add methods for the core memory operations:
// Memory represents a stored memory item
type Memory struct {
ID string `json:"id"`
Content string `json:"content"`
Project string `json:"project,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// AddMemory stores a new memory
func (c *Client) AddMemory(content, project string) (*Memory, error) {
params := map[string]interface{}{
"name": "add_memory",
"arguments": map[string]string{
"content": content,
"project": project,
},
}
resp, err := c.call("tools/call", params)
if err != nil {
return nil, err
}
var memory Memory
if err := json.Unmarshal(resp.Result, &memory); err != nil {
return nil, fmt.Errorf("parse memory: %w", err)
}
return &memory, nil
}
// SearchMemories finds memories matching a query
func (c *Client) SearchMemories(query string, limit int) ([]Memory, error) {
params := map[string]interface{}{
"name": "search_memories",
"arguments": map[string]interface{}{
"query": query,
"limit": limit,
},
}
resp, err := c.call("tools/call", params)
if err != nil {
return nil, err
}
var memories []Memory
if err := json.Unmarshal(resp.Result, &memories); err != nil {
return nil, fmt.Errorf("parse memories: %w", err)
}
return memories, nil
} Putting It All Together
Here's how to use the client in your application:
func main() {
client := codemem.NewClient(os.Getenv("CODEMEM_API_KEY"))
// Store a coding preference
memory, err := client.AddMemory(
"Project uses Go 1.22 with generics for type-safe collections",
"my-go-project",
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Stored memory: %s\n", memory.ID)
// Search for relevant context
memories, err := client.SearchMemories("generics", 5)
if err != nil {
log.Fatal(err)
}
for _, m := range memories {
fmt.Printf("Found: %s\n", m.Content)
}
} Best Practices for Go MCP Clients
- Use contexts: Add
context.Contextto all methods for timeout and cancellation control - Implement retries: Network issues happen—use exponential backoff for transient failures
- Pool connections: Reuse the HTTP client to benefit from connection pooling
- Handle rate limits: Respect
Retry-Afterheaders when you hit API limits - Log strategically: Use structured logging for debugging without exposing sensitive data
What's Next?
You now have the foundation for building Go applications that leverage AI memory. Consider extending this client with:
- Concurrent memory operations with goroutines
- Local caching for frequently accessed memories
- Integration with your existing agent framework
Ready to Build?
Get your free CodeMem API key and start building memory-powered Go applications today. Your AI agents deserve persistent context.
Get Your API Key →