Documentation Index
Fetch the complete documentation index at: https://www.getmaxim.ai/docs/llms.txt
Use this file to discover all available pages before exploring further.
MCP Overview
MCP (Model Context Protocol) enables AI models to interact with external tools and services. Bifrost’s MCP integration provides:- Automatic tool discovery from external MCP servers
- Built-in tool execution with proper error handling
- Custom tool registration for in-process tools
- Multiple connection types (HTTP, STDIO, SSE)
// Configure MCP during initialization
client, initErr := bifrost.Init(schemas.BifrostConfig{
Account: &MyAccount{},
MCPConfig: &schemas.MCPConfig{
ClientConfigs: []schemas.MCPClientConfig{
{
Name: "filesystem-tools",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-filesystem"},
},
},
},
},
})
Basic MCP Configuration
STDIO Connection (Most Common)
Connect to MCP servers via standard input/output:func setupMCPClient() *schemas.MCPConfig {
return &schemas.MCPConfig{
ClientConfigs: []schemas.MCPClientConfig{
{
Name: "filesystem-tools",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-filesystem"},
Envs: []string{"FILESYSTEM_ROOT"},
},
},
{
Name: "web-search",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "python",
Args: []string{"-m", "web_search_mcp"},
Envs: []string{"SEARCH_API_KEY"},
},
},
},
}
}
// Set environment variables
os.Setenv("FILESYSTEM_ROOT", "/safe/directory")
os.Setenv("SEARCH_API_KEY", "your-search-api-key")
client, initErr := bifrost.Init(schemas.BifrostConfig{
Account: &MyAccount{},
MCPConfig: setupMCPClient(),
})
HTTP Connection
Connect to MCP servers via HTTP:func setupHTTPMCP() *schemas.MCPConfig {
endpoint := "http://localhost:8080/mcp"
return &schemas.MCPConfig{
ClientConfigs: []schemas.MCPClientConfig{
{
Name: "database-tools",
ConnectionType: schemas.MCPConnectionTypeHTTP,
ConnectionString: &endpoint,
},
},
}
}
SSE Connection
Connect to MCP servers via Server-Sent Events:func setupSSEMCP() *schemas.MCPConfig {
sseEndpoint := "http://localhost:8080/mcp/sse"
return &schemas.MCPConfig{
ClientConfigs: []schemas.MCPClientConfig{
{
Name: "realtime-data",
ConnectionType: schemas.MCPConnectionTypeSSE,
ConnectionString: &sseEndpoint,
},
},
}
}
Using MCP Tools
Automatic Tool Integration
MCP tools are automatically added to all requests:// Tools from MCP servers are automatically available
response, err := client.ChatCompletionRequest(context.Background(), &schemas.BifrostRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o-mini",
Input: schemas.RequestInput{
ChatCompletionInput: &[]schemas.BifrostMessage{
{
Role: schemas.ModelChatMessageRoleUser,
Content: schemas.MessageContent{ContentStr: &message},
},
},
},
// No need to specify tools - MCP tools are automatically included
})
// Check if model used any tools
if len(response.Choices) > 0 && response.Choices[0].Message.ToolCalls != nil {
fmt.Printf("Model used %d tools\n", len(*response.Choices[0].Message.ToolCalls))
}
Manual Tool Execution
Execute MCP tools directly for security and control. You can pass tool calls directly from assistant message responses:// Option 1: Use tool calls from assistant response
response, err := client.ChatCompletionRequest(ctx, request)
if err != nil {
return err
}
// Execute each tool call from the assistant's response
if len(response.Choices) > 0 && response.Choices[0].Message.ToolCalls != nil {
for _, toolCall := range *response.Choices[0].Message.ToolCalls {
// Execute the tool call directly - gives you full control for security
toolResult, err := client.ExecuteMCPTool(context.Background(), toolCall)
if err != nil {
log.Printf("Tool execution failed: %v", err)
continue
}
// Process result as needed
if toolResult.Content.ContentStr != nil {
fmt.Printf("Tool result: %s\n", *toolResult.Content.ContentStr)
}
}
}
// Option 2: Create custom tool calls
toolCall := schemas.ToolCall{
ID: &[]string{"call_123"}[0],
Type: &[]string{"function"}[0],
Function: schemas.FunctionCall{
Name: &[]string{"read_file"}[0],
Arguments: `{"path": "/path/to/file.txt"}`,
},
}
// Execute the tool manually
toolResult, err := client.ExecuteMCPTool(context.Background(), toolCall)
if err != nil {
log.Printf("Tool execution failed: %v", err)
return
}
// Use the result
if toolResult.Content.ContentStr != nil {
fmt.Printf("Tool result: %s\n", *toolResult.Content.ContentStr)
}
Security Note: Manual execution gives you full control over tool calls. This allows you to validate arguments, implement access controls, and audit tool usage before execution.
Custom Tool Registration
Register In-Process Tools
Register custom tools that run within your application:// Define your tool function
func echoTool(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid arguments")
}
message, ok := argsMap["message"].(string)
if !ok {
return "", fmt.Errorf("message parameter required")
}
return fmt.Sprintf("Echo: %s", message), nil
}
// Define tool schema
echoToolSchema := schemas.Tool{
Type: "function",
Function: schemas.Function{
Name: "echo",
Description: "Echo a message back to the user",
Parameters: schemas.FunctionParameters{
Type: "object",
Properties: map[string]interface{}{
"message": map[string]interface{}{
"type": "string",
"description": "Message to echo back",
},
},
Required: []string{"message"},
},
},
}
// Register the tool
err := client.RegisterMCPTool("echo", "Echo a message", echoTool, echoToolSchema)
if err != nil {
log.Printf("Failed to register tool: %v", err)
}
// Now the tool is available to all AI requests
Advanced Custom Tools
More complex tools with error handling and validation:// Database query tool
func databaseQueryTool(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid arguments")
}
query, ok := argsMap["query"].(string)
if !ok {
return "", fmt.Errorf("query parameter required")
}
// Validate query (prevent dangerous operations)
if strings.Contains(strings.ToLower(query), "drop") ||
strings.Contains(strings.ToLower(query), "delete") ||
strings.Contains(strings.ToLower(query), "update") {
return "", fmt.Errorf("only SELECT queries are allowed")
}
// Execute query (pseudo-code)
db := getDatabase()
rows, err := db.Query(query)
if err != nil {
return "", fmt.Errorf("query failed: %w", err)
}
defer rows.Close()
// Format results as JSON
results := []map[string]interface{}{}
for rows.Next() {
// Scan row data...
row := map[string]interface{}{
"id": 1,
"name": "example",
}
results = append(results, row)
}
jsonData, _ := json.Marshal(results)
return string(jsonData), nil
}
// Register database tool
dbToolSchema := schemas.Tool{
Type: "function",
Function: schemas.Function{
Name: "database_query",
Description: "Execute a safe SELECT query on the database",
Parameters: schemas.FunctionParameters{
Type: "object",
Properties: map[string]interface{}{
"query": map[string]interface{}{
"type": "string",
"description": "SQL SELECT query to execute",
},
},
Required: []string{"query"},
},
},
}
err := client.RegisterMCPTool("database_query", "Query database", databaseQueryTool, dbToolSchema)
Tool Discovery and Filtering
Tool Filtering by Client (Config Level)
Control which tools from each MCP client are available at the configuration level:mcpConfig := &schemas.MCPConfig{
ClientConfigs: []schemas.MCPClientConfig{
{
Name: "filesystem-tools",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-filesystem"},
},
// Whitelist approach: Only allow specific tools
ToolsToExecute: []string{"read_file", "list_directory"},
},
{
Name: "web-tools",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-web"},
},
// Blacklist approach: Block dangerous tools
ToolsToSkip: []string{"delete_page", "modify_content"},
},
},
}
Filtering Rules:
ToolsToExecute: Whitelist - only these tools are available (overrides ToolsToSkip)ToolsToSkip: Blacklist - all tools except these are available- If both are specified,
ToolsToExecutetakes precedence
Context-Based Tool Filtering (Request Level)
Filter tools at runtime for specific requests using context keys:import "context"
// Whitelist specific clients (only these clients' tools will be available)
ctx := context.WithValue(context.Background(), "mcp-include-clients", []string{"filesystem-tools", "database-client"})
response, err := client.ChatCompletionRequest(ctx, &schemas.BifrostRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o-mini",
Input: input, // your input here
})
// Blacklist specific clients (all tools except these clients' tools will be available)
ctx = context.WithValue(context.Background(), "mcp-exclude-clients", []string{"web-tools", "admin-tools"})
response, err = client.ChatCompletionRequest(ctx, &schemas.BifrostRequest{
Provider: schemas.Anthropic,
Model: "claude-3-sonnet-20240229",
Input: input, // your input here
})
// Combine both approaches for fine-grained control
func createRestrictedContext() context.Context {
ctx := context.Background()
// Only allow safe tools for user-facing operations
ctx = context.WithValue(ctx, "mcp-include-clients", []string{"search-tools", "calculator"})
return ctx
}
// Use in production
userCtx := createRestrictedContext()
response, err := client.ChatCompletionRequest(userCtx, userRequest)
Context Filtering Rules:
mcp-include-clients: Whitelist - only tools from these named MCP clients are availablemcp-exclude-clients: Blacklist - tools from these named MCP clients are excluded- If both are specified,
mcp-include-clientstakes precedence Similarly you can pass values formcp-include-toolsandmcp-exclude-toolsto filter tools at runtime.- These filters work at runtime and can be different for each request
- Useful for user-based permissions, request-specific security, or A/B testing different tool sets
Dynamic Client Management
Adding Clients at Runtime
Add new MCP clients after initialization:// Add a new filesystem client
newClientConfig := schemas.MCPClientConfig{
Name: "new-filesystem",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-filesystem"},
Envs: []string{"FILESYSTEM_ROOT"},
},
}
err := client.AddMCPClient(newClientConfig)
if err != nil {
log.Printf("Failed to add MCP client: %v", err)
}
// Add HTTP client
httpEndpoint := "http://localhost:8080/mcp"
httpClientConfig := schemas.MCPClientConfig{
Name: "api-tools",
ConnectionType: schemas.MCPConnectionTypeHTTP,
ConnectionString: &httpEndpoint,
}
err = client.AddMCPClient(httpClientConfig)
if err != nil {
log.Printf("Failed to add HTTP MCP client: %v", err)
}
Removing Clients
Remove MCP clients when no longer needed:// Remove a specific client
err := client.RemoveMCPClient("filesystem-tools")
if err != nil {
log.Printf("Failed to remove MCP client: %v", err)
}
// The client will be disconnected and its tools will no longer be available
Editing Client Tool Filters
Dynamically modify which tools are available from a client:// Allow only specific tools from a client
toolsToAdd := []string{"read_file", "list_directory"}
toolsToRemove := []string{"write_file", "delete_file"}
err := client.EditMCPClientTools("filesystem-tools", toolsToAdd, toolsToRemove)
if err != nil {
log.Printf("Failed to edit client tools: %v", err)
}
// Clear all restrictions (allow all tools)
err = client.EditMCPClientTools("filesystem-tools", []string{}, []string{})
if err != nil {
log.Printf("Failed to clear tool restrictions: %v", err)
}
// Block specific dangerous tools
dangerousTools := []string{"delete_file", "format_disk", "system_shutdown"}
err = client.EditMCPClientTools("filesystem-tools", []string{}, dangerousTools)
if err != nil {
log.Printf("Failed to block dangerous tools: %v", err)
}
Listing All Clients
Get information about all connected MCP clients:import "strings"
clients, err := client.GetMCPClients()
if err != nil {
log.Printf("Failed to get MCP clients: %v", err)
return
}
for _, mcpClient := range clients {
fmt.Printf("Client: %s\n", mcpClient.Name)
fmt.Printf(" Type: %s\n", mcpClient.Config.ConnectionType)
fmt.Printf(" State: %s\n", mcpClient.State)
if mcpClient.Config.ConnectionString != nil {
fmt.Printf(" URL: %s\n", *mcpClient.Config.ConnectionString)
}
if mcpClient.Config.StdioConfig != nil {
cmdString := fmt.Sprintf("%s %s", mcpClient.Config.StdioConfig.Command,
strings.Join(mcpClient.Config.StdioConfig.Args, " "))
fmt.Printf(" Command: %s\n", cmdString)
}
fmt.Printf(" Tools: %d available\n", len(mcpClient.Tools))
for _, toolName := range mcpClient.Tools {
fmt.Printf(" - %s\n", toolName)
}
fmt.Println()
}
// Filter clients by connection state
connectedClients := []schemas.MCPClient{}
disconnectedClients := []schemas.MCPClient{}
for _, mcpClient := range clients {
if mcpClient.State == schemas.MCPConnectionStateConnected {
connectedClients = append(connectedClients, mcpClient)
} else {
disconnectedClients = append(disconnectedClients, mcpClient)
}
}
fmt.Printf("Connected clients: %d\n", len(connectedClients))
fmt.Printf("Disconnected clients: %d\n", len(disconnectedClients))
Reconnecting Disconnected Clients
Reconnect clients that have lost their connection:// Reconnect a specific client
err := client.ReconnectMCPClient("web-search")
if err != nil {
log.Printf("Failed to reconnect MCP client: %v", err)
}
// Check client status before reconnecting
clients, err := client.GetMCPClients()
if err == nil {
for _, mcpClient := range clients {
if mcpClient.State != schemas.MCPConnectionStateConnected {
fmt.Printf("Client %s is %s, attempting reconnect...\n", mcpClient.Name, mcpClient.State)
if err := client.ReconnectMCPClient(mcpClient.Name); err != nil {
log.Printf("Failed to reconnect %s: %v", mcpClient.Name, err)
} else {
fmt.Printf("Successfully reconnected %s\n", mcpClient.Name)
}
}
}
}
Dynamic Client Management Example
Complete example showing dynamic client lifecycle:func manageMCPClients(client *bifrost.Bifrost) {
// 1. Add clients based on runtime conditions
if needsFileAccess() {
fileClientConfig := schemas.MCPClientConfig{
Name: "runtime-filesystem",
ConnectionType: schemas.MCPConnectionTypeSTDIO,
StdioConfig: &schemas.MCPStdioConfig{
Command: "npx",
Args: []string{"-y", "@modelcontextprotocol/server-filesystem"},
Envs: []string{"FILESYSTEM_ROOT"},
},
}
if err := client.AddMCPClient(fileClientConfig); err != nil {
log.Printf("Failed to add filesystem client: %v", err)
}
}
// 2. Configure tool access based on user permissions
if isRestrictedUser() {
// Only allow safe read operations
safeTools := []string{"read_file", "list_directory"}
err := client.EditMCPClientTools("runtime-filesystem", safeTools, []string{})
if err != nil {
log.Printf("Failed to restrict tools: %v", err)
}
}
// 3. Monitor and maintain connections
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
clients, err := client.GetMCPClients()
if err != nil {
continue
}
for _, mcpClient := range clients {
if mcpClient.State != schemas.MCPConnectionStateConnected {
log.Printf("Detected disconnected client: %s (state: %s)", mcpClient.Name, mcpClient.State)
if err := client.ReconnectMCPClient(mcpClient.Name); err != nil {
log.Printf("Failed to reconnect %s: %v", mcpClient.Name, err)
}
}
}
}
}()
// 4. Cleanup when done
defer func() {
// Remove temporary clients
if err := client.RemoveMCPClient("runtime-filesystem"); err != nil {
log.Printf("Failed to remove temporary client: %v", err)
}
}()
}
func needsFileAccess() bool {
// Your logic to determine if file access is needed
return true
}
func isRestrictedUser() bool {
// Your logic to determine user permissions
return false
}
🔄 Multi-Turn Tool Conversations
Handling Tool Call Loops
Implement proper tool calling conversations:func handleToolConversation(client *bifrost.Bifrost, initialMessage string) {
conversation := []schemas.BifrostMessage{
{
Role: schemas.ModelChatMessageRoleUser,
Content: schemas.MessageContent{ContentStr: &initialMessage},
},
}
maxTurns := 10
for turn := 0; turn < maxTurns; turn++ {
response, err := client.ChatCompletionRequest(context.Background(), &schemas.BifrostRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o-mini",
Input: schemas.RequestInput{
ChatCompletionInput: &conversation,
},
})
if err != nil {
log.Printf("Request failed: %v", err)
return
}
choice := response.Choices[0]
// Add assistant's response to conversation
conversation = append(conversation, choice.Message)
// Check if model wants to call tools
if choice.Message.ToolCalls != nil {
// Execute all tool calls
for _, toolCall := range *choice.Message.ToolCalls {
toolResult, err := client.ExecuteMCPTool(context.Background(), toolCall)
if err != nil {
log.Printf("Tool execution failed: %v", err)
continue
}
// Add tool result to conversation
conversation = append(conversation, *toolResult)
}
// Continue conversation with tool results
continue
}
// No more tool calls - conversation is complete
if choice.Message.Content.ContentStr != nil {
fmt.Printf("Final response: %s\n", *choice.Message.Content.ContentStr)
}
break
}
}
// Usage
handleToolConversation(client, "Analyze the files in the current directory and summarize what the project does")
📊 MCP Monitoring and Debugging
Tool Execution Monitoring
Track tool usage and performance:type MCPMonitoringPlugin struct {
toolCalls map[string]int
errors map[string]int
mu sync.RWMutex
}
func (p *MCPMonitoringPlugin) PreHook(ctx *context.Context, req *schemas.BifrostRequest) (*schemas.BifrostRequest, *schemas.PluginShortCircuit, error) {
// Add monitoring context
*ctx = context.WithValue(*ctx, "mcp_monitor_start", time.Now())
return req, nil, nil
}
func (p *MCPMonitoringPlugin) PostHook(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) (*schemas.BifrostResponse, *schemas.BifrostError, error) {
if result != nil && len(result.Choices) > 0 && result.Choices[0].Message.ToolCalls != nil {
p.mu.Lock()
for _, toolCall := range *result.Choices[0].Message.ToolCalls {
if toolCall.Function.Name != nil {
p.toolCalls[*toolCall.Function.Name]++
}
}
p.mu.Unlock()
}
return result, err, nil
}
// Get monitoring data
func (p *MCPMonitoringPlugin) GetToolStats() map[string]int {
p.mu.RLock()
defer p.mu.RUnlock()
stats := make(map[string]int)
for tool, count := range p.toolCalls {
stats[tool] = count
}
return stats
}
Debug Tool Execution
Enable detailed logging for MCP operations:// Create logger that shows MCP operations
logger := log.New(os.Stdout, "[MCP] ", log.LstdFlags|log.Lshortfile)
client, initErr := bifrost.Init(schemas.BifrostConfig{
Account: &MyAccount{},
Logger: customLogger, // Use custom logger for MCP debug info
MCPConfig: mcpConfig,
})
// MCP operations will be logged with detailed information
🧪 Testing MCP Integration
Unit Testing Custom Tools
Test your custom tools in isolation:func TestEchoTool(t *testing.T) {
args := map[string]interface{}{
"message": "Hello, World!",
}
result, err := echoTool(args)
assert.NoError(t, err)
assert.Equal(t, "Echo: Hello, World!", result)
// Test error case
invalidArgs := map[string]interface{}{
"wrong_param": "value",
}
_, err = echoTool(invalidArgs)
assert.Error(t, err)
assert.Contains(t, err.Error(), "message parameter required")
}
Integration Testing with MCP
Test MCP integration with real tools:func TestMCPIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping MCP integration test")
}
// Setup MCP client with echo tool
client, initErr := bifrost.Init(schemas.BifrostConfig{
Account: &TestAccount{},
MCPConfig: &schemas.MCPConfig{
ClientConfigs: []schemas.MCPClientConfig{
// Configure test MCP server
},
},
})
require.Nil(t, initErr)
defer client.Cleanup()
// Register test tool
err = client.RegisterMCPTool("test_echo", "Test echo", echoTool, echoToolSchema)
require.NoError(t, err)
// Test tool is available in requests
message := "Use the echo tool to repeat this message"
response, err := client.ChatCompletionRequest(context.Background(), &schemas.BifrostRequest{
Provider: schemas.OpenAI,
Model: "gpt-4o-mini",
Input: schemas.RequestInput{
ChatCompletionInput: &[]schemas.BifrostMessage{
{Role: schemas.ModelChatMessageRoleUser, Content: schemas.MessageContent{ContentStr: &message}},
},
},
})
assert.NoError(t, err)
assert.NotNil(t, response)
// Check if tool was called
if len(response.Choices) > 0 && response.Choices[0].Message.ToolCalls != nil {
foundEchoTool := false
for _, toolCall := range *response.Choices[0].Message.ToolCalls {
if toolCall.Function.Name != nil && *toolCall.Function.Name == "test_echo" {
foundEchoTool = true
break
}
}
assert.True(t, foundEchoTool, "Echo tool should have been called")
}
}
Related Documentation
- Bifrost Client - Using MCP with client requests
- Plugins - MCP monitoring plugins
- Schemas - MCP configuration structures
- HTTP Transport - MCP configuration via JSON
Architecture: For MCP system design and integration details, see Architecture Documentation.