Skip to content

Commit 8a83112

Browse files
feat: added binding level toggle in mcp codemode
1 parent bd2355b commit 8a83112

File tree

27 files changed

+524
-97
lines changed

27 files changed

+524
-97
lines changed

core/bifrost.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1886,14 +1886,15 @@ func (bifrost *Bifrost) ReconnectMCPClient(id string) error {
18861886

18871887
// UpdateToolManagerConfig updates the tool manager config for the MCP manager.
18881888
// This allows for hot-reloading of the tool manager config at runtime.
1889-
func (bifrost *Bifrost) UpdateToolManagerConfig(maxAgentDepth int, toolExecutionTimeoutInSeconds int) error {
1889+
func (bifrost *Bifrost) UpdateToolManagerConfig(maxAgentDepth int, toolExecutionTimeoutInSeconds int, codeModeBindingLevel string) error {
18901890
if bifrost.mcpManager == nil {
18911891
return fmt.Errorf("MCP is not configured in this Bifrost instance")
18921892
}
18931893

18941894
bifrost.mcpManager.UpdateToolManagerConfig(&schemas.MCPToolManagerConfig{
18951895
MaxAgentDepth: maxAgentDepth,
18961896
ToolExecutionTimeout: time.Duration(toolExecutionTimeoutInSeconds) * time.Second,
1897+
CodeModeBindingLevel: schemas.CodeModeBindingLevel(codeModeBindingLevel),
18971898
})
18981899
return nil
18991900
}

core/mcp.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,14 +1136,8 @@ func (m *MCPManager) createInProcessConnection(config schemas.MCPClientConfig) (
11361136
return nil, MCPClientConnectionInfo{}, fmt.Errorf("InProcess connection requires a server instance")
11371137
}
11381138

1139-
// Type assert to ensure we have a proper MCP server
1140-
mcpServer, ok := config.InProcessServer.(*server.MCPServer)
1141-
if !ok {
1142-
return nil, MCPClientConnectionInfo{}, fmt.Errorf("InProcessServer must be a *server.MCPServer instance")
1143-
}
1144-
11451139
// Create in-process client directly connected to the provided server
1146-
inProcessClient, err := client.NewInProcessClient(mcpServer)
1140+
inProcessClient, err := client.NewInProcessClient(config.InProcessServer)
11471141
if err != nil {
11481142
return nil, MCPClientConnectionInfo{}, fmt.Errorf("failed to create in-process client: %w", err)
11491143
}

core/mcp/codemode_listfiles.go

Lines changed: 161 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,39 @@ import (
1010

1111
// createListToolFilesTool creates the listToolFiles tool definition for code mode.
1212
// This tool allows listing all available virtual .d.ts declaration files for connected MCP servers.
13+
// The description is dynamically generated based on the configured CodeModeBindingLevel.
1314
//
1415
// Returns:
1516
// - schemas.ChatTool: The tool definition for listing tool files
1617
func (m *ToolsManager) createListToolFilesTool() schemas.ChatTool {
18+
bindingLevel := m.GetCodeModeBindingLevel()
19+
var description string
20+
21+
if bindingLevel == schemas.CodeModeBindingLevelServer {
22+
description = "Returns a tree structure listing all virtual .d.ts declaration files available for connected MCP servers. " +
23+
"Each server has a corresponding file (e.g., servers/<serverName>.d.ts) that contains definitions for all tools in that server. " +
24+
"Use readToolFile to read a specific server file and see all available tools. " +
25+
"In code, access tools via: await serverName.toolName({ args }). " +
26+
"The server names used in code correspond to the human-readable names shown in this listing. " +
27+
"This tool is generic and works with any set of servers connected at runtime. " +
28+
"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. " +
29+
"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."
30+
} else {
31+
description = "Returns a tree structure listing all virtual .d.ts declaration files available for connected MCP servers, organized by individual tool. " +
32+
"Each tool has a corresponding file (e.g., servers/<serverName>/<toolName>.d.ts) that contains definitions for that specific tool. " +
33+
"Use readToolFile to read a specific tool file and see its parameters and usage. " +
34+
"In code, access tools via: await serverName.toolName({ args }). " +
35+
"The server names used in code correspond to the human-readable names shown in this listing. " +
36+
"This tool is generic and works with any set of servers connected at runtime. " +
37+
"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. " +
38+
"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."
39+
}
40+
1741
return schemas.ChatTool{
1842
Type: schemas.ChatToolTypeFunction,
1943
Function: &schemas.ChatToolFunction{
2044
Name: ToolTypeListToolFiles,
21-
Description: schemas.Ptr(
22-
"Returns a tree structure listing all virtual .d.ts declaration files available for connected MCP servers. " +
23-
"Each connected server has a corresponding virtual file that can be read using readToolFile. " +
24-
"The filenames follow the pattern <serverDisplayName>.d.ts where serverDisplayName is the human-readable " +
25-
"name reported by each connected server. Note that the code-level bindings (used in executeToolCode) use " +
26-
"configuration keys from SERVER_CONFIGS, which may differ from these display names. " +
27-
"This tool is generic and works with any set of servers connected at runtime. " +
28-
"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. " +
29-
"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.",
30-
),
45+
Description: schemas.Ptr(description),
3146
Parameters: &schemas.ToolFunctionParameters{
3247
Type: "object",
3348
Properties: &schemas.OrderedMap{},
@@ -39,6 +54,9 @@ func (m *ToolsManager) createListToolFilesTool() schemas.ChatTool {
3954

4055
// handleListToolFiles handles the listToolFiles tool call.
4156
// It builds a tree structure listing all virtual .d.ts files available for code mode clients.
57+
// The structure depends on the CodeModeBindingLevel:
58+
// - "server": servers/<name>.d.ts (one file per server)
59+
// - "tool": servers/<name>/<toolName>.d.ts (one file per tool)
4260
//
4361
// Parameters:
4462
// - ctx: Context for accessing client tools
@@ -56,10 +74,14 @@ func (m *ToolsManager) handleListToolFiles(ctx context.Context, toolCall schemas
5674
return createToolResponseMessage(toolCall, responseText), nil
5775
}
5876

59-
// Build tree structure
60-
treeLines := []string{"servers/"}
77+
// Get the code mode binding level
78+
bindingLevel := m.GetCodeModeBindingLevel()
79+
80+
// Build file list based on binding level
81+
var files []string
6182
codeModeServerCount := 0
62-
for clientName := range availableToolsPerClient {
83+
84+
for clientName, tools := range availableToolsPerClient {
6385
client := m.clientManager.GetClientByName(clientName)
6486
if client == nil {
6587
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
6991
continue
7092
}
7193
codeModeServerCount++
72-
treeLines = append(treeLines, fmt.Sprintf(" %s.d.ts", clientName))
94+
95+
if bindingLevel == schemas.CodeModeBindingLevelServer {
96+
// Server-level: one file per server
97+
files = append(files, fmt.Sprintf("servers/%s.d.ts", clientName))
98+
} else {
99+
// Tool-level: one file per tool
100+
for _, tool := range tools {
101+
if tool.Function != nil && tool.Function.Name != "" {
102+
toolFileName := fmt.Sprintf("servers/%s/%s.d.ts", clientName, tool.Function.Name)
103+
files = append(files, toolFileName)
104+
}
105+
}
106+
}
73107
}
74108

75109
if codeModeServerCount == 0 {
@@ -78,6 +112,118 @@ func (m *ToolsManager) handleListToolFiles(ctx context.Context, toolCall schemas
78112
return createToolResponseMessage(toolCall, responseText), nil
79113
}
80114

81-
responseText := strings.Join(treeLines, "\n")
115+
// Build tree structure from file list
116+
responseText := buildVFSTree(files)
82117
return createToolResponseMessage(toolCall, responseText), nil
83118
}
119+
120+
// VFS tree node structure for building hierarchical file structure
121+
type treeNode struct {
122+
isDirectory bool
123+
children map[string]*treeNode
124+
}
125+
126+
// buildVFSTree creates a hierarchical tree structure from a flat list of file paths.
127+
// It groups files by directory and formats them with proper indentation.
128+
//
129+
// Example input:
130+
// - ["servers/calculator.d.ts", "servers/youtube.d.ts"]
131+
// - ["servers/calculator/add.d.ts", "servers/youtube/GET_CHANNELS.d.ts"]
132+
//
133+
// Example output for server-level:
134+
// servers/
135+
// calculator.d.ts
136+
// youtube.d.ts
137+
//
138+
// Example output for tool-level:
139+
// servers/
140+
// calculator/
141+
// add.d.ts
142+
// youtube/
143+
// GET_CHANNELS.d.ts
144+
func buildVFSTree(files []string) string {
145+
if len(files) == 0 {
146+
return ""
147+
}
148+
149+
root := &treeNode{
150+
isDirectory: true,
151+
children: make(map[string]*treeNode),
152+
}
153+
154+
// Parse all files and build tree structure
155+
for _, file := range files {
156+
parts := strings.Split(file, "/")
157+
current := root
158+
159+
// Create all intermediate directories and final file
160+
for i, part := range parts {
161+
if _, exists := current.children[part]; !exists {
162+
current.children[part] = &treeNode{
163+
isDirectory: i < len(parts)-1, // Last part is file, not directory
164+
children: make(map[string]*treeNode),
165+
}
166+
}
167+
current = current.children[part]
168+
}
169+
}
170+
171+
// Render tree structure with proper indentation
172+
var lines []string
173+
renderTreeNode(root, "", &lines, true)
174+
175+
return strings.Join(lines, "\n")
176+
}
177+
178+
// renderTreeNode recursively renders a tree node and its children with proper indentation.
179+
func renderTreeNode(node *treeNode, indent string, lines *[]string, isRoot bool) {
180+
// Get sorted keys for consistent output
181+
var keys []string
182+
for key := range node.children {
183+
keys = append(keys, key)
184+
}
185+
186+
// Simple bubble sort for small lists (good enough for this use case)
187+
for i := 0; i < len(keys); i++ {
188+
for j := i + 1; j < len(keys); j++ {
189+
if keys[j] < keys[i] {
190+
keys[i], keys[j] = keys[j], keys[i]
191+
}
192+
}
193+
}
194+
195+
for _, key := range keys {
196+
child := node.children[key]
197+
198+
// Format the line
199+
var line string
200+
if isRoot {
201+
// Root level - no indentation
202+
if child.isDirectory {
203+
line = key + "/"
204+
} else {
205+
line = key
206+
}
207+
} else {
208+
// Non-root levels - add indentation
209+
if child.isDirectory {
210+
line = indent + key + "/"
211+
} else {
212+
line = indent + key
213+
}
214+
}
215+
216+
*lines = append(*lines, line)
217+
218+
// Recurse into children
219+
if child.isDirectory && len(child.children) > 0 {
220+
var nextIndent string
221+
if isRoot {
222+
nextIndent = " "
223+
} else {
224+
nextIndent = indent + " "
225+
}
226+
renderTreeNode(child, nextIndent, lines, false)
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)