diff --git a/.claude/skills/resolve-pr-comments/SKILL.md b/.claude/skills/resolve-pr-comments/SKILL.md index b547710a72..a136f28170 100644 --- a/.claude/skills/resolve-pr-comments/SKILL.md +++ b/.claude/skills/resolve-pr-comments/SKILL.md @@ -1,7 +1,7 @@ --- name: resolve-pr-comments -description: Resolve all unresolved PR comments interactively. Use when asked to resolve PR comments, address review feedback, handle CodeRabbit comments, or fix PR review issues. Invoked with /resolve-pr-comments or /resolve-pr-comments . +description: Resolve all unresolved PR comments interactively. Makes local edits only—NEVER commits or pushes. Use when asked to resolve PR comments, address review feedback, handle CodeRabbit comments, or fix PR review issues. Invoked with /resolve-pr-comments or /resolve-pr-comments . allowed-tools: Read, Grep, Glob, Bash, Edit, Write, WebFetch, Task, AskUserQuestion, TodoWrite --- @@ -143,7 +143,7 @@ gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments --paginate | jq '.[] | select(. ## Step 5: Execute Actions -**CRITICAL: Do NOT reply to PR comments until changes are pushed to the remote.** The reviewer cannot verify fixes until the code is pushed. Collect all fixes locally first, then push, then reply. +**CRITICAL: Do NOT reply to PR comments until changes are pushed to the remote.** The reviewer cannot verify fixes until the code is pushed. Collect all fixes locally. This skill NEVER commits or pushes—the user handles that manually. ### For FIX: 1. Make the code change using Edit tool @@ -157,12 +157,12 @@ These can be posted immediately since they don't require code verification: gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies -X POST -f body="" ``` -## Step 5b: Push and Reply to FIX comments +## Step 5b: Reply to FIX comments (after user pushes) After ALL comments have been addressed locally: -1. Ask user to if they have pushed these changes to remote. Yes/No -2. **Only after push succeeds**, reply to FIX comments: +1. Ask user if they have pushed these changes to remote (this skill never commits or pushes). Yes/No +2. **Only after user confirms push**, reply to FIX comments: ```bash gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies -X POST -f body="Fixed - . See updated code." ``` @@ -209,13 +209,14 @@ If count is 0, report success. If comments remain: ## Important Notes -1. **NEVER reply "Fixed" until code is pushed** - The reviewer cannot verify fixes until they're on the remote. Make all fixes locally, push, THEN reply. -2. **Always read the file** before suggesting fixes - understand context -3. **Check for existing replies** in the thread before responding -4. **Wait for user approval** on each action - never auto-fix without confirmation -5. **Update tracking file** after each action -6. **Some bots are slow** - CodeRabbit may take minutes to auto-resolve after push -7. **Push code changes** before expecting auto-resolution of FIX actions +1. **NEVER commit or push changes** - This skill only makes local edits. The user handles `git add`, `git commit`, and `git push` themselves. Do not run any git commit or git push commands. +2. **NEVER reply "Fixed" until code is pushed** - The reviewer cannot verify fixes until they're on the remote. Make all fixes locally. Only reply to FIX comments after the user confirms they have pushed (the user pushes manually). +3. **Always read the file** before suggesting fixes - understand context +4. **Check for existing replies** in the thread before responding +5. **Wait for user approval** on each action - never auto-fix without confirmation +6. **Update tracking file** after each action +7. **Some bots are slow** - CodeRabbit may take minutes to auto-resolve after push +8. **User pushes manually** - This skill never commits or pushes; the user must push code changes before expecting auto-resolution of FIX actions ## Error Handling diff --git a/core/bifrost.go b/core/bifrost.go index 09886fb78e..4c596c900a 100644 --- a/core/bifrost.go +++ b/core/bifrost.go @@ -3347,7 +3347,7 @@ func (bifrost *Bifrost) getProviderMutex(providerKey schemas.ModelProvider) *syn // }, toolSchema) func (bifrost *Bifrost) RegisterMCPTool(name, description string, handler func(args any) (string, error), toolSchema schemas.ChatTool) error { if bifrost.MCPManager == nil { - return fmt.Errorf("MCP is not configured in this Bifrost instance") + return fmt.Errorf("mcp is not configured in this bifrost instance") } return bifrost.MCPManager.RegisterTool(name, description, handler, toolSchema) @@ -3365,7 +3365,7 @@ func (bifrost *Bifrost) RegisterMCPTool(name, description string, handler func(a // - error: Any retrieval error func (bifrost *Bifrost) GetMCPClients() ([]schemas.MCPClient, error) { if bifrost.MCPManager == nil { - return nil, fmt.Errorf("MCP is not configured in this Bifrost instance") + return nil, fmt.Errorf("mcp is not configured in this bifrost instance") } clients := bifrost.MCPManager.GetClients() @@ -3476,7 +3476,7 @@ func (bifrost *Bifrost) AddMCPClient(config *schemas.MCPClientConfig) error { // } func (bifrost *Bifrost) RemoveMCPClient(id string) error { if bifrost.MCPManager == nil { - return fmt.Errorf("MCP is not configured in this Bifrost instance") + return fmt.Errorf("mcp is not configured in this bifrost instance") } return bifrost.MCPManager.RemoveClient(id) @@ -3509,7 +3509,7 @@ func (bifrost *Bifrost) SetMCPManager(manager mcp.MCPManagerInterface) { // }) func (bifrost *Bifrost) UpdateMCPClient(id string, updatedConfig *schemas.MCPClientConfig) error { if bifrost.MCPManager == nil { - return fmt.Errorf("MCP is not configured in this Bifrost instance") + return fmt.Errorf("mcp is not configured in this bifrost instance") } return bifrost.MCPManager.UpdateClient(id, updatedConfig) @@ -3524,7 +3524,7 @@ func (bifrost *Bifrost) UpdateMCPClient(id string, updatedConfig *schemas.MCPCli // - error: Any reconnection error func (bifrost *Bifrost) ReconnectMCPClient(id string) error { if bifrost.MCPManager == nil { - return fmt.Errorf("MCP is not configured in this Bifrost instance") + return fmt.Errorf("mcp is not configured in this bifrost instance") } return bifrost.MCPManager.ReconnectClient(id) @@ -3532,15 +3532,18 @@ func (bifrost *Bifrost) ReconnectMCPClient(id string) error { // UpdateToolManagerConfig updates the tool manager config for the MCP manager. // This allows for hot-reloading of the tool manager config at runtime. -func (bifrost *Bifrost) UpdateToolManagerConfig(maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error { +// Pass the current value of disableAutoToolInject whenever only other fields +// change so the flag is never silently reset to its zero value. +func (bifrost *Bifrost) UpdateToolManagerConfig(maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string, disableAutoToolInject bool) error { if bifrost.MCPManager == nil { - return fmt.Errorf("MCP is not configured in this Bifrost instance") + return fmt.Errorf("mcp is not configured in this bifrost instance") } bifrost.MCPManager.UpdateToolManagerConfig(&schemas.MCPToolManagerConfig{ - MaxAgentDepth: maxAgentDepth, - ToolExecutionTimeout: time.Duration(toolExecutionTimeoutInSeconds) * time.Second, - CodeModeBindingLevel: schemas.CodeModeBindingLevel(codeModeBindingLevel), + MaxAgentDepth: maxAgentDepth, + ToolExecutionTimeout: time.Duration(toolExecutionTimeoutInSeconds) * time.Second, + CodeModeBindingLevel: schemas.CodeModeBindingLevel(codeModeBindingLevel), + DisableAutoToolInject: disableAutoToolInject, }) return nil } @@ -5409,7 +5412,7 @@ func (bifrost *Bifrost) handleMCPToolExecution(ctx *schemas.BifrostContext, mcpR return nil, &schemas.BifrostError{ IsBifrostError: false, Error: &schemas.ErrorField{ - Message: "MCP is not configured in this Bifrost instance", + Message: "mcp is not configured in this bifrost instance", }, ExtraFields: schemas.BifrostErrorExtraFields{ RequestType: requestType, diff --git a/core/mcp/interface.go b/core/mcp/interface.go index 93617ce511..d573139c07 100644 --- a/core/mcp/interface.go +++ b/core/mcp/interface.go @@ -22,7 +22,9 @@ type MCPManagerInterface interface { // ExecuteToolCall executes a single tool call and returns the result ExecuteToolCall(ctx *schemas.BifrostContext, request *schemas.BifrostMCPRequest) (*schemas.BifrostMCPResponse, error) - // UpdateToolManagerConfig updates the configuration for the tool manager + // UpdateToolManagerConfig updates the configuration for the tool manager. + // DisableAutoToolInject in the config controls auto injection — pass the + // current value whenever only other fields change so it is never silently reset. UpdateToolManagerConfig(config *schemas.MCPToolManagerConfig) // Agent Mode Operations diff --git a/core/mcp/toolmanager.go b/core/mcp/toolmanager.go index 8ba68e65ab..5089880b8f 100644 --- a/core/mcp/toolmanager.go +++ b/core/mcp/toolmanager.go @@ -31,11 +31,12 @@ type PluginPipeline interface { // ToolsManager manages MCP tool execution and agent mode. type ToolsManager struct { - toolExecutionTimeout atomic.Value - maxAgentDepth atomic.Int32 - clientManager ClientManager - logger schemas.Logger - agentModeExecutor *AgentModeExecutor + toolExecutionTimeout atomic.Value + maxAgentDepth atomic.Int32 + disableAutoToolInject atomic.Bool + clientManager ClientManager + logger schemas.Logger + agentModeExecutor *AgentModeExecutor // CodeMode implementation for code execution (Starlark by default) codeMode CodeMode @@ -147,6 +148,7 @@ func NewToolsManagerWithCodeMode( // Initialize atomic values manager.toolExecutionTimeout.Store(config.ToolExecutionTimeout) manager.maxAgentDepth.Store(int32(config.MaxAgentDepth)) + manager.disableAutoToolInject.Store(config.DisableAutoToolInject) manager.logger.Info("%s tool manager initialized with tool execution timeout: %v, max agent depth: %d, and code mode binding level: %s", MCPLogPrefix, config.ToolExecutionTimeout, config.MaxAgentDepth, config.CodeModeBindingLevel) return manager @@ -294,6 +296,16 @@ func (m *ToolsManager) ParseAndAddToolsToRequest(ctx context.Context, req *schem return req } + // When auto tool injection is disabled, only inject tools if the request + // has explicit context filters set (e.g. via x-bf-mcp-include-tools header). + if m.disableAutoToolInject.Load() { + includeTools := ctx.Value(schemas.MCPContextKeyIncludeTools) + includeClients := ctx.Value(schemas.MCPContextKeyIncludeClients) + if includeTools == nil && includeClients == nil { + return req + } + } + availableTools := m.GetAvailableTools(ctx) if len(availableTools) == 0 { @@ -668,6 +680,8 @@ func (m *ToolsManager) UpdateConfig(config *schemas.MCPToolManagerConfig) { }) } + m.disableAutoToolInject.Store(config.DisableAutoToolInject) + m.logger.Info("%s tool manager configuration updated with tool execution timeout: %v, max agent depth: %d, and code mode binding level: %s", MCPLogPrefix, config.ToolExecutionTimeout, config.MaxAgentDepth, config.CodeModeBindingLevel) } diff --git a/core/schemas/mcp.go b/core/schemas/mcp.go index a8f6ffe60d..a6183491e7 100644 --- a/core/schemas/mcp.go +++ b/core/schemas/mcp.go @@ -47,9 +47,10 @@ type MCPConfig struct { } type MCPToolManagerConfig struct { - ToolExecutionTimeout time.Duration `json:"tool_execution_timeout"` - MaxAgentDepth int `json:"max_agent_depth"` - CodeModeBindingLevel CodeModeBindingLevel `json:"code_mode_binding_level,omitempty"` // How tools are exposed in VFS: "server" or "tool" + ToolExecutionTimeout time.Duration `json:"tool_execution_timeout"` + MaxAgentDepth int `json:"max_agent_depth"` + CodeModeBindingLevel CodeModeBindingLevel `json:"code_mode_binding_level,omitempty"` // How tools are exposed in VFS: "server" or "tool" + DisableAutoToolInject bool `json:"disable_auto_tool_inject,omitempty"` // When true, MCP tools are not injected into requests by default } const ( diff --git a/framework/changelog.md b/framework/changelog.md index e69de29bb2..257ebecae7 100644 --- a/framework/changelog.md +++ b/framework/changelog.md @@ -0,0 +1 @@ +- feat: add MCPDisableAutoToolInject column to TableClientConfig diff --git a/framework/configstore/clientconfig.go b/framework/configstore/clientconfig.go index 6382689007..76b8631ee4 100644 --- a/framework/configstore/clientconfig.go +++ b/framework/configstore/clientconfig.go @@ -55,6 +55,7 @@ type ClientConfig struct { MCPToolExecutionTimeout int `json:"mcp_tool_execution_timeout"` // The timeout for individual tool execution in seconds MCPCodeModeBindingLevel string `json:"mcp_code_mode_binding_level"` // Code mode binding level: "server" or "tool" MCPToolSyncInterval int `json:"mcp_tool_sync_interval"` // Global tool sync interval in minutes (default: 10, 0 = disabled) + MCPDisableAutoToolInject bool `json:"mcp_disable_auto_tool_inject"` // When true, MCP tools are not injected into requests by default HeaderFilterConfig *tables.GlobalHeaderFilterConfig `json:"header_filter_config,omitempty"` // Global header filtering configuration for x-bf-eh-* headers AsyncJobResultTTL int `json:"async_job_result_ttl"` // Default TTL for async job results in seconds (default: 3600 = 1 hour) RequiredHeaders []string `json:"required_headers,omitempty"` // Headers that must be present on every request (case-insensitive) @@ -140,6 +141,11 @@ func (c *ClientConfig) GenerateClientConfigHash() (string, error) { hash.Write([]byte("mcpToolSyncInterval:0")) } + // Only hash non-default value to avoid legacy config hash churn on upgrade. + if c.MCPDisableAutoToolInject { + hash.Write([]byte("mcpDisableAutoToolInject:true")) + } + if c.AsyncJobResultTTL > 0 { hash.Write([]byte("asyncJobResultTTL:" + strconv.Itoa(c.AsyncJobResultTTL))) } else { diff --git a/framework/configstore/migrations.go b/framework/configstore/migrations.go index b7b58daedd..d21c250017 100644 --- a/framework/configstore/migrations.go +++ b/framework/configstore/migrations.go @@ -317,6 +317,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error { if err := migrationBackfillEmptyVirtualKeyConfigs(ctx, db); err != nil { return err } + if err := migrationAddMCPDisableAutoToolInjectColumn(ctx, db); err != nil { + return err + } return nil } @@ -4335,6 +4338,36 @@ func migrationAddBedrockAssumeRoleColumns(ctx context.Context, db *gorm.DB) erro return nil } +// migrationAddMCPDisableAutoToolInjectColumn adds the mcp_disable_auto_tool_inject column to the client config table. +// When true, MCP tools are not automatically injected into requests; only explicit context filters apply. +func migrationAddMCPDisableAutoToolInjectColumn(ctx context.Context, db *gorm.DB) error { + m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{ + ID: "add_mcp_disable_auto_tool_inject_column", + Migrate: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + migratorInstance := tx.Migrator() + if !migratorInstance.HasColumn(&tables.TableClientConfig{}, "mcp_disable_auto_tool_inject") { + if err := migratorInstance.AddColumn(&tables.TableClientConfig{}, "mcp_disable_auto_tool_inject"); err != nil { + return err + } + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + migratorInstance := tx.Migrator() + if err := migratorInstance.DropColumn(&tables.TableClientConfig{}, "mcp_disable_auto_tool_inject"); err != nil { + return err + } + return nil + }, + }}) + if err := m.Migrate(); err != nil { + return fmt.Errorf("error while running mcp disable auto tool inject migration: %s", err.Error()) + } + return nil +} + // migrationAddPricingRefactorColumns adds all new pricing columns introduced in the pricing module refactor func migrationAddPricingRefactorColumns(ctx context.Context, db *gorm.DB) error { m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{ diff --git a/framework/configstore/rdb.go b/framework/configstore/rdb.go index 92d1627a28..af7ee85def 100644 --- a/framework/configstore/rdb.go +++ b/framework/configstore/rdb.go @@ -57,6 +57,7 @@ func (s *RDBConfigStore) UpdateClientConfig(ctx context.Context, config *ClientC MCPToolExecutionTimeout: config.MCPToolExecutionTimeout, MCPCodeModeBindingLevel: config.MCPCodeModeBindingLevel, MCPToolSyncInterval: config.MCPToolSyncInterval, + MCPDisableAutoToolInject: config.MCPDisableAutoToolInject, AsyncJobResultTTL: config.AsyncJobResultTTL, RequiredHeaders: config.RequiredHeaders, LoggingHeaders: config.LoggingHeaders, @@ -223,6 +224,7 @@ func (s *RDBConfigStore) GetClientConfig(ctx context.Context) (*ClientConfig, er MCPToolExecutionTimeout: dbConfig.MCPToolExecutionTimeout, MCPCodeModeBindingLevel: dbConfig.MCPCodeModeBindingLevel, MCPToolSyncInterval: dbConfig.MCPToolSyncInterval, + MCPDisableAutoToolInject: dbConfig.MCPDisableAutoToolInject, AsyncJobResultTTL: dbConfig.AsyncJobResultTTL, RequiredHeaders: dbConfig.RequiredHeaders, LoggingHeaders: dbConfig.LoggingHeaders, @@ -901,9 +903,10 @@ func (s *RDBConfigStore) GetMCPConfig(ctx context.Context) (*schemas.MCPConfig, return nil, err } toolManagerConfig := schemas.MCPToolManagerConfig{ - ToolExecutionTimeout: time.Duration(clientConfig.MCPToolExecutionTimeout) * time.Second, - MaxAgentDepth: clientConfig.MCPAgentDepth, - CodeModeBindingLevel: schemas.CodeModeBindingLevel(clientConfig.MCPCodeModeBindingLevel), + ToolExecutionTimeout: time.Duration(clientConfig.MCPToolExecutionTimeout) * time.Second, + MaxAgentDepth: clientConfig.MCPAgentDepth, + CodeModeBindingLevel: schemas.CodeModeBindingLevel(clientConfig.MCPCodeModeBindingLevel), + DisableAutoToolInject: clientConfig.MCPDisableAutoToolInject, } clientConfigs := make([]*schemas.MCPClientConfig, len(dbMCPClients)) for i, dbClient := range dbMCPClients { diff --git a/framework/configstore/tables/clientconfig.go b/framework/configstore/tables/clientconfig.go index e64e966f4a..5a34f2406c 100644 --- a/framework/configstore/tables/clientconfig.go +++ b/framework/configstore/tables/clientconfig.go @@ -29,6 +29,7 @@ type TableClientConfig struct { MCPToolExecutionTimeout int `gorm:"default:30" json:"mcp_tool_execution_timeout"` // Timeout for individual tool execution in seconds (default: 30) MCPCodeModeBindingLevel string `gorm:"default:server" json:"mcp_code_mode_binding_level"` // How tools are exposed in VFS: "server" or "tool" MCPToolSyncInterval int `gorm:"default:10" json:"mcp_tool_sync_interval"` // Global tool sync interval in minutes (default: 10, 0 = disabled) + MCPDisableAutoToolInject bool `gorm:"default:false" json:"mcp_disable_auto_tool_inject"` // When true, MCP tools are not injected into requests by default AsyncJobResultTTL int `gorm:"default:3600" json:"async_job_result_ttl"` // Default TTL for async job results in seconds (default: 3600 = 1 hour) RequiredHeadersJSON string `gorm:"type:text" json:"-"` // JSON serialized []string LoggingHeadersJSON string `gorm:"type:text" json:"-"` // JSON serialized []string diff --git a/transports/bifrost-http/handlers/config.go b/transports/bifrost-http/handlers/config.go index 96119f0879..dfb4c3967d 100644 --- a/transports/bifrost-http/handlers/config.go +++ b/transports/bifrost-http/handlers/config.go @@ -47,7 +47,7 @@ type ConfigManager interface { ReloadPricingManager(ctx context.Context) error ForceReloadPricing(ctx context.Context) error UpdateDropExcessRequests(ctx context.Context, value bool) - UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error + UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string, disableAutoToolInject bool) error ReloadPlugin(ctx context.Context, name string, path *string, pluginConfig any, placement *schemas.PluginPlacement, order *int) error RemovePlugin(ctx context.Context, name string) error ReloadProxyConfig(ctx context.Context, config *configstoreTables.GlobalProxyConfig) error @@ -276,9 +276,14 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) { shouldReloadMCPToolManagerConfig = true } - // Only reload MCP tool manager config if MCP is configured + updatedConfig.MCPDisableAutoToolInject = payload.ClientConfig.MCPDisableAutoToolInject + if payload.ClientConfig.MCPDisableAutoToolInject != currentConfig.MCPDisableAutoToolInject { + shouldReloadMCPToolManagerConfig = true + } + + // Reload MCP tool manager config with all current values in one call if shouldReloadMCPToolManagerConfig && h.store.MCPConfig != nil { - if err := h.configManager.UpdateMCPToolManagerConfig(ctx, updatedConfig.MCPAgentDepth, updatedConfig.MCPToolExecutionTimeout, updatedConfig.MCPCodeModeBindingLevel); err != nil { + if err := h.configManager.UpdateMCPToolManagerConfig(ctx, updatedConfig.MCPAgentDepth, updatedConfig.MCPToolExecutionTimeout, updatedConfig.MCPCodeModeBindingLevel, updatedConfig.MCPDisableAutoToolInject); err != nil { logger.Warn(fmt.Sprintf("failed to update mcp tool manager config: %v", err)) SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to update mcp tool manager config: %v", err)) return diff --git a/transports/bifrost-http/lib/config_test.go b/transports/bifrost-http/lib/config_test.go index bb2f2852c1..70037d0a50 100644 --- a/transports/bifrost-http/lib/config_test.go +++ b/transports/bifrost-http/lib/config_test.go @@ -15412,7 +15412,8 @@ var excludedGoFields = map[string]map[string]bool{ "mcp_agent_depth": true, // Managed via MCP config "mcp_code_mode_binding_level": true, "mcp_tool_execution_timeout": true, - "mcp_tool_sync_interval": true, + "mcp_tool_sync_interval": true, + "mcp_disable_auto_tool_inject": true, }, "configstore.ProviderConfig": {"ConfigHash": true}, // GovernanceConfig - some fields are internal/enterprise diff --git a/transports/bifrost-http/server/server.go b/transports/bifrost-http/server/server.go index 0c48fb9984..aa256bbdd1 100644 --- a/transports/bifrost-http/server/server.go +++ b/transports/bifrost-http/server/server.go @@ -88,7 +88,7 @@ type ServerCallbacks interface { AddMCPClient(ctx context.Context, clientConfig *schemas.MCPClientConfig) error RemoveMCPClient(ctx context.Context, id string) error UpdateMCPClient(ctx context.Context, id string, updatedConfig *schemas.MCPClientConfig) error - UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error + UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string, disableAutoToolInject bool) error ReconnectMCPClient(ctx context.Context, id string) error // Logging related callbacks NewLogEntryAdded(ctx context.Context, logEntry *logstore.Log) error @@ -698,12 +698,13 @@ func (s *BifrostHTTPServer) UpdateDropExcessRequests(ctx context.Context, value s.Client.UpdateDropExcessRequests(value) } -// UpdateMCPToolManagerConfig updates the MCP tool manager config -func (s *BifrostHTTPServer) UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error { +// UpdateMCPToolManagerConfig updates the MCP tool manager config. +// Always pass the current disableAutoToolInject value so it is never reset. +func (s *BifrostHTTPServer) UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string, disableAutoToolInject bool) error { if s.Config == nil { return fmt.Errorf("config not found") } - return s.Client.UpdateToolManagerConfig(maxAgentDepth, toolExecutionTimeoutInSeconds, codeModeBindingLevel) + return s.Client.UpdateToolManagerConfig(maxAgentDepth, toolExecutionTimeoutInSeconds, codeModeBindingLevel, disableAutoToolInject) } // reloadObservabilityPlugins reloads all observability plugins in the tracing middleware diff --git a/transports/changelog.md b/transports/changelog.md index e69de29bb2..ca15010d82 100644 --- a/transports/changelog.md +++ b/transports/changelog.md @@ -0,0 +1 @@ +- feat: add option to disable automatic MCP tool injection per request diff --git a/transports/config.schema.json b/transports/config.schema.json index 04edba6dfc..327e3a42a3 100644 --- a/transports/config.schema.json +++ b/transports/config.schema.json @@ -173,6 +173,11 @@ "minimum": 0, "description": "Global tool sync interval in minutes (0 = disabled)", "default": 10 + }, + "mcp_disable_auto_tool_inject": { + "type": "boolean", + "description": "When true, MCP tools are not automatically injected into requests. Tools are only included when explicitly specified via request context filters or headers, such as x-bf-mcp-include-tools or x-bf-mcp-include-clients.", + "default": false } }, "additionalProperties": false @@ -2674,6 +2679,11 @@ "type": "string", "enum": ["server", "tool"], "description": "How tools are exposed in VFS for code execution" + }, + "disable_auto_tool_inject": { + "type": "boolean", + "description": "When true, MCP tools are not automatically injected into requests. Tools are only included when explicitly specified via request context filters or headers, such as x-bf-mcp-include-tools or x-bf-mcp-include-clients.", + "default": false } } }, diff --git a/transports/schema_test/config_schema_test.go b/transports/schema_test/config_schema_test.go index 70baceeb85..cb551591cc 100644 --- a/transports/schema_test/config_schema_test.go +++ b/transports/schema_test/config_schema_test.go @@ -272,6 +272,7 @@ func TestSchemaClientMCPFields(t *testing.T) { "mcp_tool_execution_timeout", "mcp_code_mode_binding_level", "mcp_tool_sync_interval", + "mcp_disable_auto_tool_inject", } for _, field := range fields { t.Run("client has "+field, func(t *testing.T) { @@ -290,7 +291,8 @@ func TestSchemaClientMCPFields(t *testing.T) { "mcp_agent_depth": 5, "mcp_tool_execution_timeout": 60, "mcp_code_mode_binding_level": "server", - "mcp_tool_sync_interval": 10 + "mcp_tool_sync_interval": 10, + "mcp_disable_auto_tool_inject": false } }` if err := validateConfig(t, compiled, config); err != nil { diff --git a/ui/app/workspace/config/views/mcpView.tsx b/ui/app/workspace/config/views/mcpView.tsx index eaf84d3bb7..5bc0a83a15 100644 --- a/ui/app/workspace/config/views/mcpView.tsx +++ b/ui/app/workspace/config/views/mcpView.tsx @@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; import { getErrorMessage, useGetCoreConfigQuery, useUpdateCoreConfigMutation } from "@/lib/store"; import { CoreConfig, DefaultCoreConfig } from "@/lib/types/config"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; @@ -40,13 +41,15 @@ export default function MCPView() { } }, [config, bifrostConfig]); + const hasChanges = useMemo(() => { if (!config) return false; return ( localConfig.mcp_agent_depth !== config.mcp_agent_depth || localConfig.mcp_tool_execution_timeout !== config.mcp_tool_execution_timeout || localConfig.mcp_code_mode_binding_level !== (config.mcp_code_mode_binding_level || "server") || - localConfig.mcp_tool_sync_interval !== (config.mcp_tool_sync_interval ?? 10) + localConfig.mcp_tool_sync_interval !== (config.mcp_tool_sync_interval ?? 10) || + localConfig.mcp_disable_auto_tool_inject !== (config.mcp_disable_auto_tool_inject ?? false) ); }, [config, localConfig]); @@ -81,6 +84,10 @@ export default function MCPView() { } }, []); + const handleDisableAutoToolInjectChange = useCallback((checked: boolean) => { + setLocalConfig((prev) => ({ ...prev, mcp_disable_auto_tool_inject: checked })); + }, []); + const handleSave = useCallback(async () => { try { const agentDepth = Number.parseInt(localValues.mcp_agent_depth); @@ -170,6 +177,25 @@ export default function MCPView() { /> + {/* Disable Auto Tool Injection */} +
+
+ +

+ When enabled, MCP tools are not automatically included in every request. Tools are only injected when explicitly specified via request headers (x-bf-mcp-include-tools) or virtual key configuration. +

+
+ +
+ {/* Code Mode Binding Level */}
diff --git a/ui/app/workspace/logs/sheets/logDetailsSheet.tsx b/ui/app/workspace/logs/sheets/logDetailsSheet.tsx index 527010e3e5..3e2beee8e6 100644 --- a/ui/app/workspace/logs/sheets/logDetailsSheet.tsx +++ b/ui/app/workspace/logs/sheets/logDetailsSheet.tsx @@ -28,7 +28,7 @@ import { StatusColors, } from "@/lib/constants/logs"; import { LogEntry } from "@/lib/types/logs"; -import { Clipboard, DollarSign, FileText, MoreVertical, Timer, Trash2 } from "lucide-react"; +import { Clipboard, MoreVertical, Trash2 } from "lucide-react"; import moment from "moment"; import { toast } from "sonner"; import BlockHeader from "../views/blockHeader"; diff --git a/ui/lib/types/config.ts b/ui/lib/types/config.ts index 252962c5ef..6fd46509f9 100644 --- a/ui/lib/types/config.ts +++ b/ui/lib/types/config.ts @@ -474,6 +474,7 @@ export interface CoreConfig { mcp_tool_execution_timeout: number; mcp_code_mode_binding_level?: string; mcp_tool_sync_interval: number; + mcp_disable_auto_tool_inject: boolean; async_job_result_ttl: number; required_headers: string[]; logging_headers: string[]; @@ -498,6 +499,7 @@ export const DefaultCoreConfig: CoreConfig = { mcp_tool_execution_timeout: 30, mcp_code_mode_binding_level: "server", mcp_tool_sync_interval: 10, + mcp_disable_auto_tool_inject: false, async_job_result_ttl: 3600, allowed_headers: [], required_headers: [], diff --git a/ui/lib/types/schemas.ts b/ui/lib/types/schemas.ts index 71e5b04108..0f581dab3b 100644 --- a/ui/lib/types/schemas.ts +++ b/ui/lib/types/schemas.ts @@ -611,6 +611,7 @@ export const coreConfigSchema = z.object({ mcp_agent_depth: z.number().min(1).default(10), mcp_tool_execution_timeout: z.number().min(1).default(30), mcp_code_mode_binding_level: z.enum(["server", "tool"]).default("server"), + mcp_disable_auto_tool_inject: z.boolean().default(false), }); // Bifrost config schema