AgenticGoKit is currently in Beta. APIs may change before the stable v1.0 release.
Skip to content

Tool Integration ​

Learn how to add tool capabilities to your agents, enabling them to interact with external systems, APIs, and services through the Model Context Protocol (MCP).


🎯 Overview ​

AgenticGoKit v1beta provides comprehensive tool integration through:

  • MCP Protocol - Standard protocol for tool communication
  • Multiple Transports - TCP, stdio, WebSocket, HTTP SSE, HTTP streaming
  • Tool Discovery - Automatic discovery of available tools
  • Caching - Intelligent result caching with TTL
  • Circuit Breaker - Fault tolerance for external services
  • Metrics - Comprehensive monitoring and observability

🚀 Quick Start ​

Basic MCP Server Configuration ​

go
package main

import (
    "context"
    "log"
    "github.com/agenticgokit/agenticgokit/v1beta"
)

func main() {
    // Define MCP servers
    mcpServers := []v1beta.MCPServer{
        {
            Name:    "filesystem",
            Type:    "stdio",
            Command: "mcp-server-filesystem",
            Enabled: true,
        },
        {
            Name:    "web-api",
            Type:    "http_sse",
            Address: "localhost",
            Port:    8080,
            Enabled: true,
        },
    }
    
    // Create agent with MCP tools
    agent, err := v1beta.NewBuilder("ToolAgent").
        WithPreset(v1beta.ChatAgent).
        WithLLM("openai", "gpt-4").
        WithTools(
            v1beta.WithMCP(mcpServers...),
        ).
        Build()
    if err != nil {
        log.Fatal(err)
    }
    
    // Agent can now use MCP tools
    result, _ := agent.Run(context.Background(), "List files in current directory")
}

🔧 MCP Server Types ​

1. Stdio Transport ​

Execute MCP servers as child processes:

go
mcpServer := v1beta.MCPServer{
    Name:    "filesystem",
    Type:    "stdio",
    Command: "mcp-server-filesystem",
    Enabled: true,
}

agent, _ := v1beta.NewBuilder("FSAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(mcpServer),
    ).
    Build()

Use Cases:

  • Local file operations
  • Command-line tools
  • Sandboxed environments

2. TCP Transport ​

Connect to MCP servers over TCP:

go
mcpServer := v1beta.MCPServer{
    Name:    "database",
    Type:    "tcp",
    Address: "localhost",
    Port:    9090,
    Enabled: true,
}

agent, _ := v1beta.NewBuilder("DBAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(mcpServer),
    ).
    Build()

Use Cases:

  • Remote services
  • Microservices
  • Database connections

3. WebSocket Transport ​

Bi-directional communication with MCP servers:

go
mcpServer := v1beta.MCPServer{
    Name:    "realtime-data",
    Type:    "websocket",
    Address: "ws://localhost:8081/mcp",
    Enabled: true,
}

agent, _ := v1beta.NewBuilder("RealtimeAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(mcpServer),
    ).
    Build()

Use Cases:

  • Real-time updates
  • Streaming data
  • Interactive services

4. HTTP SSE Transport ​

Server-Sent Events for streaming:

go
mcpServer := v1beta.MCPServer{
    Name:    "web-api",
    Type:    "http_sse",
    Address: "localhost",
    Port:    8080,
    Enabled: true,
}

agent, _ := v1beta.NewBuilder("WebAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(mcpServer),
    ).
    Build()

Use Cases:

  • Web APIs
  • Event streams
  • HTTP-based services

5. HTTP Streaming Transport ​

Chunked transfer encoding for large responses:

go
mcpServer := v1beta.MCPServer{
    Name:    "data-processor",
    Type:    "http_streaming",
    Address: "api.example.com",
    Port:    443,
    Enabled: true,
}

agent, _ := v1beta.NewBuilder("DataAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(mcpServer),
    ).
    Build()

Use Cases:

  • Large data transfers
  • Progressive processing
  • Media streaming

🔍 Tool Discovery ​

Automatic Discovery ​

Automatically discover MCP servers on the network:

go
agent, _ := v1beta.NewBuilder("DiscoveryAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCPDiscovery(8080, 8081, 8090, 8100), // Scan these ports
    ).
    Build()

Configuration:

go
// Custom discovery settings
agent, _ := v1beta.NewBuilder("CustomDiscovery").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCPDiscovery(), // Uses default ports
        v1beta.WithToolTimeout(30 * time.Second),
    ).
    Build()

Manual Tool Registration ​

