---
title: "Tool Hosting"
sidebarTitle: "Tool Hosting"
description: "Register custom tools directly in your Go application without external MCP servers."
icon: "toolbox"
---

<Info>
This feature is only available when using Bifrost as a **Go SDK**. It is not available in the Gateway deployment.
</Info>

## Overview

**Tool Hosting** allows you to register custom tools directly within your Go application. These tools run in-process with zero network overhead, making them ideal for:

- Application-specific business logic
- High-performance operations
- Testing and development
- Tools that need access to application state

Bifrost automatically creates an internal MCP server (`bifrostInternal`) when you register your first tool.

---

## Basic Usage

### Step 1: Define Your Tool Schema

Create a schema that describes your tool's parameters:

```go
import "github.com/maximhq/bifrost/core/schemas"

// Define the tool schema
calculatorSchema := schemas.ChatTool{
    Type: schemas.ChatToolTypeFunction,
    Function: &schemas.ChatToolFunction{
        Name:        "calculator",
        Description: schemas.Ptr("Perform basic arithmetic operations"),
        Parameters: &schemas.ToolFunctionParameters{
            Type: "object",
            Properties: &schemas.OrderedMap{
                "operation": map[string]interface{}{
                    "type":        "string",
                    "description": "The arithmetic operation to perform",
                    "enum":        []string{"add", "subtract", "multiply", "divide"},
                },
                "a": map[string]interface{}{
                    "type":        "number",
                    "description": "First operand",
                },
                "b": map[string]interface{}{
                    "type":        "number",
                    "description": "Second operand",
                },
            },
            Required: []string{"operation", "a", "b"},
        },
    },
}
```

### Step 2: Implement the Handler

Create a function that handles tool execution:

```go
func calculatorHandler(args any) (string, error) {
    // Parse arguments
    argsMap, ok := args.(map[string]interface{})
    if !ok {
        return "", fmt.Errorf("invalid arguments")
    }

    operation, _ := argsMap["operation"].(string)
    a, _ := argsMap["a"].(float64)
    b, _ := argsMap["b"].(float64)

    var result float64
    switch operation {
    case "add":
        result = a + b
    case "subtract":
        result = a - b
    case "multiply":
        result = a * b
    case "divide":
        if b == 0 {
            return "", fmt.Errorf("division by zero")
        }
        result = a / b
    default:
        return "", fmt.Errorf("unknown operation: %s", operation)
    }

    return fmt.Sprintf("%.2f", result), nil
}
```

### Step 3: Register the Tool

Register your tool with Bifrost:

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

func main() {
    // Initialize Bifrost with MCP enabled (even empty config is fine)
    client, err := bifrost.Init(context.Background(), schemas.BifrostConfig{
        Account: account,
        MCPConfig: &schemas.MCPConfig{}, // Required for tool registration
    })
    if err != nil {
        panic(err)
    }

    // Register the calculator tool
    err = client.RegisterMCPTool(
        "calculator",
        "Perform basic arithmetic operations",
        calculatorHandler,
        calculatorSchema,
    )
    if err != nil {
        panic(fmt.Sprintf("Failed to register tool: %v", err))
    }

    // Now the tool is available in all chat requests
}
```

---

## Complete Example

Here's a complete example with multiple tools:

```go
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "time"

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

func main() {
    // Initialize with empty MCP config to enable tool registration
    client, err := bifrost.Init(context.Background(), schemas.BifrostConfig{
        Account: schemas.Account{
            Provider: schemas.OpenAI,
            APIKey:   "your-api-key",
        },
        MCPConfig: &schemas.MCPConfig{},
    })
    if err != nil {
        panic(err)
    }

    // Register a calculator tool
    registerCalculator(client)

    // Register a time tool
    registerTimeTool(client)

    // Make a request - tools are automatically available
    response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(context.Background(), schemas.NoDeadline), &schemas.BifrostChatRequest{
        Provider: schemas.OpenAI,
        Model:    "gpt-4o",
        Input: []schemas.ChatMessage{
            {
                Role: schemas.ChatMessageRoleUser,
                Content: schemas.ChatMessageContent{
                    ContentStr: bifrost.Ptr("What is 15 * 7? Also, what time is it?"),
                },
            },
        },
    })

    if err != nil {
        panic(err)
    }

    // Handle tool calls...
}

