Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -3405,7 +3405,7 @@ func (bifrost *Bifrost) GetMCPClients() ([]schemas.MCPClient, error) {
//
// Returns:
// - []schemas.ChatTool: List of available tools
func (bifrost *Bifrost) GetAvailableMCPTools(ctx context.Context) []schemas.ChatTool {
func (bifrost *Bifrost) GetAvailableMCPTools(ctx *schemas.BifrostContext) []schemas.ChatTool {
if bifrost.MCPManager == nil {
return nil
}
Expand Down
2 changes: 2 additions & 0 deletions core/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- feat: add DisableAutoToolInject to MCPToolManagerConfig to suppress automatic MCP tool injection per request
- feat: add BifrostContextKeyMCPAddedTools to context to track MCP tools added to the request
6 changes: 2 additions & 4 deletions core/mcp/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
package mcp

import (
"context"

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

Expand All @@ -14,10 +12,10 @@ import (
type MCPManagerInterface interface {
// Tool Operations
// AddToolsToRequest parses available MCP tools and adds them to the request
AddToolsToRequest(ctx context.Context, req *schemas.BifrostRequest) *schemas.BifrostRequest
AddToolsToRequest(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) *schemas.BifrostRequest

// GetAvailableTools returns all available MCP tools for the given context
GetAvailableTools(ctx context.Context) []schemas.ChatTool
GetAvailableTools(ctx *schemas.BifrostContext) []schemas.ChatTool

// ExecuteToolCall executes a single tool call and returns the result
ExecuteToolCall(ctx *schemas.BifrostContext, request *schemas.BifrostMCPRequest) (*schemas.BifrostMCPResponse, error)
Expand Down
4 changes: 2 additions & 2 deletions core/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,11 @@ func NewMCPManager(ctx context.Context, config schemas.MCPConfig, oauth2Provider
//
// Returns:
// - *schemas.BifrostRequest: The request with tools added
func (m *MCPManager) AddToolsToRequest(ctx context.Context, req *schemas.BifrostRequest) *schemas.BifrostRequest {
func (m *MCPManager) AddToolsToRequest(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) *schemas.BifrostRequest {
return m.toolsManager.ParseAndAddToolsToRequest(ctx, req)
}

func (m *MCPManager) GetAvailableTools(ctx context.Context) []schemas.ChatTool {
func (m *MCPManager) GetAvailableTools(ctx *schemas.BifrostContext) []schemas.ChatTool {
return m.toolsManager.GetAvailableTools(ctx)
}

Expand Down
20 changes: 10 additions & 10 deletions core/mcp/toolmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func (m *ToolsManager) GetCodeModeDependencies() *CodeModeDependencies {
}

// GetAvailableTools returns the available tools for the given context.
func (m *ToolsManager) GetAvailableTools(ctx context.Context) []schemas.ChatTool {
func (m *ToolsManager) GetAvailableTools(ctx *schemas.BifrostContext) []schemas.ChatTool {
availableToolsPerClient := m.clientManager.GetToolPerClient(ctx)
// Flatten tools from all clients into a single slice, avoiding duplicates
var availableTools []schemas.ChatTool
Expand All @@ -193,14 +193,14 @@ func (m *ToolsManager) GetAvailableTools(ctx context.Context) []schemas.ChatTool
}
if client.ExecutionConfig.IsCodeModeClient {
includeCodeModeTools = true
} else {
// Add tools from this client, checking for duplicates
for _, tool := range clientTools {
if tool.Function != nil && tool.Function.Name != "" {
if !seenToolNames[tool.Function.Name] {
availableTools = append(availableTools, tool)
seenToolNames[tool.Function.Name] = true
}
}
// Add tools from this client, checking for duplicates
for _, tool := range clientTools {
if tool.Function != nil && tool.Function.Name != "" && !seenToolNames[tool.Function.Name] {
seenToolNames[tool.Function.Name] = true
schemas.AppendToContextList(ctx, schemas.BifrostContextKeyMCPAddedTools, tool.Function.Name)
if !client.ExecutionConfig.IsCodeModeClient {
availableTools = append(availableTools, tool)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -290,7 +290,7 @@ func buildIntegrationDuplicateCheckMap(existingTools []schemas.ChatTool, integra
//
// Returns:
// - *schemas.BifrostRequest: Bifrost request with MCP tools added
func (m *ToolsManager) ParseAndAddToolsToRequest(ctx context.Context, req *schemas.BifrostRequest) *schemas.BifrostRequest {
func (m *ToolsManager) ParseAndAddToolsToRequest(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) *schemas.BifrostRequest {
// MCP is only supported for chat and responses requests
if req.ChatRequest == nil && req.ResponsesRequest == nil {
return req
Expand Down
1 change: 1 addition & 0 deletions core/schemas/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ const (
BifrostContextKeyVideoOutputRequested BifrostContextKey = "bifrost-video-output-requested"
BifrostContextKeyValidateKeys BifrostContextKey = "bifrost-validate-keys" // bool (triggers additional key validation during provider add/update)
BifrostContextKeyProviderResponseHeaders BifrostContextKey = "bifrost-provider-response-headers" // map[string]string (set by provider handlers for response header forwarding)
BifrostContextKeyMCPAddedTools BifrostContextKey = "bifrost-mcp-added-tools" // []string (set by bifrost - DO NOT SET THIS MANUALLY)) - list of tools added to the request by MCP, all the tool are in the format "clientName-toolName"
BifrostContextKeyLargePayloadMode BifrostContextKey = "bifrost-large-payload-mode" // bool (set by bifrost - DO NOT SET THIS MANUALLY)) indicates large payload streaming mode is active
BifrostContextKeyLargePayloadReader BifrostContextKey = "bifrost-large-payload-reader" // io.Reader (set by bifrost - DO NOT SET THIS MANUALLY)) upstream reader for large payloads
BifrostContextKeyLargePayloadContentLength BifrostContextKey = "bifrost-large-payload-content-length" // int (set by bifrost - DO NOT SET THIS MANUALLY)) content length for large payloads
Expand Down
8 changes: 4 additions & 4 deletions docs/features/governance/mcp-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ The filtering logic is determined by the Virtual Key's configuration:

2. **With MCP Configuration on Virtual Key**
- When you configure MCP clients on a Virtual Key, its settings take full precedence.
- Bifrost automatically generates an `x-bf-mcp-include-tools` header based on your VK configuration. This acts as a strict allow-list for the request.
- This generated header **overrides** any `x-bf-mcp-include-tools` header that might have been sent manually with the request.
- Bifrost automatically generates an `x-bf-mcp-include-tools` header based on your VK configuration (unless `disable_auto_tool_inject` is enabled or the caller already sent the header). This acts as a strict allow-list for the request.
- If the caller already includes an `x-bf-mcp-include-tools` header, auto-injection is skipped — but the VK allow-list is enforced at inference time and still enforced again at MCP tool execution time.

For each MCP client associated with a Virtual Key, you can specify the allowed tools:
- **Select specific tools**: Only the chosen tools from that client will be available.
Expand All @@ -40,7 +40,7 @@ You can configure which tools a Virtual Key has access to via the UI.
2. Create/Edit virtual key
![Virtual Key MCP Tool Restrictions](../../media/ui-virtual-key-mcp-filter.png)
3. In **MCP Client Configurations** section, add the MCP client you want to restrict the VK to
4. Add the tools you want to restrict the VK to, or leave it blank to allow all tools for this client
4. Select the specific tools to allow, or choose **Allow All Tools** to permit all current and future tools from that client (stored as `*`). Leaving the list empty blocks all tools for that client.
5. Click on the **Save** button

</Tab>
Expand Down Expand Up @@ -156,5 +156,5 @@ A request with this Virtual Key cannot access any tools. All tools from all clie
</Tabs>

<Note>
When a Virtual Key has MCP configurations, it dynamically generates the `x-bf-mcp-include-tools` header. This ensures that the VK's rules are always enforced and will override any manual header sent by the user. Though you can still use `x-bf-mcp-include-clients` header to filter the MCP clients per request.
When a Virtual Key has MCP configurations, Bifrost enforces the allow-list at both inference time and MCP tool execution time. Auto-injection of the `x-bf-mcp-include-tools` header is skipped if the caller already provides it or if `disable_auto_tool_inject` is enabled — but the VK's restrictions are always applied regardless. You can still use the `x-bf-mcp-include-clients` header to filter MCP clients per request.
</Note>
1 change: 1 addition & 0 deletions plugins/governance/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- feat: enforce VK MCPConfigs as an execution-time allow-list — empty MCPConfigs denies all MCP tools, non-empty validates each tool in both PreMCPHook and evaluateGovernanceRequest; respects disable_auto_tool_inject toggle (transport config key: mcp_disable_auto_tool_inject) and skips auto-injection header when caller already set it
Loading
Loading