Register tools programmatically:

go
import "github.com/agenticgokit/agenticgokit/v1beta"

// Get tool manager from capabilities
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // List available tools
    tools := capabilities.Tools.List()
    for _, tool := range tools {
        fmt.Printf("Tool: %s - %s\n", tool.Name, tool.Description)
    }
    
    // Check if specific tool is available
    if capabilities.Tools.IsAvailable("web_search") {
        // Use the tool
        result, _ := capabilities.Tools.Execute(ctx, "web_search", map[string]interface{}{
            "query": "latest news",
        })
        return fmt.Sprintf("%v", result.Content), nil
    }
    
    return capabilities.LLM("You are a helpful assistant.", input)
}

⚙️ Tool Configuration ​

Basic Configuration ​

go
agent, _ := v1beta.NewBuilder("ConfiguredAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(servers...),
        v1beta.WithToolTimeout(30 * time.Second),
        v1beta.WithMaxConcurrentTools(5),
    ).
    Build()

Advanced Configuration via TOML ​

toml
[tools]
enabled = true
max_retries = 3
timeout = "30s"
rate_limit = 100  # requests per second
max_concurrent = 10

[tools.mcp]
enabled = true
discovery = true
connection_timeout = "30s"
max_retries = 3
retry_delay = "1s"
discovery_timeout = "10s"
scan_ports = [8080, 8081, 8090, 8100]

[[tools.mcp.servers]]
name = "filesystem"
type = "stdio"
command = "mcp-server-filesystem"
enabled = true

[[tools.mcp.servers]]
name = "web-api"
type = "http_sse"
address = "localhost"
port = 8080
enabled = true

[tools.cache]
enabled = true
ttl = "15m"
max_size = 100  # MB
max_keys = 10000
eviction_policy = "lru"
cleanup_interval = "5m"
backend = "memory"

[tools.cache.tool_ttls]
web_search = "5m"
content_fetch = "30m"
summarize_text = "60m"

[tools.circuit_breaker]
enabled = true
failure_threshold = 5
success_threshold = 2
timeout = "60s"
half_open_max_calls = 3

🎯 Using Tools in Handlers ​

Execute Tool Directly ​

go
import "github.com/agenticgokit/agenticgokit/v1beta"

handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Execute a specific tool
    result, err := capabilities.Tools.Execute(ctx, "web_search", map[string]interface{}{
        "query":      input,
        "max_results": 5,
    })
    if err != nil {
        return "", fmt.Errorf("tool execution failed: %w", err)
    }
    
    if !result.Success {
        return "", fmt.Errorf("tool error: %s", result.Error)
    }
    
    // Use tool result
    return fmt.Sprintf("Search results: %v", result.Content), nil
}

Tool Discovery Pattern ​

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // List all available tools
    tools := capabilities.Tools.List()
    
    // Find appropriate tool
    var selectedTool *v1beta.ToolInfo
    for _, tool := range tools {
        if strings.Contains(tool.Name, "search") {
            selectedTool = &tool
            break
        }
    }
    
    if selectedTool == nil {
        return "No search tool available", nil
    }
    
    // Execute the found tool
    result, err := capabilities.Tools.Execute(ctx, selectedTool.Name, map[string]interface{}{
        "query": input,
    })
    if err != nil {
        return "", err
    }
    
    return fmt.Sprintf("%v", result.Content), nil
}

LLM-Directed Tool Selection ​

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Get available tools
    tools := capabilities.Tools.List()
    toolList := make([]string, len(tools))
    for i, tool := range tools {
        toolList[i] = fmt.Sprintf("%s: %s", tool.Name, tool.Description)
    }
    
    // Ask LLM which tool to use
    prompt := fmt.Sprintf("Available tools:\n%s\n\nWhich tool should be used for: %s\nReply with just the tool name.",
        strings.Join(toolList, "\n"), input)
    
    toolName, err := capabilities.LLM("You are a tool selection expert.", prompt)
    if err != nil {
        return "", err
    }
    
    // Execute selected tool
    result, err := capabilities.Tools.Execute(ctx, strings.TrimSpace(toolName), map[string]interface{}{
        "query": input,
    })
    if err != nil {
        return "", err
    }
    
    return fmt.Sprintf("%v", result.Content), nil
}

💾 Caching ​

Enable Caching ​

go
agent, _ := v1beta.NewBuilder("CachedAgent").
    WithLLM("openai", "gpt-4").
    WithTools(
        v1beta.WithMCP(servers...),
        v1beta.WithToolCaching(15 * time.Minute), // 15 minute TTL
    ).
    Build()

