---
title: "Tool Filtering"
sidebarTitle: "Filtering"
description: "Control which MCP tools are available at the client, request, and virtual key levels."
icon: "filter"
---

## Overview

Bifrost provides **three levels of tool filtering** to control which MCP tools are available:

1. **Client Configuration** - Set which tools a client can execute (`tools_to_execute`)
2. **Request Headers** - Filter tools per-request via HTTP headers or context
3. **Virtual Key Configuration** - Control tools per-VK (Gateway only)

These levels stack: a tool must pass all applicable filters to be available.

```mermaid
graph LR
    All["<b>Available<br/>Tools</b>"]
    Client["<b>Client Config</b><br/>tools_to_execute"]
    Request["<b>Request<br/>Headers</b>"]
    VK["<b>Virtual Key<br/>Filter</b>"]
    Final["<b>Tools<br/>for LLM</b>"]

    All --> Client
    Client --> Request
    Request --> VK
    VK --> Final

    style All fill:#EEEEEE,stroke:#424242,stroke-width:2.5px,color:#1A1A1A
    style Client fill:#E3F2FD,stroke:#0D47A1,stroke-width:2.5px,color:#1A1A1A
    style Request fill:#F3E5F5,stroke:#4A148C,stroke-width:2.5px,color:#1A1A1A
    style VK fill:#E8F5E9,stroke:#1B5E20,stroke-width:2.5px,color:#1A1A1A
    style Final fill:#FFFDE7,stroke:#F57F17,stroke-width:2.5px,color:#1A1A1A
```

---

## Level 1: Client Configuration

The `tools_to_execute` field on each MCP client config defines the **baseline** of available tools.

### Semantics

| Value | Behavior |
|-------|----------|
| `["*"]` | All tools from this client are available |
| `[]` or omitted | No tools available (deny-by-default) |
| `["tool1", "tool2"]` | Only specified tools are available |

### Configuration

<Tabs>
<Tab title="Gateway">

```bash
curl -X POST http://localhost:8080/api/mcp/client \
  -H "Content-Type: application/json" \
  -d '{
    "name": "filesystem",
    "connection_type": "stdio",
    "stdio_config": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-filesystem"]
    },
    "tools_to_execute": ["read_file", "list_directory"]
  }'
```

</Tab>
<Tab title="Go SDK">

```go
mcpConfig := &schemas.MCPConfig{
    ClientConfigs: []schemas.MCPClientConfig{
        {
            Name:           "filesystem",
            ConnectionType: schemas.MCPConnectionTypeSTDIO,
            StdioConfig: &schemas.MCPStdioConfig{
                Command: "npx",
                Args:    []string{"-y", "@anthropic/mcp-filesystem"},
            },
            ToolsToExecute: []string{"read_file", "list_directory"}, // Only these tools
        },
    },
}
```

</Tab>
<Tab title="config.json">

```json
{
  "mcp": {
    "client_configs": [
      {
        "name": "filesystem",
        "connection_type": "stdio",
        "stdio_config": {
          "command": "npx",
          "args": ["-y", "@anthropic/mcp-filesystem"]
        },
        "tools_to_execute": ["read_file", "list_directory"]
      }
    ]
  }
}
```

</Tab>
</Tabs>

---

## Level 2: Request-Level Filtering

Filter tools dynamically on a per-request basis using headers (Gateway) or context values (SDK).

### Available Filters

| Filter | Purpose |
|--------|---------|
| `mcp-include-clients` | Only include tools from specified clients |
| `mcp-include-tools` | Only include specified tools (format: `clientName-toolName`) |

### Gateway Headers

```bash
# Include only specific clients
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-clients: filesystem,web_search" \
  -d '...'

# Include only specific tools
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-tools: filesystem-read_file,web_search-search" \
  -d '...'

# Include all tools from one client, specific tools from another
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-tools: filesystem-*,web_search-search" \
  -d '...'

# Include internal tools registered via RegisterTool()
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-tools: bifrostInternal-echo,bifrostInternal-calculator" \
  -d '...'

# Empty clients filter blocks ALL tools - no tools available to LLM
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-clients:" \
  -d '...'
# Result: No MCP tools available (deny-all)

# Empty tools filter also blocks ALL tools
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-tools:" \
  -d '...'
# Result: No MCP tools available (deny-all)
```

### Go SDK Context Values

