MCP Tool Serialization
The Model Context Protocol (MCP) allows servers to expose tools that can be used by AI models. The Go-AI SDK provides methods to retrieve tool definitions in a format that can be stored, transmitted, or cached.
GetSerializableTools()
The GetSerializableTools() method returns tool definitions from an MCP server in a JSON-serializable format with full pagination support.
Method Signature
func (c *MCPClient) GetSerializableTools(ctx context.Context) (*ListToolsResult, error)
Return Type
type ListToolsResult struct {
Tools []MCPTool `json:"tools"`
NextCursor string `json:"nextCursor,omitempty"`
}
type MCPTool struct {
Name string `json:"name"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
InputSchema map[string]interface{} `json:"inputSchema"`
OutputSchema map[string]interface{} `json:"outputSchema,omitempty"`
Annotations map[string]interface{} `json:"annotations,omitempty"`
Meta map[string]interface{} `json:"_meta,omitempty"`
}
MCPTool preserves the current MCP tool JSON shape, including title,
outputSchema, annotations, and _meta. MCP Apps metadata is stored in
_meta.ui or the legacy flat _meta["ui/resourceUri"] field and can be
normalized with mcp.GetMCPAppToolMeta.
Basic Usage
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/digitallysavvy/go-ai/pkg/mcp"
)
func main() {
// Create MCP client
transport := mcp.NewStdioTransport(mcp.StdioTransportConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-everything"},
})
client := mcp.NewMCPClient(transport, mcp.MCPClientConfig{
ClientName: "my-app",
ClientVersion: "1.0.0",
})
// Connect to server
ctx := context.Background()
if err := client.Connect(ctx); err != nil {
log.Fatal(err)
}
defer client.Close()
// Get serializable tools
tools, err := client.GetSerializableTools(ctx)
if err != nil {
log.Fatal(err)
}
// Tools can now be serialized and stored
data, _ := json.MarshalIndent(tools, "", " ")
fmt.Printf("Serialized tools:\n%s\n", string(data))
}
Storing Tool Definitions
Tool definitions can be cached to avoid repeated server queries:
import (
"encoding/json"
"os"
)
// Cache tools to file
func cacheTools(tools *mcp.ListToolsResult, filename string) error {
data, err := json.Marshal(tools)
if err != nil {
return err
}
return os.WriteFile(filename, data, 0644)
}
// Load tools from cache
func loadCachedTools(filename string) (*mcp.ListToolsResult, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var tools mcp.ListToolsResult
if err := json.Unmarshal(data, &tools); err != nil {
return nil, err
}
return &tools, nil
}
// Usage
tools, err := client.GetSerializableTools(ctx)
if err != nil {
log.Fatal(err)
}
// Cache for later use
if err := cacheTools(tools, "mcp-tools-cache.json"); err != nil {
log.Printf("Warning: Failed to cache tools: %v", err)
}
Transmitting Tool Definitions
Tool definitions can be sent over the network:
import (
"bytes"
"encoding/json"
"net/http"
)
// Send tools to remote service
func sendToolsToService(tools *mcp.ListToolsResult, url string) error {
data, err := json.Marshal(tools)
if err != nil {
return err
}
resp, err := http.Post(url, "application/json", bytes.NewReader(data))
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
// Usage
tools, err := client.GetSerializableTools(ctx)
if err != nil {
log.Fatal(err)
}
err = sendToolsToService(tools, "https://api.example.com/mcp-tools")
if err != nil {
log.Printf("Failed to send tools: %v", err)
}
Pagination Support
When MCP servers have many tools, results may be paginated:
func getAllTools(client *mcp.MCPClient, ctx context.Context) ([]mcp.MCPTool, error) {
var allTools []mcp.MCPTool
// Get first page
result, err := client.GetSerializableTools(ctx)
if err != nil {
return nil, err
}
allTools = append(allTools, result.Tools...)
// Handle pagination if present.
if result.NextCursor != "" {
log.Printf("More tools available (cursor: %s)", result.NextCursor)
}
return allTools, nil
}
Comparison: ListTools vs GetSerializableTools
The SDK provides two methods for retrieving tools:
| Method | Return Type | Use Case |
|---|---|---|
ListTools() | []MCPTool | Convenience method for immediate use |
GetSerializableTools() | *ListToolsResult | Full result with pagination support for storage/transmission |
// Convenience method - just the tools
tools, err := client.ListTools(ctx)
// Returns: []MCPTool
// Serialization method - complete result
result, err := client.GetSerializableTools(ctx)
// Returns: *ListToolsResult with Tools + NextCursor
Use Cases
1. Tool Registry Service
Build a centralized registry of available tools:
type ToolRegistry struct {
tools map[string]*mcp.ListToolsResult
}
func (r *ToolRegistry) RegisterServer(serverName string, tools *mcp.ListToolsResult) {
r.tools[serverName] = tools
}
func (r *ToolRegistry) GetAllTools() []*mcp.ListToolsResult {
var all []*mcp.ListToolsResult
for _, tools := range r.tools {
all = append(all, tools)
}
return all
}
2. Offline Tool Discovery
Cache tools for offline browsing:
func discoverAndCache(servers []string) error {
for _, server := range servers {
client := connectToServer(server)
tools, err := client.GetSerializableTools(ctx)
if err != nil {
log.Printf("Failed to get tools from %s: %v", server, err)
continue
}
filename := fmt.Sprintf("tools-%s.json", server)
if err := cacheTools(tools, filename); err != nil {
log.Printf("Failed to cache tools for %s: %v", server, err)
}
}
return nil
}
3. Tool Versioning
Track tool changes over time:
type ToolSnapshot struct {
Timestamp time.Time `json:"timestamp"`
Tools *mcp.ListToolsResult `json:"tools"`
}
func snapshotTools(client *mcp.MCPClient) (*ToolSnapshot, error) {
tools, err := client.GetSerializableTools(context.Background())
if err != nil {
return nil, err
}
return &ToolSnapshot{
Timestamp: time.Now(),
Tools: tools,
}, nil
}
Best Practices
- Cache tool definitions: Avoid repeated queries by caching results
- Handle pagination: Check for
NextCursorand implement pagination when needed - Validate after deserialization: Ensure tools loaded from cache are still valid
- Version your cache: Include timestamps or version numbers in cached data
- Implement TTL: Refresh cached tools periodically to catch updates
Error Handling
tools, err := client.GetSerializableTools(ctx)
if err != nil {
var mcpErr *mcp.MCPClientError
switch {
case errors.Is(err, context.DeadlineExceeded):
log.Fatal("Timeout getting tools")
case errors.As(err, &mcpErr):
log.Fatalf("MCP error %d: %s", mcpErr.Code, mcpErr.Message)
default:
log.Fatalf("Failed to get tools: %v", err)
}
}