Per-Tool TTL Configuration ​

toml
[tools.cache.tool_ttls]
web_search = "5m"           # Short TTL for dynamic data
content_fetch = "30m"       # Medium TTL for semi-static content
database_query = "1m"       # Very short for real-time data
static_api = "24h"          # Long TTL for static APIs

Cache Backend Options ​

toml
[tools.cache]
backend = "memory"  # Options: memory, redis, file

# Redis backend configuration
[tools.cache.backend_config]
redis_url = "redis://localhost:6379"
redis_db = "0"
redis_password = "${REDIS_PASSWORD}"

🔒 Circuit Breaker ​

Protect against cascading failures:

toml
[tools.circuit_breaker]
enabled = true
failure_threshold = 5        # Open after 5 consecutive failures
success_threshold = 2        # Close after 2 consecutive successes
timeout = "60s"              # Time before attempting to half-open
half_open_max_calls = 3      # Max calls in half-open state

States:

  • Closed: Normal operation, all requests go through
  • Open: Circuit is tripped, requests fail immediately
  • Half-Open: Testing if service has recovered

📊 Monitoring and Metrics ​

Get Tool Metrics ​

go
import "github.com/agenticgokit/agenticgokit/v1beta"

handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Get metrics
    metrics := capabilities.Tools.GetMetrics()
    
    fmt.Printf("Total Executions: %d\n", metrics.TotalExecutions)
    fmt.Printf("Success Rate: %.2f%%\n", 
        float64(metrics.SuccessfulCalls)/float64(metrics.TotalExecutions)*100)
    fmt.Printf("Average Latency: %v\n", metrics.AverageLatency)
    fmt.Printf("Cache Hit Rate: %.2f%%\n", metrics.CacheHitRate*100)
    
    // Per-tool metrics
    for toolName, toolMetrics := range metrics.ToolMetrics {
        fmt.Printf("\nTool: %s\n", toolName)
        fmt.Printf("  Executions: %d\n", toolMetrics.Executions)
        fmt.Printf("  Success Rate: %.2f%%\n", toolMetrics.SuccessRate*100)
        fmt.Printf("  Avg Latency: %v\n", toolMetrics.AverageLatency)
    }
    
    return capabilities.LLM("You are a helpful assistant.", input)
}

Health Checks ​

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Check health of all MCP servers
    healthStatus := capabilities.Tools.HealthCheck(ctx)
    
    for serverName, status := range healthStatus {
        fmt.Printf("Server: %s\n", serverName)
        fmt.Printf("  Status: %s\n", status.Status)
        fmt.Printf("  Response Time: %v\n", status.ResponseTime)
        fmt.Printf("  Tool Count: %d\n", status.ToolCount)
        if status.Error != "" {
            fmt.Printf("  Error: %s\n", status.Error)
        }
    }
    
    return capabilities.LLM("You are a helpful assistant.", input)
}

🎨 Common Patterns ​

Pattern 1: Tool-First Agent ​

Prefer tools over LLM for specific tasks:

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Try to use appropriate tool first
    if strings.Contains(strings.ToLower(input), "search") {
        result, err := capabilities.Tools.Execute(ctx, "web_search", map[string]interface{}{
            "query": input,
        })
        if err == nil && result.Success {
            return fmt.Sprintf("%v", result.Content), nil
        }
    }
    
    // Fall back to LLM
    return capabilities.LLM("You are a helpful assistant.", input)
}

Pattern 2: Multi-Tool Pipeline ​

Chain multiple tools:

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Step 1: Search for information
    searchResult, err := capabilities.Tools.Execute(ctx, "web_search", map[string]interface{}{
        "query": input,
    })
    if err != nil {
        return "", err
    }
    
    // Step 2: Fetch content
    contentResult, err := capabilities.Tools.Execute(ctx, "content_fetch", map[string]interface{}{
        "url": searchResult.Content,
    })
    if err != nil {
        return "", err
    }
    
    // Step 3: Summarize with LLM
    return capabilities.LLM(
        "Summarize this content concisely.",
        fmt.Sprintf("%v", contentResult.Content),
    )
}

Pattern 3: Parallel Tool Execution ​