```go
// Include only specific clients
ctx := context.WithValue(context.Background(),
    schemas.BifrostContextKey("mcp-include-clients"),
    []string{"filesystem", "web_search"})

// Include only specific tools
ctx = context.WithValue(ctx,
    schemas.BifrostContextKey("mcp-include-tools"),
    []string{"filesystem-read_file", "web_search-search"})

// Wildcard for all tools from a client
ctx = context.WithValue(ctx,
    schemas.BifrostContextKey("mcp-include-tools"),
    []string{"filesystem-*", "web_search-search"})

// Include all internal tools (registered via RegisterTool)
ctx = context.WithValue(ctx,
    schemas.BifrostContextKey("mcp-include-tools"),
    []string{"bifrostInternal-*"})

response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), request)

// Empty include-clients blocks ALL tools - no tools available
ctx = context.WithValue(context.Background(),
    schemas.BifrostContextKey("mcp-include-clients"),
    []string{})  // Empty slice = deny-all
// Result: No MCP tools available to LLM

// Empty include-tools also blocks ALL tools
ctx = context.WithValue(context.Background(),
    schemas.BifrostContextKey("mcp-include-tools"),
    []string{})  // Empty slice = deny-all
// Result: No MCP tools available to LLM
```

### Wildcard Support

| Pattern | Meaning |
|---------|---------|
| `*` (in include-clients) | Include all clients |
| `clientName-*` (in include-tools) | Include all tools from that client |
| `clientName-toolName` | Include specific tool |

### Tool Naming Convention

**Important:** All MCP tools follow a consistent naming convention using the **prefixed format** `clientName-toolName`:

- **External MCP Clients** (HTTP, SSE, STDIO): Tools use the format `clientName-toolName`
  - Example: `filesystem-read_file`, `web_search-search`
  - The `clientName` is the name configured for the MCP client

- **Internal (In-Process) Tools**: Tools registered via `RegisterTool()` use the prefix `bifrostInternal-`
  - Example: `bifrostInternal-echo`, `bifrostInternal-my_custom_tool`
  - These tools are registered via `RegisterTool()` in the SDK

This consistent naming convention ensures clear separation between tools from different clients and prevents naming conflicts across all MCP client types.

---

## Level 3: Virtual Key Filtering (Gateway Only)

Virtual Keys can have their own MCP tool access configuration, which **takes precedence** over request-level headers.

<Note>
When a Virtual Key has no MCP configurations, **no MCP tools are available** (deny-by-default). You must explicitly add MCP client configurations to allow tools. When a Virtual Key has MCP configurations, it generates the `x-bf-mcp-include-tools` header automatically, overriding any manually sent header.
</Note>

### Configuration

<Tabs>
<Tab title="Web UI">

1. Navigate to **Virtual Keys** in the governance section
2. Create or edit a Virtual Key
3. In **MCP Client Configurations**, add the clients and tools this VK can access

<Frame>
  <img src="/media/ui-virtual-key-mcp-filter.png" alt="Virtual Key MCP Configuration" />
</Frame>

</Tab>
<Tab title="API">

```bash
curl -X POST http://localhost:8080/api/governance/virtual-keys \
  -H "Content-Type: application/json" \
  -d '{
    "name": "support-team-key",
    "mcp_configs": [
      {
        "mcp_client_name": "knowledge_base",
        "tools_to_execute": ["search", "get_article"]
      },
      {
        "mcp_client_name": "ticketing",
        "tools_to_execute": ["*"]
      }
    ]
  }'
```

</Tab>
<Tab title="config.json">

```json
{
  "governance": {
    "virtual_keys": [
      {
        "name": "support-team-key",
        "mcp_configs": [
          {
            "mcp_client_name": "knowledge_base",
            "tools_to_execute": ["search", "get_article"]
          },
          {
            "mcp_client_name": "ticketing",
            "tools_to_execute": ["*"]
          }
        ]
      }
    ]
  }
}
```

</Tab>
</Tabs>

### Virtual Key MCP Config Semantics

| Configuration | Result |
|---------------|--------|
| `tools_to_execute: ["*"]` | All tools from this client |
| `tools_to_execute: []` | No tools from this client |
| `tools_to_execute: ["a", "b"]` | Only specified tools |
| Client not configured | All tools blocked from that client |

Learn more in [MCP Tool Filtering for Virtual Keys](../features/governance/mcp-tools).

