Overview

Model Context Protocol (MCP) enables AI models to seamlessly discover and execute external tools at runtime, transforming static chat models into dynamic, action-capable agents. Instead of being limited to text generation, AI models can interact with filesystems, search the web, query databases, and execute custom business logic through external MCP servers. Bifrost’s MCP integration provides a secure, high-performance bridge between AI models and external tools, with client-side control over all tool execution and granular filtering capabilities. 🔒 Security-First Design: Bifrost never automatically executes tool calls. Instead, it provides APIs for explicit tool execution, ensuring human oversight and approval for all potentially dangerous operations.

Key Benefits

FeatureDescription
Dynamic DiscoveryTools are discovered at runtime from external MCP servers
Stateless DesignIndependent API calls with no session state management
Client-Side ControlBifrost manages all tool execution for security and observability
Multiple ProtocolsSTDIO, HTTP, and SSE connection types
Granular FilteringControl tool availability per request and client
High PerformanceAsync execution with minimal latency overhead
Copy-Pastable ResponsesTool results designed for seamless conversation assembly

How MCP Works in Bifrost

Bifrost acts as an MCP client that connects to external MCP servers hosting tools. The integration is completely stateless with independent API calls:
  1. Discovery: Bifrost connects to configured MCP servers and discovers available tools
  2. Integration: Tools are automatically added to the AI model’s function calling schema
  3. Suggestion: Chat completion requests return tool call suggestions (not executed)
  4. Execution: Separate tool execution API calls execute specific tool calls
  5. Assembly: Your application manages conversation state and assembles chat history
  6. Continuation: Follow-up chat requests use the complete conversation history
Stateless Tool Flow:
Chat Request → Tool Call Suggestions (Independent)

Tool Execution Request → Tool Results (Independent)  

Your App Assembles History → Continue Chat (Independent)
Bifrost never automatically executes tool calls. All API calls are independent and stateless:
  • Chat completions return tool call suggestions without executing them
  • Tool execution requires separate API calls with explicit tool call data
  • No state management - your application controls conversation flow
  • Copy-pastable responses designed for easy conversation assembly
This design prevents:
  • Unintended API calls to external services
  • Accidental data modification or deletion
  • Execution of potentially harmful commands
Implementation Pattern:
1. POST /v1/chat/completions → Get tool call suggestions (stateless)
2. Your App Reviews Tool Calls → Decides which to execute
3. POST /v1/mcp/tool/execute → Execute specific tool calls (stateless)  
4. Your App Assembles History → Continue with complete conversation
This stateless pattern ensures explicit control over all tool operations while providing responses optimized for conversation continuity.

Setup Guides

Go SDK Setup

Configure MCP in your Bifrost initialization:
package main

import (
    "github.com/maximhq/bifrost/core"
    "github.com/maximhq/bifrost/core/schemas"
)

func main() {
    mcpConfig := &schemas.MCPConfig{
        ClientConfigs: []schemas.MCPClientConfig{
            {
                Name:           "filesystem-tools",
                ConnectionType: schemas.MCPConnectionTypeSTDIO,
                StdioConfig: &schemas.MCPStdioConfig{
                    Command: "node",
                    Args:    []string{"filesystem-mcp-server.js"},
                },
            },
            {
                Name:             "web-search",
                ConnectionType:   schemas.MCPConnectionTypeHTTP,
                ConnectionString: bifrost.Ptr("http://localhost:3001/mcp"),
            },
        },
    }

    client, err := bifrost.Init(schemas.BifrostConfig{
        Account:   account,
        MCPConfig: mcpConfig,
        Logger:    bifrost.NewDefaultLogger(schemas.LogLevelInfo),
    })
}

Gateway Setup

MCP Configuration in Web UI
  1. Navigate to MCP Clients in the Bifrost Gateway UI
  2. Click Add MCP Client
  3. Configure connection details:
    • Name: Unique identifier for the MCP client
    • Connection Type: STDIO, HTTP, or SSE
    • Connection Details: Command/URL based on connection type
    • Tool Filtering: Optional whitelist/blacklist of tools
