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
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:
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:
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:
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:
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:
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:
agent, _ := v1beta.NewBuilder("DiscoveryAgent").
WithLLM("openai", "gpt-4").
WithTools(
v1beta.WithMCPDiscovery(8080, 8081, 8090, 8100), // Scan these ports
).
Build()Configuration:
// 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:
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
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
[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
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
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
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
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
[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 APIsCache Backend Options
[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:
[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 stateStates:
- 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
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
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:
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:
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:
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:
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:
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:
// 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:
[tools.cache.tool_ttls]
stock_price = "30s" # Real-time data
weather = "15m" # Changes frequently
wikipedia = "24h" # Relatively static
company_info = "7d" # Rarely changes4. Rate Limiting
Respect API limits:
[tools]
rate_limit = 10 # 10 requests per second
max_concurrent = 3 # Max 3 parallel executions5. Monitoring
Regularly check tool health:
// 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
# Test server manually
curl http://localhost:8080/health
# Check logs
cat /var/log/mcp-server.logIssue: Tools Not Discovered
Cause: Discovery disabled or ports not scanned
Solution: Enable discovery explicitly
agent, _ := v1beta.NewBuilder("Agent").
WithTools(
v1beta.WithMCPDiscovery(8080, 8081, 8090),
).
Build()Issue: Tool Execution Timeout
Cause: Operation taking too long
Solution: Increase timeout
agent, _ := v1beta.NewBuilder("Agent").
WithTools(
v1beta.WithMCP(servers...),
v1beta.WithToolTimeout(60 * time.Second),
).
Build()📚 Next Steps
- Custom Handlers - Advanced tool integration patterns
- Error Handling - Robust error management
- Performance - Tool optimization strategies
- Examples - Complete tool integration examples
Ready for error handling? Continue to Error Handling →