---

## Filtering Logic

### How Filters Combine

1. **Client config** is the baseline (must include the tool)
2. **Request filters** further narrow down (if specified)
3. **VK filters** override request filters (if VK has MCP configs)

### Example Scenario

**Setup:**
- Client `filesystem` has `tools_to_execute: ["read_file", "write_file", "delete_file"]`
- Virtual Key `prod-key` has `mcp_configs: [{ mcp_client_name: "filesystem", tools_to_execute: ["read_file"] }]`

**Request with `prod-key`:**
```bash
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer vk_prod_key" \
  -H "x-bf-mcp-include-tools: filesystem-write_file" \  # This is IGNORED
  -d '...'
```

**Result:** Only `read_file` is available (VK config overrides request header)

**Request without VK (if allowed):**
```bash
curl -X POST http://localhost:8080/v1/chat/completions \
  -H "x-bf-mcp-include-tools: filesystem-write_file" \
  -d '...'
```

**Result:** Only `write_file` is available (request header applies)

---

## Common Patterns

### Read-Only Access

Allow only read operations:

```json
{
  "tools_to_execute": ["read_file", "list_directory", "get_file_info"]
}
```

### Environment-Based Filtering

Use different VKs for different environments:

```json
{
  "virtual_keys": [
    {
      "name": "development",
      "mcp_configs": [
        { "mcp_client_name": "filesystem", "tools_to_execute": ["*"] },
        { "mcp_client_name": "database", "tools_to_execute": ["*"] }
      ]
    },
    {
      "name": "production",
      "mcp_configs": [
        { "mcp_client_name": "filesystem", "tools_to_execute": ["read_file"] },
        { "mcp_client_name": "database", "tools_to_execute": ["query"] }
      ]
    }
  ]
}
```

### Per-User Tool Access

Create VKs for different user roles:

```json
{
  "virtual_keys": [
    {
      "name": "viewer-role",
      "mcp_configs": [
        { "mcp_client_name": "documents", "tools_to_execute": ["view", "search"] }
      ]
    },
    {
      "name": "editor-role",
      "mcp_configs": [
        { "mcp_client_name": "documents", "tools_to_execute": ["view", "search", "edit", "create"] }
      ]
    },
    {
      "name": "admin-role",
      "mcp_configs": [
        { "mcp_client_name": "documents", "tools_to_execute": ["*"] }
      ]
    }
  ]
}
```

---

## Advanced: Context-Based Filtering

For SDK users, filtering can be applied at the context level, enabling per-request tool customization:

### Go SDK Context Filtering

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

// Filter to specific clients
ctx := context.WithValue(
    context.Background(),
    schemas.BifrostContextKey("mcp-include-clients"),
    []string{"filesystem", "web_search"},
)

// Or filter to specific tools
ctx = context.WithValue(
    ctx,
    schemas.BifrostContextKey("mcp-include-tools"),
    []string{"filesystem-read_file", "web_search-search"},
)

// Request will only see filtered tools
response, _ := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), request)
```

### Filter Precedence

When multiple filters apply, they combine as an intersection (AND logic):

```
Client Config Tools ∩ Request Filters ∩ VK Filters = Available Tools
```

**Example:**
- Client config allows: [read_file, write_file, delete_file]
- Request header specifies: [read_file, write_file]
- VK config restricts to: [read_file]
- **Result:** Only [read_file] available

---

## Debugging Tool Availability

### Check Available Tools

**Gateway API:**
```bash
curl http://localhost:8080/api/mcp/clients
```

**Response shows tools per client:**
```json
[
  {
    "config": { "name": "filesystem", "tools_to_execute": ["read_file", "write_file"] },
    "tools": [
      { "name": "read_file", "description": "Read file contents" },
      { "name": "write_file", "description": "Write to file" }
    ],
    "state": "connected"
  }
]
```

### Check What LLM Receives

The tools included in a chat request depend on all active filters. To see what tools are available for a specific request, check the request body sent to the LLM provider in your logs or observability platform.

---

## Next Steps

<CardGroup cols={2}>
  <Card title="Virtual Key MCP Tools" icon="key" href="../features/governance/mcp-tools">
    Detailed VK tool configuration
  </Card>
  <Card title="Agent Mode" icon="robot" href="./agent-mode">
    Configure auto-execution for filtered tools
  </Card>
</CardGroup>