The UI automatically validates configurations and shows connection status in real-time.

Connection Types

STDIO Connection

STDIO connections launch external processes and communicate via standard input/output. Best for local tools and scripts. Configuration:
{
  "name": "local-tools",
  "connection_type": "stdio", 
  "stdio_config": {
    "command": "python",
    "args": ["-m", "my_mcp_server"],
    "envs": ["PYTHON_PATH", "API_KEY"]
  }
}
Use Cases:
  • Local filesystem operations
  • Database queries with local credentials
  • Python/Node.js MCP servers
  • Custom business logic scripts

HTTP Connection

HTTP connections communicate with MCP servers via HTTP requests. Ideal for remote services and microservices. Configuration:
{
  "name": "remote-api",
  "connection_type": "http",
  "connection_string": "https://mcp-server.example.com/api"
}
Use Cases:
  • Remote API integrations
  • Cloud-hosted MCP services
  • Microservice architectures
  • Third-party tool providers

SSE Connection

Server-Sent Events (SSE) connections provide real-time, persistent connections to MCP servers. Best for streaming data and live updates. Configuration:
{
  "name": "live-data",
  "connection_type": "sse",
  "connection_string": "https://stream.example.com/mcp/events"
}
Use Cases:
  • Real-time market data
  • Live system monitoring
  • Streaming analytics
  • Event-driven workflows

End-to-End Tool Calling

Complete tool calling workflow with the Go SDK:
package main

import (
    "context"
    "fmt"
    "github.com/maximhq/bifrost/core"
    "github.com/maximhq/bifrost/core/schemas"
)

func main() {
    // Initialize Bifrost with MCP
    client, err := bifrost.Init(schemas.BifrostConfig{
        Account: account,
        MCPConfig: &schemas.MCPConfig{
            ClientConfigs: []schemas.MCPClientConfig{
                {
                    Name:           "filesystem",
                    ConnectionType: schemas.MCPConnectionTypeSTDIO,
                    StdioConfig: &schemas.MCPStdioConfig{
                        Command: "node",
                        Args:    []string{"fs-mcp-server.js"},
                    },
                },
            },
        },
    })

    firstMessage := schemas.BifrostMessage{
        Role: schemas.ModelChatMessageRoleUser,
        Content: schemas.MessageContent{
            ContentStr: bifrost.Ptr("Read the contents of config.json file"),
        },
    }

    // Create request with tools automatically included
    request := &schemas.BifrostRequest{
        Provider: schemas.OpenAI,
        Model:    "gpt-4o-mini",
        Input: schemas.RequestInput{
            ChatCompletionInput: &[]schemas.BifrostMessage{
                firstMessage,
            },
        },
        Params: &schemas.ModelParameters{
            Temperature: bifrost.Ptr(0.7),
        },
    }

    // Send chat completion request - MCP tools are automatically available  
    response, err := client.ChatCompletionRequest(context.Background(), request)
    if err != nil {
        panic(err)
    }

    // Build conversation history for final response
    conversationHistory := []schemas.BifrostMessage{
        firstMessage,
    }

    // Handle tool calls in response (suggestions only - not executed)
    if response.Choices[0].Message.ToolCalls != nil {
        secondMessage := response.Choices[0].Message
        
        // Add assistant message with tool calls to history
        conversationHistory = append(conversationHistory, secondMessage)

        for _, toolCall := range *secondMessage.ToolCalls {
            fmt.Printf("Tool suggested: %s\n", *toolCall.Function.Name)
            
            // YOUR APPLICATION DECISION: Review the tool call
            // - Validate tool name and arguments
            // - Apply security and business rules  
            // - Check permissions and rate limits
            // - Decide whether to execute
            
            shouldExecute := validateToolCall(toolCall) // Your validation logic
            if !shouldExecute {
                fmt.Printf("Tool call rejected by application\n")
                continue
            }
            
            // EXPLICIT EXECUTION: Separate API call
            thirdMessage, err := client.ExecuteMCPTool(context.Background(), toolCall)
            if err != nil {
                fmt.Printf("Tool execution failed: %v\n", err)
                continue
            }
            
            fmt.Printf("Tool result: %s\n", *thirdMessage.Content.ContentStr)
            
            // Add tool result to conversation history
            conversationHistory = append(conversationHistory, thirdMessage)
        }

        // Send complete conversation history for final response
        finalRequest := &schemas.BifrostRequest{
            Provider: schemas.OpenAI,
            Model:    "gpt-4o-mini",
            Input: schemas.RequestInput{
                ChatCompletionInput: &conversationHistory,
            },
            Params: &schemas.ModelParameters{
                Temperature: bifrost.Ptr(0.7),
            },
        }

        finalResponse, err := client.ChatCompletionRequest(context.Background(), finalRequest)
        if err != nil {
            panic(err)
        }

        fmt.Printf("Final response: %s\n", *finalResponse.Choices[0].Message.Content.ContentStr)
    }
}

