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
3 changes: 2 additions & 1 deletion core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -1886,14 +1886,15 @@ 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")
}

bifrost.mcpManager.UpdateToolManagerConfig(&schemas.MCPToolManagerConfig{
MaxAgentDepth: maxAgentDepth,
ToolExecutionTimeout: time.Duration(toolExecutionTimeoutInSeconds) * time.Second,
CodeModeBindingLevel: schemas.CodeModeBindingLevel(codeModeBindingLevel),
})
return nil
}
Expand Down
4 changes: 3 additions & 1 deletion core/changelog.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
- fix: gemini thought signature handling in multi-turn conversations
- feat: added code mode to mcp
- feat: added health monitoring to mcp
- feat: added responses format tool execution support to mcp
176 changes: 161 additions & 15 deletions core/mcp/codemode_listfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/<serverName>.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/<serverName>/<toolName>.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 <serverDisplayName>.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{},
Expand All @@ -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/<name>.d.ts (one file per server)
// - "tool": servers/<name>/<toolName>.d.ts (one file per tool)
//
// Parameters:
// - ctx: Context for accessing client tools
Expand All @@ -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))
Expand All @@ -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 {
Expand All @@ -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)
}
}
}
Loading