diff --git a/core/bifrost.go b/core/bifrost.go index 1405ccf65..f754cc810 100644 --- a/core/bifrost.go +++ b/core/bifrost.go @@ -1886,7 +1886,7 @@ 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) error { +func (bifrost *Bifrost) UpdateToolManagerConfig(maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error { if bifrost.mcpManager == nil { return fmt.Errorf("MCP is not configured in this Bifrost instance") } @@ -1894,6 +1894,7 @@ func (bifrost *Bifrost) UpdateToolManagerConfig(maxAgentDepth int, toolExecution bifrost.mcpManager.UpdateToolManagerConfig(&schemas.MCPToolManagerConfig{ MaxAgentDepth: maxAgentDepth, ToolExecutionTimeout: time.Duration(toolExecutionTimeoutInSeconds) * time.Second, + CodeModeBindingLevel: schemas.CodeModeBindingLevel(codeModeBindingLevel), }) return nil } diff --git a/core/changelog.md b/core/changelog.md index d0b882b5d..b717cf5e0 100644 --- a/core/changelog.md +++ b/core/changelog.md @@ -1 +1,3 @@ -- fix: gemini thought signature handling in multi-turn conversations \ No newline at end of file +- feat: added code mode to mcp +- feat: added health monitoring to mcp +- feat: added responses format tool execution support to mcp \ No newline at end of file diff --git a/core/mcp/codemode_listfiles.go b/core/mcp/codemode_listfiles.go index a35a7405a..730a1083d 100644 --- a/core/mcp/codemode_listfiles.go +++ b/core/mcp/codemode_listfiles.go @@ -10,24 +10,39 @@ import ( // createListToolFilesTool creates the listToolFiles tool definition for code mode. // This tool allows listing all available virtual .d.ts declaration files for connected MCP servers. +// The description is dynamically generated based on the configured CodeModeBindingLevel. // // Returns: // - schemas.ChatTool: The tool definition for listing tool files func (m *ToolsManager) createListToolFilesTool() schemas.ChatTool { + bindingLevel := m.GetCodeModeBindingLevel() + var description string + + if bindingLevel == schemas.CodeModeBindingLevelServer { + description = "Returns a tree structure listing all virtual .d.ts declaration files available for connected MCP servers. " + + "Each server has a corresponding file (e.g., servers/.d.ts) that contains definitions for all tools in that server. " + + "Use readToolFile to read a specific server file and see all available tools. " + + "In code, access tools via: await serverName.toolName({ args }). " + + "The server names used in code correspond to the human-readable names shown in this listing. " + + "This tool is generic and works with any set of servers connected at runtime. " + + "Always check this tool whenever you are unsure about what tools you have available or if you want to verify available servers and their tools. " + + "If you have even the SLIGHTEST DOUBT that the current tools might not be useful for the task, check listToolFiles to discover all available tools." + } else { + description = "Returns a tree structure listing all virtual .d.ts declaration files available for connected MCP servers, organized by individual tool. " + + "Each tool has a corresponding file (e.g., servers//.d.ts) that contains definitions for that specific tool. " + + "Use readToolFile to read a specific tool file and see its parameters and usage. " + + "In code, access tools via: await serverName.toolName({ args }). " + + "The server names used in code correspond to the human-readable names shown in this listing. " + + "This tool is generic and works with any set of servers connected at runtime. " + + "Always check this tool whenever you are unsure about what tools you have available or if you want to verify available servers and their tools. " + + "If you have even the SLIGHTEST DOUBT that the current tools might not be useful for the task, check listToolFiles to discover all available tools." + } + return schemas.ChatTool{ Type: schemas.ChatToolTypeFunction, Function: &schemas.ChatToolFunction{ Name: ToolTypeListToolFiles, - Description: schemas.Ptr( - "Returns a tree structure listing all virtual .d.ts declaration files available for connected MCP servers. " + - "Each connected server has a corresponding virtual file that can be read using readToolFile. " + - "The filenames follow the pattern .d.ts where serverDisplayName is the human-readable " + - "name reported by each connected server. Note that the code-level bindings (used in executeToolCode) use " + - "configuration keys from SERVER_CONFIGS, which may differ from these display names. " + - "This tool is generic and works with any set of servers connected at runtime. " + - "Always check this tool whenever you are unsure about what tools you have available or if you want to verify available servers and their tools. " + - "If you have even the SLIGHTEST DOUBT that the current tools might not be useful for the task, check listToolFiles to discover all available tools.", - ), + Description: schemas.Ptr(description), Parameters: &schemas.ToolFunctionParameters{ Type: "object", Properties: &schemas.OrderedMap{}, @@ -39,6 +54,9 @@ func (m *ToolsManager) createListToolFilesTool() schemas.ChatTool { // handleListToolFiles handles the listToolFiles tool call. // It builds a tree structure listing all virtual .d.ts files available for code mode clients. +// The structure depends on the CodeModeBindingLevel: +// - "server": servers/.d.ts (one file per server) +// - "tool": servers//.d.ts (one file per tool) // // Parameters: // - ctx: Context for accessing client tools @@ -56,10 +74,14 @@ func (m *ToolsManager) handleListToolFiles(ctx context.Context, toolCall schemas return createToolResponseMessage(toolCall, responseText), nil } - // Build tree structure - treeLines := []string{"servers/"} + // Get the code mode binding level + bindingLevel := m.GetCodeModeBindingLevel() + + // Build file list based on binding level + var files []string codeModeServerCount := 0 - for clientName := range availableToolsPerClient { + + for clientName, tools := range availableToolsPerClient { client := m.clientManager.GetClientByName(clientName) if client == nil { logger.Warn(fmt.Sprintf("%s Client %s not found, skipping", MCPLogPrefix, clientName)) @@ -69,7 +91,19 @@ func (m *ToolsManager) handleListToolFiles(ctx context.Context, toolCall schemas continue } codeModeServerCount++ - treeLines = append(treeLines, fmt.Sprintf(" %s.d.ts", clientName)) + + if bindingLevel == schemas.CodeModeBindingLevelServer { + // Server-level: one file per server + files = append(files, fmt.Sprintf("servers/%s.d.ts", clientName)) + } else { + // Tool-level: one file per tool + for _, tool := range tools { + if tool.Function != nil && tool.Function.Name != "" { + toolFileName := fmt.Sprintf("servers/%s/%s.d.ts", clientName, tool.Function.Name) + files = append(files, toolFileName) + } + } + } } if codeModeServerCount == 0 { @@ -78,6 +112,118 @@ func (m *ToolsManager) handleListToolFiles(ctx context.Context, toolCall schemas return createToolResponseMessage(toolCall, responseText), nil } - responseText := strings.Join(treeLines, "\n") + // Build tree structure from file list + responseText := buildVFSTree(files) return createToolResponseMessage(toolCall, responseText), nil } + +// VFS tree node structure for building hierarchical file structure +type treeNode struct { + isDirectory bool + children map[string]*treeNode +} + +// buildVFSTree creates a hierarchical tree structure from a flat list of file paths. +// It groups files by directory and formats them with proper indentation. +// +// Example input: +// - ["servers/calculator.d.ts", "servers/youtube.d.ts"] +// - ["servers/calculator/add.d.ts", "servers/youtube/GET_CHANNELS.d.ts"] +// +// Example output for server-level: +// servers/ +// calculator.d.ts +// youtube.d.ts +// +// Example output for tool-level: +// servers/ +// calculator/ +// add.d.ts +// youtube/ +// GET_CHANNELS.d.ts +func buildVFSTree(files []string) string { + if len(files) == 0 { + return "" + } + + root := &treeNode{ + isDirectory: true, + children: make(map[string]*treeNode), + } + + // Parse all files and build tree structure + for _, file := range files { + parts := strings.Split(file, "/") + current := root + + // Create all intermediate directories and final file + for i, part := range parts { + if _, exists := current.children[part]; !exists { + current.children[part] = &treeNode{ + isDirectory: i < len(parts)-1, // Last part is file, not directory + children: make(map[string]*treeNode), + } + } + current = current.children[part] + } + } + + // Render tree structure with proper indentation + var lines []string + renderTreeNode(root, "", &lines, true) + + return strings.Join(lines, "\n") +} + +// renderTreeNode recursively renders a tree node and its children with proper indentation. +func renderTreeNode(node *treeNode, indent string, lines *[]string, isRoot bool) { + // Get sorted keys for consistent output + var keys []string + for key := range node.children { + keys = append(keys, key) + } + + // Simple bubble sort for small lists (good enough for this use case) + for i := 0; i < len(keys); i++ { + for j := i + 1; j < len(keys); j++ { + if keys[j] < keys[i] { + keys[i], keys[j] = keys[j], keys[i] + } + } + } + + for _, key := range keys { + child := node.children[key] + + // Format the line + var line string + if isRoot { + // Root level - no indentation + if child.isDirectory { + line = key + "/" + } else { + line = key + } + } else { + // Non-root levels - add indentation + if child.isDirectory { + line = indent + key + "/" + } else { + line = indent + key + } + } + + *lines = append(*lines, line) + + // Recurse into children + if child.isDirectory && len(child.children) > 0 { + var nextIndent string + if isRoot { + nextIndent = " " + } else { + nextIndent = indent + " " + } + renderTreeNode(child, nextIndent, lines, false) + } + } +} diff --git a/core/mcp/codemode_readfile.go b/core/mcp/codemode_readfile.go index 28c8cc9e4..776a6ac3b 100644 --- a/core/mcp/codemode_readfile.go +++ b/core/mcp/codemode_readfile.go @@ -10,16 +10,45 @@ import ( ) // createReadToolFileTool creates the readToolFile tool definition for code mode. -// This tool allows reading virtual .d.ts declaration files for specific MCP servers, +// This tool allows reading virtual .d.ts declaration files for specific MCP servers/tools, // generating TypeScript type definitions from the server's tool schemas. +// The description is dynamically generated based on the configured CodeModeBindingLevel. // // Returns: // - schemas.ChatTool: The tool definition for reading tool files func (m *ToolsManager) createReadToolFileTool() schemas.ChatTool { + bindingLevel := m.GetCodeModeBindingLevel() + + var fileNameDescription, toolDescription string + + if bindingLevel == schemas.CodeModeBindingLevelServer { + fileNameDescription = "The virtual filename from listToolFiles in format: servers/.d.ts (e.g., 'calculator.d.ts')" + toolDescription = "Reads a virtual .d.ts declaration file for a specific MCP server, generating TypeScript type definitions " + + "for all tools available on that server. The fileName should be in format servers/.d.ts as listed by listToolFiles. " + + "The function performs case-insensitive matching and removes the .d.ts extension. " + + "Optionally, you can specify startLine and endLine (1-based, inclusive) to read only a portion of the file. " + + "IMPORTANT: Line numbers are 1-based, not 0-based. The first line is line 1, not line 0. " + + "This generates TypeScript type definitions describing all tools in the server and their argument types, " + + "enabling code-mode execution. Each tool can be accessed in code via: await serverName.toolName({ args }). " + + "Always follow this workflow: first use listToolFiles to see available servers, then use readToolFile to understand " + + "all available tool definitions for a server, and finally use executeToolCode to execute your code." + } else { + fileNameDescription = "The virtual filename from listToolFiles in format: servers//.d.ts (e.g., 'calculator/add.d.ts')" + toolDescription = "Reads a virtual .d.ts declaration file for a specific tool, generating TypeScript type definitions " + + "for that individual tool. The fileName should be in format servers//.d.ts as listed by listToolFiles. " + + "The function performs case-insensitive matching and removes the .d.ts extension. " + + "Optionally, you can specify startLine and endLine (1-based, inclusive) to read only a portion of the file. " + + "IMPORTANT: Line numbers are 1-based, not 0-based. The first line is line 1, not line 0. " + + "This generates TypeScript type definitions for a single tool, describing its parameters and usage, " + + "enabling focused code-mode execution. The tool can be accessed in code via: await serverName.toolName({ args }). " + + "Always follow this workflow: first use listToolFiles to see available tools, then use readToolFile to understand " + + "a specific tool's definition, and finally use executeToolCode to execute your code." + } + readToolFileProps := schemas.OrderedMap{ "fileName": map[string]interface{}{ "type": "string", - "description": "The virtual filename (e.g., 'calculator-server.d.ts') from listToolFiles", + "description": fileNameDescription, }, "startLine": map[string]interface{}{ "type": "number", @@ -34,18 +63,7 @@ func (m *ToolsManager) createReadToolFileTool() schemas.ChatTool { Type: schemas.ChatToolTypeFunction, Function: &schemas.ChatToolFunction{ Name: ToolTypeReadToolFile, - Description: schemas.Ptr( - "Reads a virtual .d.ts declaration file for a specific MCP server, generating TypeScript type definitions " + - "from the server's tool schemas. The fileName should match one of the virtual files listed by listToolFiles. " + - "The function removes the .d.ts extension and performs case-insensitive matching against both the server's " + - "display name and configuration key. Optionally, you can specify startLine and endLine (1-based, inclusive) " + - "to read only a portion of the file. IMPORTANT: Line numbers are 1-based, not 0-based. The first line is line 1, not line 0. " + - "This tool generates pseudo declaration files that describe the available " + - "tools and their argument types, enabling code-mode execution as described in the MCP code execution pattern. " + - "The generated file includes interfaces for each tool's arguments and corresponding function declarations. " + - "Always follow this workflow: first use listToolFiles to see available servers, then use readToolFile to understand " + - "the tool definitions, and finally use executeToolCode to execute your code.", - ), + Description: schemas.Ptr(toolDescription), Parameters: &schemas.ToolFunctionParameters{ Type: "object", Properties: &readToolFileProps, @@ -56,8 +74,9 @@ func (m *ToolsManager) createReadToolFileTool() schemas.ChatTool { } // handleReadToolFile handles the readToolFile tool call. -// It reads a virtual .d.ts file for a specific MCP server, generates TypeScript type definitions, +// It reads a virtual .d.ts file for a specific MCP server/tool, generates TypeScript type definitions, // and optionally returns a portion of the file based on line range parameters. +// Supports both server-level files (e.g., "calculator.d.ts") and tool-level files (e.g., "calculator/add.d.ts"). // // Parameters: // - ctx: Context for accessing client tools @@ -78,15 +97,17 @@ func (m *ToolsManager) handleReadToolFile(ctx context.Context, toolCall schemas. return nil, fmt.Errorf("fileName parameter is required and must be a string") } + // Parse the file path to extract server name and optional tool name + serverName, toolName, isToolLevel := parseVFSFilePath(fileName) + // Get available tools per client availableToolsPerClient := m.clientManager.GetToolPerClient(ctx) - // Remove .d.ts extension and normalize to lowercase for matching - baseName := strings.ToLower(strings.TrimSuffix(fileName, ".d.ts")) - // Find matching client var matchedClientName string var matchedTools []schemas.ChatTool + matchCount := 0 + for clientName, tools := range availableToolsPerClient { client := m.clientManager.GetClientByName(clientName) if client == nil { @@ -96,43 +117,90 @@ func (m *ToolsManager) handleReadToolFile(ctx context.Context, toolCall schemas. if !client.ExecutionConfig.IsCodeModeClient || len(tools) == 0 { continue } + clientNameLower := strings.ToLower(clientName) - if clientNameLower == baseName { - if matchedClientName != "" { + serverNameLower := strings.ToLower(serverName) + + if clientNameLower == serverNameLower { + matchCount++ + if matchCount > 1 { // Multiple matches found - availableFiles := make([]string, 0, len(availableToolsPerClient)) - for name := range availableToolsPerClient { - availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name)) - } errorMsg := fmt.Sprintf("Multiple servers match filename '%s':\n", fileName) for name := range availableToolsPerClient { - if strings.ToLower(name) == baseName { + if strings.ToLower(name) == serverNameLower { errorMsg += fmt.Sprintf(" - %s\n", name) } } errorMsg += "\nPlease use a more specific filename. Use the exact display name from listToolFiles to avoid ambiguity." return createToolResponseMessage(toolCall, errorMsg), nil } + matchedClientName = clientName - matchedTools = tools + + if isToolLevel { + // Tool-level: filter to specific tool + var foundTool *schemas.ChatTool + toolNameLower := strings.ToLower(toolName) + for i, tool := range tools { + if tool.Function != nil && strings.ToLower(tool.Function.Name) == toolNameLower { + foundTool = &tools[i] + break + } + } + + if foundTool == nil { + availableTools := make([]string, 0) + for _, tool := range tools { + if tool.Function != nil { + availableTools = append(availableTools, tool.Function.Name) + } + } + errorMsg := fmt.Sprintf("Tool '%s' not found in server '%s'. Available tools in this server are:\n", toolName, clientName) + for _, t := range availableTools { + errorMsg += fmt.Sprintf(" - %s/%s.d.ts\n", clientName, t) + } + return createToolResponseMessage(toolCall, errorMsg), nil + } + + matchedTools = []schemas.ChatTool{*foundTool} + } else { + // Server-level: use all tools + matchedTools = tools + } } } if matchedClientName == "" { - availableFiles := make([]string, 0, len(availableToolsPerClient)) + // Build helpful error message with available files + bindingLevel := m.GetCodeModeBindingLevel() + var availableFiles []string + for name := range availableToolsPerClient { - availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name)) + if bindingLevel == schemas.CodeModeBindingLevelServer { + availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name)) + } else { + client := m.clientManager.GetClientByName(name) + if client != nil && client.ExecutionConfig.IsCodeModeClient { + if tools, ok := availableToolsPerClient[name]; ok { + for _, tool := range tools { + if tool.Function != nil { + availableFiles = append(availableFiles, fmt.Sprintf("%s/%s.d.ts", name, tool.Function.Name)) + } + } + } + } + } } - errorMsg := fmt.Sprintf("No server found matching filename '%s'. Available virtual files are:\n", fileName) + + errorMsg := fmt.Sprintf("No server found matching '%s'. Available virtual files are:\n", serverName) for _, f := range availableFiles { errorMsg += fmt.Sprintf(" - %s\n", f) } - errorMsg += "\nPlease use one of the exact filenames listed above. The matching is case-insensitive and works with both display names and configuration keys." return createToolResponseMessage(toolCall, errorMsg), nil } // Generate TypeScript definitions - fileContent := generateTypeDefinitions(matchedClientName, matchedTools) + fileContent := generateTypeDefinitions(matchedClientName, matchedTools, isToolLevel) lines := strings.Split(fileContent, "\n") totalLines := len(lines) @@ -184,6 +252,34 @@ func (m *ToolsManager) handleReadToolFile(ctx context.Context, toolCall schemas. // HELPER FUNCTIONS +// parseVFSFilePath parses a VFS file path and extracts the server name and optional tool name. +// For server-level paths (e.g., "calculator.d.ts"), returns (serverName="calculator", toolName="", isToolLevel=false) +// For tool-level paths (e.g., "calculator/add.d.ts"), returns (serverName="calculator", toolName="add", isToolLevel=true) +// +// Parameters: +// - fileName: The virtual file path from listToolFiles +// +// Returns: +// - serverName: The name of the MCP server +// - toolName: The name of the tool (empty for server-level) +// - isToolLevel: Whether this is a tool-level path +func parseVFSFilePath(fileName string) (serverName, toolName string, isToolLevel bool) { + // Remove .d.ts extension + basePath := strings.TrimSuffix(fileName, ".d.ts") + + // Remove "servers/" prefix if present + basePath = strings.TrimPrefix(basePath, "servers/") + + // Check for path separator + parts := strings.Split(basePath, "/") + if len(parts) == 2 { + // Tool-level: "serverName/toolName" + return parts[0], parts[1], true + } + // Server-level: "serverName" + return basePath, "", false +} + // generateTypeDefinitions generates TypeScript type definitions from ChatTool schemas // with comprehensive comments to help LLMs understand how to use the tools. // It creates interfaces for tool inputs and responses, along with function declarations. @@ -191,18 +287,29 @@ func (m *ToolsManager) handleReadToolFile(ctx context.Context, toolCall schemas. // Parameters: // - clientName: Name of the MCP client/server // - tools: List of chat tools to generate definitions for +// - isToolLevel: Whether this is a tool-level definition (single tool) or server-level (all tools) // // Returns: // - string: Complete TypeScript declaration file content -func generateTypeDefinitions(clientName string, tools []schemas.ChatTool) string { +func generateTypeDefinitions(clientName string, tools []schemas.ChatTool, isToolLevel bool) string { var sb strings.Builder // Write comprehensive header comment sb.WriteString("// ============================================================================\n") - sb.WriteString(fmt.Sprintf("// Type definitions for %s MCP server\n", clientName)) + if isToolLevel && len(tools) == 1 && tools[0].Function != nil { + // Tool-level: show individual tool name + sb.WriteString(fmt.Sprintf("// Type definitions for %s.%s tool\n", clientName, tools[0].Function.Name)) + } else { + // Server-level: show all tools in server + sb.WriteString(fmt.Sprintf("// Type definitions for %s MCP server\n", clientName)) + } sb.WriteString("// ============================================================================\n") sb.WriteString("//\n") - sb.WriteString("// This file contains TypeScript type definitions for all tools available on this MCP server.\n") + if isToolLevel && len(tools) == 1 { + sb.WriteString("// This file contains TypeScript type definitions for a specific tool on this MCP server.\n") + } else { + sb.WriteString("// This file contains TypeScript type definitions for all tools available on this MCP server.\n") + } sb.WriteString("// These definitions enable code-mode execution as described in the MCP code execution pattern.\n") sb.WriteString("//\n") sb.WriteString("// USAGE INSTRUCTIONS:\n") diff --git a/core/mcp/toolmanager.go b/core/mcp/toolmanager.go index e0b719ed9..ca4233234 100644 --- a/core/mcp/toolmanager.go +++ b/core/mcp/toolmanager.go @@ -22,6 +22,7 @@ type ClientManager interface { type ToolsManager struct { toolExecutionTimeout atomic.Value maxAgentDepth atomic.Int32 + codeModeBindingLevel atomic.Value // Stores CodeModeBindingLevel clientManager ClientManager logMu sync.Mutex // Protects concurrent access to logs slice in codemode execution @@ -54,6 +55,7 @@ func NewToolsManager(config *schemas.MCPToolManagerConfig, clientManager ClientM config = &schemas.MCPToolManagerConfig{ ToolExecutionTimeout: schemas.DefaultToolExecutionTimeout, MaxAgentDepth: schemas.DefaultMaxAgentDepth, + CodeModeBindingLevel: schemas.CodeModeBindingLevelServer, } } if config.MaxAgentDepth <= 0 { @@ -62,6 +64,10 @@ func NewToolsManager(config *schemas.MCPToolManagerConfig, clientManager ClientM if config.ToolExecutionTimeout <= 0 { config.ToolExecutionTimeout = schemas.DefaultToolExecutionTimeout } + // Default to server-level binding if not specified + if config.CodeModeBindingLevel == "" { + config.CodeModeBindingLevel = schemas.CodeModeBindingLevelServer + } manager := &ToolsManager{ clientManager: clientManager, fetchNewRequestIDFunc: fetchNewRequestIDFunc, @@ -69,8 +75,9 @@ func NewToolsManager(config *schemas.MCPToolManagerConfig, clientManager ClientM // Initialize atomic values manager.toolExecutionTimeout.Store(config.ToolExecutionTimeout) manager.maxAgentDepth.Store(int32(config.MaxAgentDepth)) + manager.codeModeBindingLevel.Store(config.CodeModeBindingLevel) - logger.Info(fmt.Sprintf("%s tool manager initialized with tool execution timeout: %v and max agent depth: %d", MCPLogPrefix, config.ToolExecutionTimeout, config.MaxAgentDepth)) + logger.Info(fmt.Sprintf("%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 } @@ -457,7 +464,7 @@ func (m *ToolsManager) ExecuteAgentForResponsesRequest( ) } -// UpdateConfig updates both tool execution timeout and max agent depth atomically. +// UpdateConfig updates tool manager configuration atomically. // This method is safe to call concurrently from multiple goroutines. func (m *ToolsManager) UpdateConfig(config *schemas.MCPToolManagerConfig) { if config == nil { @@ -469,6 +476,19 @@ func (m *ToolsManager) UpdateConfig(config *schemas.MCPToolManagerConfig) { if config.MaxAgentDepth > 0 { m.maxAgentDepth.Store(int32(config.MaxAgentDepth)) } + if config.CodeModeBindingLevel != "" { + m.codeModeBindingLevel.Store(config.CodeModeBindingLevel) + } + + logger.Info(fmt.Sprintf("%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)) +} - logger.Info(fmt.Sprintf("%s tool manager configuration updated with tool execution timeout: %v and max agent depth: %d", MCPLogPrefix, config.ToolExecutionTimeout, config.MaxAgentDepth)) +// GetCodeModeBindingLevel returns the current code mode binding level. +// This method is safe to call concurrently from multiple goroutines. +func (m *ToolsManager) GetCodeModeBindingLevel() schemas.CodeModeBindingLevel { + val := m.codeModeBindingLevel.Load() + if val == nil { + return schemas.CodeModeBindingLevelServer + } + return val.(schemas.CodeModeBindingLevel) } diff --git a/core/schemas/mcp.go b/core/schemas/mcp.go index 53ae358eb..663eaa1f4 100644 --- a/core/schemas/mcp.go +++ b/core/schemas/mcp.go @@ -23,8 +23,9 @@ type MCPConfig struct { } type MCPToolManagerConfig struct { - ToolExecutionTimeout time.Duration `json:"tool_execution_timeout"` - MaxAgentDepth int `json:"max_agent_depth"` + 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" } const ( @@ -32,6 +33,14 @@ const ( DefaultToolExecutionTimeout = 30 * time.Second ) +// CodeModeBindingLevel defines how tools are exposed in the VFS for code execution +type CodeModeBindingLevel string + +const ( + CodeModeBindingLevelServer CodeModeBindingLevel = "server" + CodeModeBindingLevelTool CodeModeBindingLevel = "tool" +) + // MCPClientConfig defines tool filtering for an MCP client. type MCPClientConfig struct { ID string `json:"id"` // Client ID diff --git a/framework/changelog.md b/framework/changelog.md index 84777f43e..e69de29bb 100644 --- a/framework/changelog.md +++ b/framework/changelog.md @@ -1 +0,0 @@ -- chore: upgraded version of core to 1.2.41 \ No newline at end of file diff --git a/framework/configstore/clientconfig.go b/framework/configstore/clientconfig.go index 5b55e7991..047676d36 100644 --- a/framework/configstore/clientconfig.go +++ b/framework/configstore/clientconfig.go @@ -49,6 +49,7 @@ type ClientConfig struct { EnableLiteLLMFallbacks bool `json:"enable_litellm_fallbacks"` // Enable litellm-specific fallbacks for text completion for Groq MCPAgentDepth int `json:"mcp_agent_depth"` // The maximum depth for MCP agent mode tool execution 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" ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized) } @@ -112,6 +113,12 @@ func (c *ClientConfig) GenerateClientConfigHash() (string, error) { hash.Write([]byte("mcpToolExecutionTimeout:0")) } + if c.MCPCodeModeBindingLevel != "" { + hash.Write([]byte("mcpCodeModeBindingLevel:" + c.MCPCodeModeBindingLevel)) + } else { + hash.Write([]byte("mcpCodeModeBindingLevel:server")) + } + // Hash integer fields data, err := sonic.Marshal(c.InitialPoolSize) if err != nil { diff --git a/framework/configstore/migrations.go b/framework/configstore/migrations.go index cf0bec4fd..4ac808636 100644 --- a/framework/configstore/migrations.go +++ b/framework/configstore/migrations.go @@ -98,6 +98,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error { if err := migrationAddMCPAgentDepthAndMCPToolExecutionTimeoutColumns(ctx, db); err != nil { return err } + if err := migrationAddMCPCodeModeBindingLevelColumn(ctx, db); err != nil { + return err + } if err := migrationNormalizeMCPClientNames(ctx, db); err != nil { return err } @@ -1314,6 +1317,37 @@ func migrationAddMCPAgentDepthAndMCPToolExecutionTimeoutColumns(ctx context.Cont return nil } +// migrationAddMCPCodeModeBindingLevelColumn adds the mcp_code_mode_binding_level column to the client config table. +// This column stores the code mode binding level preference (server or tool). +func migrationAddMCPCodeModeBindingLevelColumn(ctx context.Context, db *gorm.DB) error { + m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{ + ID: "add_mcp_code_mode_binding_level_column", + Migrate: func(tx *gorm.DB) error { + tx = tx.WithContext(ctx) + migratorInstance := tx.Migrator() + if !migratorInstance.HasColumn(&tables.TableClientConfig{}, "mcp_code_mode_binding_level") { + if err := migratorInstance.AddColumn(&tables.TableClientConfig{}, "mcp_code_mode_binding_level"); 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_code_mode_binding_level"); err != nil { + return err + } + return nil + }, + }}) + err := m.Migrate() + if err != nil { + return fmt.Errorf("error while running db migration: %s", err.Error()) + } + return nil +} + // normalizeMCPClientName normalizes an MCP client name by: // 1. Replacing hyphens and spaces with underscores // 2. Removing leading digits diff --git a/framework/configstore/rdb.go b/framework/configstore/rdb.go index eb6ac4604..e420cf328 100644 --- a/framework/configstore/rdb.go +++ b/framework/configstore/rdb.go @@ -44,6 +44,7 @@ func (s *RDBConfigStore) UpdateClientConfig(ctx context.Context, config *ClientC EnableLiteLLMFallbacks: config.EnableLiteLLMFallbacks, MCPAgentDepth: config.MCPAgentDepth, MCPToolExecutionTimeout: config.MCPToolExecutionTimeout, + MCPCodeModeBindingLevel: config.MCPCodeModeBindingLevel, } // Delete existing client config and create new one in a transaction return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { @@ -199,6 +200,7 @@ func (s *RDBConfigStore) GetClientConfig(ctx context.Context) (*ClientConfig, er EnableLiteLLMFallbacks: dbConfig.EnableLiteLLMFallbacks, MCPAgentDepth: dbConfig.MCPAgentDepth, MCPToolExecutionTimeout: dbConfig.MCPToolExecutionTimeout, + MCPCodeModeBindingLevel: dbConfig.MCPCodeModeBindingLevel, }, nil } @@ -764,6 +766,7 @@ func (s *RDBConfigStore) GetMCPConfig(ctx context.Context) (*schemas.MCPConfig, toolManagerConfig := schemas.MCPToolManagerConfig{ ToolExecutionTimeout: time.Duration(clientConfig.MCPToolExecutionTimeout) * time.Second, MaxAgentDepth: clientConfig.MCPAgentDepth, + CodeModeBindingLevel: schemas.CodeModeBindingLevel(clientConfig.MCPCodeModeBindingLevel), } return &schemas.MCPConfig{ ClientConfigs: clientConfigs, diff --git a/framework/configstore/tables/clientconfig.go b/framework/configstore/tables/clientconfig.go index 870dc1c4b..395277711 100644 --- a/framework/configstore/tables/clientconfig.go +++ b/framework/configstore/tables/clientconfig.go @@ -22,7 +22,9 @@ type TableClientConfig struct { AllowDirectKeys bool `gorm:"" json:"allow_direct_keys"` MaxRequestBodySizeMB int `gorm:"default:100" json:"max_request_body_size_mb"` MCPAgentDepth int `gorm:"default:10" json:"mcp_agent_depth"` - MCPToolExecutionTimeout int `gorm:"default:30" json:"mcp_tool_execution_timeout"` // Timeout for individual tool execution in seconds (default: 30) + 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" + // LiteLLM fallback flag EnableLiteLLMFallbacks bool `gorm:"column:enable_litellm_fallbacks;default:false" json:"enable_litellm_fallbacks"` diff --git a/framework/configstore/tables/mcp.go b/framework/configstore/tables/mcp.go index 687c60355..b991a545a 100644 --- a/framework/configstore/tables/mcp.go +++ b/framework/configstore/tables/mcp.go @@ -79,6 +79,7 @@ func (c *TableMCPClient) BeforeSave(tx *gorm.DB) error { } else { c.HeadersJSON = "{}" } + return nil } diff --git a/transports/bifrost-http/handlers/config.go b/transports/bifrost-http/handlers/config.go index 9c71933c2..4b0509d2c 100644 --- a/transports/bifrost-http/handlers/config.go +++ b/transports/bifrost-http/handlers/config.go @@ -12,6 +12,7 @@ import ( "github.com/fasthttp/router" bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/network" + "github.com/maximhq/bifrost/core/schemas" "github.com/maximhq/bifrost/framework" "github.com/maximhq/bifrost/framework/configstore" configstoreTables "github.com/maximhq/bifrost/framework/configstore/tables" @@ -28,7 +29,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) error + UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error ReloadPlugin(ctx context.Context, name string, path *string, pluginConfig any) error ReloadProxyConfig(ctx context.Context, config *configstoreTables.GlobalProxyConfig) error } @@ -229,6 +230,14 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) { return } + if payload.ClientConfig.MCPCodeModeBindingLevel != "" { + if payload.ClientConfig.MCPCodeModeBindingLevel != string(schemas.CodeModeBindingLevelServer) && payload.ClientConfig.MCPCodeModeBindingLevel != string(schemas.CodeModeBindingLevelTool) { + logger.Warn("mcp_code_mode_binding_level must be 'server' or 'tool'") + SendError(ctx, fasthttp.StatusBadRequest, "mcp_code_mode_binding_level must be 'server' or 'tool'") + return + } + } + shouldReloadMCPToolManagerConfig := false if payload.ClientConfig.MCPAgentDepth != currentConfig.MCPAgentDepth { @@ -241,8 +250,13 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) { shouldReloadMCPToolManagerConfig = true } + if payload.ClientConfig.MCPCodeModeBindingLevel != "" && payload.ClientConfig.MCPCodeModeBindingLevel != currentConfig.MCPCodeModeBindingLevel { + updatedConfig.MCPCodeModeBindingLevel = payload.ClientConfig.MCPCodeModeBindingLevel + shouldReloadMCPToolManagerConfig = true + } + if shouldReloadMCPToolManagerConfig { - if err := h.configManager.UpdateMCPToolManagerConfig(ctx, updatedConfig.MCPAgentDepth, updatedConfig.MCPToolExecutionTimeout); err != nil { + if err := h.configManager.UpdateMCPToolManagerConfig(ctx, updatedConfig.MCPAgentDepth, updatedConfig.MCPToolExecutionTimeout, updatedConfig.MCPCodeModeBindingLevel); 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 @@ -268,6 +282,10 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) { updatedConfig.EnableLiteLLMFallbacks = payload.ClientConfig.EnableLiteLLMFallbacks updatedConfig.MCPAgentDepth = payload.ClientConfig.MCPAgentDepth updatedConfig.MCPToolExecutionTimeout = payload.ClientConfig.MCPToolExecutionTimeout + // Only update MCPCodeModeBindingLevel if payload is non-empty to avoid clearing stored value + if payload.ClientConfig.MCPCodeModeBindingLevel != "" { + updatedConfig.MCPCodeModeBindingLevel = payload.ClientConfig.MCPCodeModeBindingLevel + } // Validate LogRetentionDays if payload.ClientConfig.LogRetentionDays < 1 { logger.Warn("log_retention_days must be at least 1") diff --git a/transports/bifrost-http/lib/config.go b/transports/bifrost-http/lib/config.go index ac243369c..719f60c84 100644 --- a/transports/bifrost-http/lib/config.go +++ b/transports/bifrost-http/lib/config.go @@ -232,6 +232,7 @@ var DefaultClientConfig = configstore.ClientConfig{ MaxRequestBodySizeMB: 100, MCPAgentDepth: 10, MCPToolExecutionTimeout: 30, + MCPCodeModeBindingLevel: string(schemas.CodeModeBindingLevelServer), EnableLiteLLMFallbacks: false, } @@ -509,6 +510,9 @@ func loadClientConfigFromFile(ctx context.Context, config *Config, configData *C if config.ClientConfig.MCPToolExecutionTimeout == 0 && configData.Client.MCPToolExecutionTimeout != 0 { config.ClientConfig.MCPToolExecutionTimeout = configData.Client.MCPToolExecutionTimeout } + if config.ClientConfig.MCPCodeModeBindingLevel == "" && configData.Client.MCPCodeModeBindingLevel != "" { + config.ClientConfig.MCPCodeModeBindingLevel = configData.Client.MCPCodeModeBindingLevel + } // Update store with merged config if config.ConfigStore != nil { logger.Debug("updating merged client config in store") diff --git a/transports/bifrost-http/server/server.go b/transports/bifrost-http/server/server.go index 987908127..ef31b68e3 100644 --- a/transports/bifrost-http/server/server.go +++ b/transports/bifrost-http/server/server.go @@ -66,7 +66,7 @@ type ServerCallbacks interface { ForceReloadPricing(ctx context.Context) error ReloadProxyConfig(ctx context.Context, config *tables.GlobalProxyConfig) error UpdateDropExcessRequests(ctx context.Context, value bool) - UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int) error + UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error ReloadTeam(ctx context.Context, id string) (*tables.TableTeam, error) RemoveTeam(ctx context.Context, id string) error ReloadCustomer(ctx context.Context, id string) (*tables.TableCustomer, error) @@ -708,11 +708,11 @@ func (s *BifrostHTTPServer) UpdateDropExcessRequests(ctx context.Context, value } // UpdateMCPToolManagerConfig updates the MCP tool manager config -func (s *BifrostHTTPServer) UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int) error { +func (s *BifrostHTTPServer) UpdateMCPToolManagerConfig(ctx context.Context, maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error { if s.Config == nil { return fmt.Errorf("config not found") } - return s.Client.UpdateToolManagerConfig(maxAgentDepth, toolExecutionTimeoutInSeconds) + return s.Client.UpdateToolManagerConfig(maxAgentDepth, toolExecutionTimeoutInSeconds, codeModeBindingLevel) } // UpdatePluginStatus updates the status of a plugin diff --git a/transports/changelog.md b/transports/changelog.md index d0b882b5d..85c47faef 100644 --- a/transports/changelog.md +++ b/transports/changelog.md @@ -1 +1,4 @@ -- fix: gemini thought signature handling in multi-turn conversations \ No newline at end of file +- feat: added code mode to mcp +- feat: added health monitoring to mcp +- feat: added responses format tool execution support to mcp +- refactor: governance plugin refactored for extensibility and optimization \ No newline at end of file diff --git a/ui/app/workspace/config/logging/page.tsx b/ui/app/workspace/config/logging/page.tsx index 4ccaddb31..a285754aa 100644 --- a/ui/app/workspace/config/logging/page.tsx +++ b/ui/app/workspace/config/logging/page.tsx @@ -1,12 +1,11 @@ -"use client" +"use client"; -import LoggingView from "../views/loggingView" +import LoggingView from "../views/loggingView"; export default function LoggingPage() { - return ( -
- -
- ) + return ( +
+ +
+ ); } - diff --git a/ui/app/workspace/config/mcp-gateway/page.tsx b/ui/app/workspace/config/mcp-gateway/page.tsx new file mode 100644 index 000000000..47f6865a9 --- /dev/null +++ b/ui/app/workspace/config/mcp-gateway/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import MCPGatewayView from "../views/mcpView"; + +export default function MCPGatewayPage() { + return ( +
+ +
+ ); +} diff --git a/ui/app/workspace/config/views/clientSettingsView.tsx b/ui/app/workspace/config/views/clientSettingsView.tsx index 55e10ac7f..25d8e8d7f 100644 --- a/ui/app/workspace/config/views/clientSettingsView.tsx +++ b/ui/app/workspace/config/views/clientSettingsView.tsx @@ -23,6 +23,7 @@ const defaultConfig: CoreConfig = { log_retention_days: 365, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function ClientSettingsView() { @@ -72,7 +73,7 @@ export default function ClientSettingsView() { }, [bifrostConfig, localConfig, updateCoreConfig]); return ( -
+

Client Settings

diff --git a/ui/app/workspace/config/views/governanceView.tsx b/ui/app/workspace/config/views/governanceView.tsx index 6fb9ee2bb..e95441986 100644 --- a/ui/app/workspace/config/views/governanceView.tsx +++ b/ui/app/workspace/config/views/governanceView.tsx @@ -23,6 +23,7 @@ const defaultConfig: CoreConfig = { enable_litellm_fallbacks: false, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function GovernanceView() { @@ -63,7 +64,7 @@ export default function GovernanceView() { }, [bifrostConfig, localConfig, updateCoreConfig]); return ( -
+

Governance

diff --git a/ui/app/workspace/config/views/loggingView.tsx b/ui/app/workspace/config/views/loggingView.tsx index 0fd7401a4..2f6f9ed2c 100644 --- a/ui/app/workspace/config/views/loggingView.tsx +++ b/ui/app/workspace/config/views/loggingView.tsx @@ -25,6 +25,7 @@ const defaultConfig: CoreConfig = { enable_litellm_fallbacks: false, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function LoggingView() { diff --git a/ui/app/workspace/config/views/mcpView.tsx b/ui/app/workspace/config/views/mcpView.tsx index 088a01130..9fa664c1a 100644 --- a/ui/app/workspace/config/views/mcpView.tsx +++ b/ui/app/workspace/config/views/mcpView.tsx @@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { getErrorMessage, useGetCoreConfigQuery, useUpdateCoreConfigMutation } from "@/lib/store"; import { CoreConfig } from "@/lib/types/config"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; @@ -23,6 +24,7 @@ const defaultConfig: CoreConfig = { log_retention_days: 365, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function MCPView() { @@ -35,9 +37,11 @@ export default function MCPView() { const [localValues, setLocalValues] = useState<{ mcp_agent_depth: string; mcp_tool_execution_timeout: string; + mcp_code_mode_binding_level: string; }>({ mcp_agent_depth: "10", mcp_tool_execution_timeout: "30", + mcp_code_mode_binding_level: "server", }); useEffect(() => { @@ -46,6 +50,7 @@ export default function MCPView() { setLocalValues({ mcp_agent_depth: config?.mcp_agent_depth?.toString() || "10", mcp_tool_execution_timeout: config?.mcp_tool_execution_timeout?.toString() || "30", + mcp_code_mode_binding_level: config?.mcp_code_mode_binding_level || "server", }); } }, [config, bifrostConfig]); @@ -53,7 +58,9 @@ export default function MCPView() { 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_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") ); }, [config, localConfig]); @@ -73,6 +80,13 @@ export default function MCPView() { } }, []); + const handleCodeModeBindingLevelChange = useCallback((value: string) => { + setLocalValues((prev) => ({ ...prev, mcp_code_mode_binding_level: value })); + if (value === "server" || value === "tool") { + setLocalConfig((prev) => ({ ...prev, mcp_code_mode_binding_level: value })); + } + }, []); + const handleSave = useCallback(async () => { try { const agentDepth = Number.parseInt(localValues.mcp_agent_depth); @@ -100,7 +114,7 @@ export default function MCPView() { }, [bifrostConfig, localConfig, localValues, updateCoreConfig]); return ( -
+

MCP Settings

@@ -110,7 +124,6 @@ export default function MCPView() { {isLoading ? "Saving..." : "Save Changes"}
-
{/* Max Agent Depth */}
@@ -147,6 +160,59 @@ export default function MCPView() { min="1" />
+ + {/* Code Mode Binding Level */} +
+
+ +

+ How tools are exposed in the VFS: server-level (all tools per server) or tool-level (individual tools). +

+
+ + + {/* Visual Example */} +
+

VFS Structure:

+ + {localValues.mcp_code_mode_binding_level === "server" ? ( +
+
+
servers/
+
├─ calculator.d.ts
+
├─ youtube.d.ts
+
└─ weather.d.ts
+
+

All tools per server in a single .d.ts file

+
+ ) : ( +
+
+
servers/
+
├─ calculator/
+
├─ add.d.ts
+
└─ subtract.d.ts
+
├─ youtube/
+
├─ GET_CHANNELS.d.ts
+
└─ SEARCH_VIDEOS.d.ts
+
└─ weather/
+
└─ get_forecast.d.ts
+
+

Individual .d.ts file for each tool

+
+ )} +
+
); diff --git a/ui/app/workspace/config/views/observabilityView.tsx b/ui/app/workspace/config/views/observabilityView.tsx index d52015021..126da2a5d 100644 --- a/ui/app/workspace/config/views/observabilityView.tsx +++ b/ui/app/workspace/config/views/observabilityView.tsx @@ -26,6 +26,7 @@ const defaultConfig: CoreConfig = { log_retention_days: 365, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function ObservabilityView() { diff --git a/ui/app/workspace/config/views/performanceTuningView.tsx b/ui/app/workspace/config/views/performanceTuningView.tsx index 40b99ad97..e6b605553 100644 --- a/ui/app/workspace/config/views/performanceTuningView.tsx +++ b/ui/app/workspace/config/views/performanceTuningView.tsx @@ -25,6 +25,7 @@ const defaultConfig: CoreConfig = { log_retention_days: 365, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function PerformanceTuningView() { @@ -105,7 +106,7 @@ export default function PerformanceTuningView() { }, [bifrostConfig, localConfig, localValues, updateCoreConfig]); return ( -
+

Performance Tuning

diff --git a/ui/app/workspace/config/views/securityView.tsx b/ui/app/workspace/config/views/securityView.tsx index e59a1e8f7..76b0b6163 100644 --- a/ui/app/workspace/config/views/securityView.tsx +++ b/ui/app/workspace/config/views/securityView.tsx @@ -33,6 +33,7 @@ const defaultConfig: CoreConfig = { log_retention_days: 365, mcp_agent_depth: 10, mcp_tool_execution_timeout: 30, + mcp_code_mode_binding_level: "server", }; export default function SecurityView() { diff --git a/ui/components/sidebar.tsx b/ui/components/sidebar.tsx index 6733b45b7..c4393a441 100644 --- a/ui/components/sidebar.tsx +++ b/ui/components/sidebar.tsx @@ -486,6 +486,13 @@ export default function AppSidebar() { description: "Client configuration settings", hasAccess: hasSettingsAccess, }, + { + title: "MCP Gateway", + url: "/workspace/config/mcp-gateway", + icon: MCPIcon, + description: "MCP gateway configuration", + hasAccess: hasMCPGatewayAccess, + }, { title: "Pricing Config", url: "/workspace/config/pricing-config", @@ -715,7 +722,7 @@ export default function AppSidebar() { isExpanded={expandedItems.has(item.title)} onToggle={() => toggleItem(item.title)} pathname={pathname} - router={router} + router={router} /> ); })} diff --git a/ui/lib/types/config.ts b/ui/lib/types/config.ts index 91703f4a3..c4d4544ba 100644 --- a/ui/lib/types/config.ts +++ b/ui/lib/types/config.ts @@ -325,6 +325,7 @@ export interface CoreConfig { enable_litellm_fallbacks: boolean; mcp_agent_depth: number; mcp_tool_execution_timeout: number; + mcp_code_mode_binding_level?: string; } // Semantic cache configuration types diff --git a/ui/lib/types/schemas.ts b/ui/lib/types/schemas.ts index 17500ea98..0cae6157b 100644 --- a/ui/lib/types/schemas.ts +++ b/ui/lib/types/schemas.ts @@ -91,11 +91,11 @@ export const s3BucketConfigSchema = z.object({ bucket_name: z.string().min(1, "Bucket name is required"), prefix: z.string().optional(), is_default: z.boolean().optional(), -}) +}); export const batchS3ConfigSchema = z.object({ buckets: z.array(s3BucketConfigSchema).optional(), -}) +}); // Bedrock key config schema export const bedrockKeyConfigSchema = z @@ -462,6 +462,7 @@ export const coreConfigSchema = z.object({ max_request_body_size_mb: z.number().min(1).default(100), 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"), }); // Bifrost config schema @@ -647,7 +648,7 @@ export const mcpClientUpdateSchema = z.object({ }); // Global proxy type schema -export const globalProxyTypeSchema = z.enum(['http', 'socks5', 'tcp']); +export const globalProxyTypeSchema = z.enum(["http", "socks5", "tcp"]); // Global proxy configuration schema export const globalProxyConfigSchema = z @@ -674,8 +675,8 @@ export const globalProxyConfigSchema = z return true; }, { - message: 'Proxy URL is required when proxy is enabled', - path: ['url'], + message: "Proxy URL is required when proxy is enabled", + path: ["url"], }, ) .refine( @@ -692,8 +693,8 @@ export const globalProxyConfigSchema = z return true; }, { - message: 'Must be a valid URL (e.g., http://proxy.example.com:8080)', - path: ['url'], + message: "Must be a valid URL (e.g., http://proxy.example.com:8080)", + path: ["url"], }, );