Tool Registry (Go SDK Only)

The Go SDK provides a powerful tool registry for hosting custom tools directly within your application using typed handlers.

Registering Typed Tools

package main

import (
    "fmt"
    "strings"
    "github.com/maximhq/bifrost/core"
    "github.com/maximhq/bifrost/core/schemas"
)

// Define typed arguments for your tool
type CalculatorArgs struct {
    Operation string  `json:"operation"` // add, subtract, multiply, divide
    A         float64 `json:"a"`
    B         float64 `json:"b"`
}

// Define typed tool handler
func calculatorHandler(args CalculatorArgs) (string, error) {
    switch strings.ToLower(args.Operation) {
    case "add":
        return fmt.Sprintf("%.2f", args.A + args.B), nil
    case "subtract": 
        return fmt.Sprintf("%.2f", args.A - args.B), nil
    case "multiply":
        return fmt.Sprintf("%.2f", args.A * args.B), nil
    case "divide":
        if args.B == 0 {
            return "", fmt.Errorf("cannot divide by zero")
        }
        return fmt.Sprintf("%.2f", args.A / args.B), nil
    default:
        return "", fmt.Errorf("unsupported operation: %s", args.Operation)
    }
}

func main() {
    // Initialize Bifrost (tool registry creates in-process MCP automatically)
    client, err := bifrost.Init(schemas.BifrostConfig{
        Account: account,
        Logger:  bifrost.NewDefaultLogger(schemas.LogLevelInfo),
    })

    // Define tool schema
    calculatorSchema := schemas.Tool{
        Type: "function",
        Function: schemas.Function{
            Name:        "calculator",
            Description: "Perform basic arithmetic operations",
            Parameters: schemas.FunctionParameters{
                Type: "object",
                Properties: map[string]interface{}{
                    "operation": map[string]interface{}{
                        "type":        "string",
                        "description": "The operation to perform",
                        "enum":        []string{"add", "subtract", "multiply", "divide"},
                    },
                    "a": map[string]interface{}{
                        "type":        "number", 
                        "description": "First number",
                    },
                    "b": map[string]interface{}{
                        "type":        "number",
                        "description": "Second number",
                    },
                },
                Required: []string{"operation", "a", "b"},
            },
        },
    }

    // Register the typed tool
    err = client.RegisterMCPTool("calculator", "Perform arithmetic calculations", 
        func(args any) (string, error) {
            // Convert args to typed struct
            calculatorArgs := CalculatorArgs{}
            if jsonBytes, err := json.Marshal(args); err == nil {
                json.Unmarshal(jsonBytes, &calculatorArgs)
            }
            return calculatorHandler(calculatorArgs)
        }, calculatorSchema)

    if err != nil {
        panic(fmt.Sprintf("Failed to register tool: %v", err))
    }

    // Now use the tool in requests
    request := &schemas.BifrostRequest{
        Provider: schemas.OpenAI,
        Model:    "gpt-4o-mini",
        Input: schemas.RequestInput{
            ChatCompletionInput: &[]schemas.BifrostMessage{
                {
                    Role: schemas.ModelChatMessageRoleUser,
                    Content: schemas.MessageContent{
                        ContentStr: bifrost.Ptr("Calculate 15.5 + 24.3"),
                    },
                },
            },
        },
        Params: &schemas.ModelParameters{
            Temperature: bifrost.Ptr(0.7),
        },
    }

    response, err := client.ChatCompletionRequest(context.Background(), request)
    // The model can now use the calculator tool automatically
}