func registerCalculator(client *bifrost.Bifrost) {
    schema := schemas.ChatTool{
        Type: schemas.ChatToolTypeFunction,
        Function: &schemas.ChatToolFunction{
            Name:        "calculator",
            Description: schemas.Ptr("Perform arithmetic: add, subtract, multiply, divide"),
            Parameters: &schemas.ToolFunctionParameters{
                Type: "object",
                Properties: &schemas.OrderedMap{
                    "operation": map[string]interface{}{
                        "type": "string",
                        "enum": []string{"add", "subtract", "multiply", "divide"},
                    },
                    "a": map[string]interface{}{"type": "number"},
                    "b": map[string]interface{}{"type": "number"},
                },
                Required: []string{"operation", "a", "b"},
            },
        },
    }

    handler := func(args any) (string, error) {
        m := args.(map[string]interface{})
        op := m["operation"].(string)
        a := m["a"].(float64)
        b := m["b"].(float64)

        var result float64
        switch op {
        case "add":
            result = a + b
        case "subtract":
            result = a - b
        case "multiply":
            result = a * b
        case "divide":
            if b == 0 {
                return "", fmt.Errorf("cannot divide by zero")
            }
            result = a / b
        }
        return fmt.Sprintf("%.2f", result), nil
    }

    if err := client.RegisterMCPTool("calculator", "Arithmetic calculator", handler, schema); err != nil {
        panic(err)
    }
}

func registerTimeTool(client *bifrost.Bifrost) {
    schema := schemas.ChatTool{
        Type: schemas.ChatToolTypeFunction,
        Function: &schemas.ChatToolFunction{
            Name:        "get_current_time",
            Description: schemas.Ptr("Get the current date and time"),
            Parameters: &schemas.ToolFunctionParameters{
                Type: "object",
                Properties: &schemas.OrderedMap{
                    "timezone": map[string]interface{}{
                        "type":        "string",
                        "description": "Timezone (e.g., 'America/New_York', 'UTC')",
                    },
                },
                Required: []string{},
            },
        },
    }

    handler := func(args any) (string, error) {
        m := args.(map[string]interface{})
        tzName, _ := m["timezone"].(string)

        var loc *time.Location
        var err error
        if tzName != "" {
            loc, err = time.LoadLocation(tzName)
            if err != nil {
                return "", fmt.Errorf("invalid timezone: %s", tzName)
            }
        } else {
            loc = time.UTC
        }

        now := time.Now().In(loc)
        return now.Format("2006-01-02 15:04:05 MST"), nil
    }

    if err := client.RegisterMCPTool("get_current_time", "Get current time", handler, schema); err != nil {
        panic(err)
    }
}
```

---

## Typed Handlers

For better type safety, use typed structs with JSON marshaling:

```go
// Define typed arguments
type WeatherArgs struct {
    City    string `json:"city"`
    Units   string `json:"units,omitempty"` // celsius or fahrenheit
}

type WeatherResponse struct {
    City        string  `json:"city"`
    Temperature float64 `json:"temperature"`
    Units       string  `json:"units"`
    Condition   string  `json:"condition"`
}

