> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbifrost.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool Execution

> Execute MCP tools with full control over approval and conversation flow.

## Overview

When an LLM returns tool calls in its response, Bifrost does **not** automatically execute them. Instead, your application explicitly calls the tool execution API, giving you full control over:

* Which tool calls to execute
* User approval workflows
* Security validation
* Audit logging

The basic flow is: **Chat Request → Review Tool Calls → Execute Tools → Continue Conversation**. For detailed architecture diagrams, see the [MCP Architecture](/architecture/core/mcp#tool-execution-engine) documentation.

***

## Authentication

The `/v1/mcp/tool/execute` endpoint uses the same authentication as other inference endpoints like `/v1/chat/completions`:

| Auth Configuration                 | Behavior         |
| ---------------------------------- | ---------------- |
| `disable_auth_on_inference: true`  | No auth required |
| `disable_auth_on_inference: false` | Auth required    |

Virtual keys and authentication are independent layers that work together. For details on how to use virtual keys with authentication, see [Authentication and Virtual Keys](/features/governance/virtual-keys#authentication-and-virtual-keys).

***

## End-to-End Example

<Tabs>
  <Tab title="Gateway">
    ### Step 1: Send Chat Request

    ```bash theme={null}
    curl -X POST http://localhost:8080/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "openai/gpt-4o",
        "messages": [
          {
            "role": "user",
            "content": "List files in the current directory"
          }
        ]
      }'
    ```

    **Response with tool calls:**

    ```json theme={null}
    {
      "id": "chatcmpl-abc123",
      "choices": [{
        "index": 0,
        "message": {
          "role": "assistant",
          "content": null,
          "tool_calls": [{
            "id": "call_xyz789",
            "type": "function",
            "function": {
              "name": "filesystem_list_directory",
              "arguments": "{\"path\": \".\"}"
            }
          }]
        },
        "finish_reason": "tool_calls"
      }]
    }
    ```

    <Note>
      Tool names are prefixed with the MCP client name (e.g., `filesystem_list_directory`). This ensures uniqueness across multiple MCP clients.
    </Note>

    ### Step 2: Execute the Tool

    The request body matches the tool call object from the response:

    ```bash theme={null}
    curl -X POST http://localhost:8080/v1/mcp/tool/execute \
      -H "Content-Type: application/json" \
      -d '{
        "id": "call_xyz789",
        "type": "function",
        "function": {
          "name": "filesystem_list_directory",
          "arguments": "{\"path\": \".\"}"
        }
      }'
    ```

    **Tool result response:**

    ```json theme={null}
    {
      "role": "tool",
      "content": "[\"config.json\", \"main.go\", \"README.md\"]",
      "tool_call_id": "call_xyz789"
    }
    ```

    ### Step 3: Continue the Conversation

    Assemble the full conversation history and continue:

    ```bash theme={null}
    curl -X POST http://localhost:8080/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "openai/gpt-4o",
        "messages": [
          {
            "role": "user",
            "content": "List files in the current directory"
          },
          {
            "role": "assistant",
            "content": null,
            "tool_calls": [{
              "id": "call_xyz789",
              "type": "function",
              "function": {
                "name": "filesystem_list_directory",
                "arguments": "{\"path\": \".\"}"
              }
            }]
          },
          {
            "role": "tool",
            "content": "[\"config.json\", \"main.go\", \"README.md\"]",
            "tool_call_id": "call_xyz789"
          }
        ]
      }'
    ```

    **Final response:**

    ```json theme={null}
    {
      "choices": [{
        "message": {
          "role": "assistant",
          "content": "The current directory contains 3 files:\n\n1. **config.json** - Configuration file\n2. **main.go** - Go source file\n3. **README.md** - Documentation"
        },
        "finish_reason": "stop"
      }]
    }
    ```
  </Tab>

  <Tab title="Go SDK">
    ```go theme={null}
    package main

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

    func main() {
        // Initialize Bifrost with MCP (see Connecting to Servers)
        client, _ := bifrost.Init(context.Background(), config)

        // Step 1: Send initial request
        firstMessage := schemas.ChatMessage{
            Role: schemas.ChatMessageRoleUser,
            Content: schemas.ChatMessageContent{
                ContentStr: bifrost.Ptr("List files in the current directory"),
            },
        }

        request := &schemas.BifrostChatRequest{
            Provider: schemas.OpenAI,
            Model:    "gpt-4o",
            Input:    []schemas.ChatMessage{firstMessage},
        }

        response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(context.Background(), schemas.NoDeadline), request)
        if err != nil {
            panic(err)
        }

        // Build conversation history
        history := []schemas.ChatMessage{firstMessage}

        // Step 2: Process tool calls
        if response.Choices[0].Message.ToolCalls != nil {
            assistantMessage := response.Choices[0].Message
            history = append(history, assistantMessage)

            for _, toolCall := range *assistantMessage.ToolCalls {
                fmt.Printf("Tool requested: %s\n", *toolCall.Function.Name)

                // YOUR APPROVAL LOGIC HERE
                // - Validate arguments
                // - Check permissions
                // - Get user confirmation if needed

                // Step 3: Execute the tool
                toolResult, err := client.ExecuteChatMCPTool(context.Background(), toolCall)
                if err != nil {
                    fmt.Printf("Tool execution failed: %v\n", err)
                    continue
                }

                fmt.Printf("Tool result: %s\n", *toolResult.Content.ContentStr)
                history = append(history, *toolResult)
            }
        }

        // Step 4: Continue conversation with results
        finalRequest := &schemas.BifrostChatRequest{
            Provider: schemas.OpenAI,
            Model:    "gpt-4o",
            Input:    history,
        }

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

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

***

## Response Formats

Bifrost supports two API formats for tool execution:

### Chat Format (Default)

Use `?format=chat` or omit the parameter:

```bash theme={null}
POST /v1/mcp/tool/execute?format=chat
```

**Request:**

```json theme={null}
{
  "id": "call_xyz789",
  "type": "function",
  "function": {
    "name": "filesystem_read_file",
    "arguments": "{\"path\": \"config.json\"}"
  }
}
```

**Response:**

```json theme={null}
{
  "role": "tool",
  "content": "{\"key\": \"value\"}",
  "tool_call_id": "call_xyz789"
}
```

### Responses Format

Use `?format=responses` for the Responses API format:

```bash theme={null}
POST /v1/mcp/tool/execute?format=responses
```

**Request:**

```json theme={null}
{
  "type": "function_call_output",
  "call_id": "call_xyz789",
  "name": "filesystem_read_file",
  "arguments": "{\"path\": \"config.json\"}"
}
```

**Response:**

```json theme={null}
{
  "type": "function_call_output",
  "call_id": "call_xyz789",
  "output": "{\"key\": \"value\"}"
}
```

***

## Multiple Tool Calls

LLMs often request multiple tools in a single response. Execute them in sequence or parallel:

<Tabs>
  <Tab title="Sequential">
    ```go theme={null}
    for _, toolCall := range *response.Choices[0].Message.ToolCalls {
        result, err := client.ExecuteChatMCPTool(ctx, toolCall)
        if err != nil {
            // Handle error
            continue
        }
        history = append(history, *result)
    }
    ```
  </Tab>

  <Tab title="Parallel">
    ```go theme={null}
    toolCalls := *response.Choices[0].Message.ToolCalls
    results := make([]*schemas.ChatMessage, len(toolCalls))
    var wg sync.WaitGroup

    for i, toolCall := range toolCalls {
        wg.Add(1)
        go func(idx int, tc schemas.ChatAssistantMessageToolCall) {
            defer wg.Done()
            result, err := client.ExecuteChatMCPTool(ctx, tc)
            if err == nil {
                results[idx] = result
            }
        }(i, toolCall)
    }

    wg.Wait()

    for _, result := range results {
        if result != nil {
            history = append(history, *result)
        }
    }
    ```
  </Tab>
</Tabs>

***

## Error Handling

Tool execution can fail for various reasons:

```go theme={null}
result, err := client.ExecuteChatMCPTool(ctx, toolCall)
if err != nil {
    switch {
    case errors.Is(err, context.DeadlineExceeded):
        // Tool execution timed out
    case strings.Contains(err.Error(), "tool not found"):
        // Tool doesn't exist or client disconnected
    case strings.Contains(err.Error(), "not allowed"):
        // Tool filtered out by configuration
    default:
        // Other execution error
    }
}
```

**Gateway error responses:**

```json theme={null}
{
  "error": {
    "type": "tool_execution_error",
    "message": "Tool 'filesystem_delete_file' is not allowed for this request"
  }
}
```

***

## Copy-Pastable Responses

Tool execution responses are designed to be directly appended to your conversation history:

```go theme={null}
// Tool result is already in the correct format
toolResult, _ := client.ExecuteChatMCPTool(ctx, toolCall)

// Just append it directly
history = append(history, *toolResult)
```

The response includes:

* Correct `role` field (`"tool"`)
* Matching `tool_call_id` for correlation
* Properly formatted `content`

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Agent Mode" icon="robot" href="./agent-mode">
    Enable autonomous tool execution with auto-approval
  </Card>

  <Card title="Tool Filtering" icon="filter" href="./filtering">
    Control which tools are available per request
  </Card>
</CardGroup>