Execute multiple tools concurrently:

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    type result struct {
        name string
        data interface{}
        err  error
    }
    
    resultChan := make(chan result, 3)
    
    // Execute tools in parallel
    go func() {
        res, err := capabilities.Tools.Execute(ctx, "web_search", map[string]interface{}{"query": input})
        resultChan <- result{"search", res.Content, err}
    }()
    
    go func() {
        res, err := capabilities.Tools.Execute(ctx, "news_api", map[string]interface{}{"topic": input})
        resultChan <- result{"news", res.Content, err}
    }()
    
    go func() {
        res, err := capabilities.Tools.Execute(ctx, "social_media", map[string]interface{}{"hashtag": input})
        resultChan <- result{"social", res.Content, err}
    }()
    
    // Collect results
    var results []string
    for i := 0; i < 3; i++ {
        r := <-resultChan
        if r.err == nil {
            results = append(results, fmt.Sprintf("%s: %v", r.name, r.data))
        }
    }
    
    // Synthesize with LLM
    return capabilities.LLM(
        "Synthesize these results into a coherent response.",
        strings.Join(results, "\n\n"),
    )
}

Pattern 4: Conditional Tool Usage ​

Use tools based on conditions:

go
handler := func(ctx context.Context, input string, capabilities *v1beta.Capabilities) (string, error) {
    // Check if input requires external data
    needsData, err := capabilities.LLM(
        "Does this query require external data? Reply YES or NO only.",
        input,
    )
    if err != nil {
        return "", err
    }
    
    if strings.TrimSpace(needsData) == "YES" {
        // Use tools to fetch data
        result, err := capabilities.Tools.Execute(ctx, "web_search", map[string]interface{}{
            "query": input,
        })
        if err != nil {
            return "", err
        }
        
        // Use LLM with tool data
        return capabilities.LLM(
            fmt.Sprintf("Use this data to answer: %v", result.Content),
            input,
        )
    }
    
    // Use LLM directly
    return capabilities.LLM("You are a helpful assistant.", input)
}

🎯 Best Practices ​

1. Error Handling ​

Always handle tool errors gracefully:

go
result, err := capabilities.Tools.Execute(ctx, "web_search", args)
if err != nil {
    // Log error
    log.Printf("Tool execution failed: %v", err)
    
    // Provide fallback
    return capabilities.LLM("Answer without external data.", input)
}

if !result.Success {
    // Handle tool-specific errors
    log.Printf("Tool returned error: %s", result.Error)
    return "Tool encountered an issue. Please try again.", nil
}

2. Timeout Configuration ​

Set appropriate timeouts:

go
// Quick operations
v1beta.WithToolTimeout(5 * time.Second)

// Standard operations
v1beta.WithToolTimeout(30 * time.Second)

// Long-running operations
v1beta.WithToolTimeout(2 * time.Minute)

3. Caching Strategy ​

Choose TTL based on data freshness requirements:

toml
[tools.cache.tool_ttls]
stock_price = "30s"        # Real-time data
weather = "15m"            # Changes frequently
wikipedia = "24h"          # Relatively static
company_info = "7d"        # Rarely changes

4. Rate Limiting ​

Respect API limits:

toml
[tools]
rate_limit = 10  # 10 requests per second
max_concurrent = 3  # Max 3 parallel executions

5. Monitoring ​

Regularly check tool health:

go
// Periodic health check
go func() {
    ticker := time.NewTicker(5 * time.Minute)
    for range ticker.C {
        health := capabilities.Tools.HealthCheck(ctx)
        for name, status := range health {
            if status.Status != "healthy" {
                log.Printf("Unhealthy tool server: %s - %s", name, status.Error)
            }
        }
    }
}()

🐛 Troubleshooting ​

Issue: MCP Server Not Connecting ​

Cause: Server not running or wrong configuration

Solution: Verify server configuration

bash
# Test server manually
curl http://localhost:8080/health

# Check logs
cat /var/log/mcp-server.log

Issue: Tools Not Discovered ​

Cause: Discovery disabled or ports not scanned

Solution: Enable discovery explicitly

go
agent, _ := v1beta.NewBuilder("Agent").
    WithTools(
        v1beta.WithMCPDiscovery(8080, 8081, 8090),
    ).
    Build()

Issue: Tool Execution Timeout ​

Cause: Operation taking too long

Solution: Increase timeout

go
agent, _ := v1beta.NewBuilder("Agent").
    WithTools(
        v1beta.WithMCP(servers...),
        v1beta.WithToolTimeout(60 * time.Second),
    ).
    Build()

📚 Next Steps ​


Ready for error handling? Continue to Error Handling →

Released under the Apache 2.0 License.