func weatherHandler(args any) (string, error) {
    // Parse to typed struct
    argsBytes, _ := json.Marshal(args)
    var typedArgs WeatherArgs
    if err := json.Unmarshal(argsBytes, &typedArgs); err != nil {
        return "", fmt.Errorf("invalid arguments: %v", err)
    }

    // Default units
    if typedArgs.Units == "" {
        typedArgs.Units = "celsius"
    }

    // Your weather logic here...
    response := WeatherResponse{
        City:        typedArgs.City,
        Temperature: 22.5,
        Units:       typedArgs.Units,
        Condition:   "sunny",
    }

    // Return as JSON string
    result, _ := json.Marshal(response)
    return string(result), nil
}
```

---

## Tool Naming

Tool names from `RegisterMCPTool` are prefixed with `bifrostInternal_` when exposed to LLMs:

| Registered Name | LLM Sees |
|-----------------|----------|
| `calculator` | `bifrostInternal_calculator` |
| `get_weather` | `bifrostInternal_get_weather` |

This prevents naming conflicts with tools from external MCP servers.

---

## Error Handling

Return errors from your handler to indicate tool execution failures:

```go
func myHandler(args any) (string, error) {
    // Validation errors
    if args == nil {
        return "", fmt.Errorf("arguments required")
    }

    // Business logic errors
    if someCondition {
        return "", fmt.Errorf("operation not permitted: %s", reason)
    }

    // External service errors
    result, err := callExternalService()
    if err != nil {
        return "", fmt.Errorf("service error: %w", err)
    }

    return result, nil
}
```

Errors are returned to the LLM as tool error messages, allowing it to handle the failure gracefully.

---

## Accessing Application State

Since tools run in-process, they can access your application's state:

```go
type AppContext struct {
    DB        *sql.DB
    Cache     *redis.Client
    UserID    string
    SessionID string
}

func createUserTool(appCtx *AppContext) func(args any) (string, error) {
    return func(args any) (string, error) {
        // Access database
        rows, err := appCtx.DB.Query("SELECT * FROM users WHERE id = ?", appCtx.UserID)
        if err != nil {
            return "", err
        }
        defer rows.Close()

        // Access cache
        cached, _ := appCtx.Cache.Get(context.Background(), "user:"+appCtx.UserID).Result()

        // Return result
        return fmt.Sprintf("User data: %s", cached), nil
    }
}

// Usage
appCtx := &AppContext{
    DB:     db,
    Cache:  redisClient,
    UserID: "user123",
}
client.RegisterMCPTool("get_user_data", "Get current user data", createUserTool(appCtx), schema)
```

---

## Best Practices

<AccordionGroup>
  <Accordion title="Validate inputs">
    Always validate arguments before processing:
    ```go
    func handler(args any) (string, error) {
        m, ok := args.(map[string]interface{})
        if !ok {
            return "", fmt.Errorf("expected object arguments")
        }

        required := []string{"field1", "field2"}
        for _, field := range required {
            if _, exists := m[field]; !exists {
                return "", fmt.Errorf("missing required field: %s", field)
            }
        }
        // ...
    }
    ```
  </Accordion>

  <Accordion title="Return structured data">
    Return JSON for complex responses:
    ```go
    func handler(args any) (string, error) {
        result := map[string]interface{}{
            "status": "success",
            "data": []string{"item1", "item2"},
            "count": 2,
        }
        bytes, _ := json.Marshal(result)
        return string(bytes), nil
    }
    ```
  </Accordion>

  <Accordion title="Handle timeouts">
    Use context for long-running operations:
    ```go
    func handler(args any) (string, error) {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()

        result, err := longOperation(ctx)
        if errors.Is(err, context.DeadlineExceeded) {
            return "", fmt.Errorf("operation timed out")
        }
        return result, err
    }
    ```
  </Accordion>

  <Accordion title="Log for debugging">
    Add logging for troubleshooting:
    ```go
    func handler(args any) (string, error) {
        log.Printf("Tool called with args: %+v", args)

        result, err := doWork(args)
        if err != nil {
            log.Printf("Tool error: %v", err)
            return "", err
        }

        log.Printf("Tool result: %s", result)
        return result, nil
    }
    ```
  </Accordion>
</AccordionGroup>

---

## Comparison with External MCP Servers

| Aspect | Tool Hosting (In-Process) | External MCP Server |
|--------|---------------------------|---------------------|
| Latency | ~0.1ms (no network) | 10-500ms (network dependent) |
| Deployment | Part of your app | Separate process/service |
| Language | Go only | Any language |
| Configuration | Code only | config.json, API, or UI |
| State Access | Direct access | Via APIs |
| Scaling | Scales with app | Independent scaling |

---

## Next Steps

<CardGroup cols={2}>
  <Card title="Tool Execution" icon="play" href="./tool-execution">
    Learn how tool execution works
  </Card>
  <Card title="Agent Mode" icon="robot" href="./agent-mode">
    Enable auto-execution for hosted tools
  </Card>
</CardGroup>