Tool Registry Benefits

  • Type Safety: Compile-time checking of tool arguments and return types
  • Performance: In-process execution with zero network overhead
  • Simplicity: No external MCP server setup required
  • Integration: Tools are automatically available to all AI requests
  • Error Handling: Structured error responses with detailed context

Advanced Configuration

Tool and Client Filtering

Control which tools and clients are available per request or globally: Request-Level Filtering:
Use context values to filter clients and tools per request:
// Include only specific clients
ctx := context.WithValue(context.Background(), "mcp-include-clients", []string{"filesystem", "web-search"})

// Exclude specific tools  
ctx = context.WithValue(ctx, "mcp-exclude-tools", "debug_tool,internal_tool")

// Include only specific tools
ctx = context.WithValue(ctx, "mcp-include-tools", "search,read_file")

// Exclude specific clients
ctx = context.WithValue(ctx, "mcp-exclude-clients", "admin-tools")

response, err := client.ChatCompletionRequest(ctx, request)
Filtering Priority Rules:
  1. Request-Level vs Config-Level: Request-level filtering (context/headers) takes priority over config-level filtering and can override it
  2. Include vs Exclude Priority:
    • Include lists are strict whitelists: If include-clients/include-tools is specified, ONLY those clients/tools are allowed
    • Whitelist priority: When both include and exclude are specified and a tool/client is in both, include takes priority
Client Configuration Filtering:
{
  "name": "external-api",
  "connection_type": "http",
  "connection_string": "https://api.example.com/mcp",
  "tools_to_execute": ["search", "summarize"],      // Whitelist specific tools
  "tools_to_skip": ["delete", "admin_action"]       // Blacklist specific tools
}

Environment Variables

Use environment variables for sensitive configuration: Gateway:
{
  "name": "secure-api",
  "connection_type": "http",
  "connection_string": "env.SECURE_MCP_URL",  // References $SECURE_MCP_URL
  "stdio_config": {
    "command": "python",
    "args": ["-m", "secure_server"],
    "envs": ["API_SECRET", "DATABASE_URL"]     // Required environment variables
  }
}
Environment variables are:
  • Automatically resolved during client connection
  • Redacted in API responses and UI for security
  • Validated at startup to ensure all required variables are set

Client State Management

Monitor and manage MCP client connections:
// Get all connected clients and their status
clients, err := client.GetMCPClients()
for _, mcpClient := range clients {
    fmt.Printf("Client: %s, State: %s, Tools: %v\n", 
        mcpClient.Name, mcpClient.State, mcpClient.Tools)
}

// Reconnect a disconnected client
err = client.ReconnectMCPClient("filesystem-tools")

// Add new client at runtime  
err = client.AddMCPClient(newClientConfig)

// Remove client
err = client.RemoveMCPClient("old-client")

// Edit client tools
err = client.EditMCPClientTools("filesystem-tools", 
    []string{"read_file", "write_file"}, // tools to add
    []string{"delete_file"})             // tools to remove
Connection States:
  • Connected: Client is active and tools are available
  • Connecting: Client is establishing connection
  • Disconnected: Client lost connection but can be reconnected
  • Error: Client configuration or connection failed

Architecture Details

For detailed information about MCP’s internal architecture, concurrency model, tool discovery process, and performance characteristics, see the MCP Architecture Guide.