Skip to content

feat: add codemode to mcp#903

Closed
Pratham-Mishra04 wants to merge 1 commit into
11-18-feat_mcp_core_refactor_and_agent_mode_addedfrom
11-18-feat_added_codemode_to_mcp
Closed

feat: add codemode to mcp#903
Pratham-Mishra04 wants to merge 1 commit into
11-18-feat_mcp_core_refactor_and_agent_mode_addedfrom
11-18-feat_added_codemode_to_mcp

Conversation

@Pratham-Mishra04
Copy link
Copy Markdown
Collaborator

Summary

This PR adds a new CodeMode feature to the MCP (Model Context Protocol) integration, enabling JavaScript code execution in a sandboxed environment with access to MCP tools. This allows for more complex orchestration of tool calls and data processing directly within Bifrost.

Changes

  • Added a new MCPModeCodeMode option to the MCP configuration
  • Implemented a JavaScript execution environment using the Goja VM
  • Created three new tools for CodeMode:
    • listToolFiles: Lists available MCP server tools
    • readToolFile: Generates TypeScript definitions for MCP tools
    • executeToolCode: Executes JavaScript code with access to MCP tools
  • Added comprehensive test suite for CodeMode functionality
  • Enhanced MCP tool handler interface to support different execution modes
  • Implemented auto-return functionality for expressions in JavaScript code
  • Added import/export statement stripping for JavaScript code
  • Implemented error handling with helpful hints for common issues

Type of change

  • Feature

Affected areas

  • Core (Go)

How to test

# Run the core MCP tests
cd tests/core-mcp
go test -v ./...

# Run specific test cases
go test -v -run TestSimpleExpression
go test -v -run TestToolCallWithPromise

To use CodeMode in your application:

  1. Configure MCP with the CodeMode option:
mcpConfig := &schemas.MCPConfig{
    Mode: schemas.MCPModeCodeMode,
    // other config options...
}
  1. Use the new CodeMode tools in your LLM prompts:
You can use these tools:
- listToolFiles: Lists available MCP servers
- readToolFile: Gets TypeScript definitions for a server
- executeToolCode: Runs JavaScript code with access to MCP tools

Breaking changes

  • No

Security considerations

The JavaScript execution environment is sandboxed using Goja VM with several restrictions:

  • No access to browser APIs (fetch, setTimeout, etc.)
  • No access to Node.js APIs (require, fs, etc.)
  • No async/await syntax (must use Promises)
  • Import/export statements are automatically stripped
  • Execution timeout prevents long-running code
  • Only MCP tools explicitly allowed for a client can be executed

Copy link
Copy Markdown
Collaborator Author

Pratham-Mishra04 commented Nov 21, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 21, 2025

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added code execution capability with sandboxed TypeScript/JavaScript evaluation and MCP tool integration
    • Introduced Code Mode for MCP clients, enabling dynamic tool discovery and file reading
    • Added agent mode auto-execution configuration with per-tool allowlists
    • Redesigned MCP management interface as "MCP Gateway" with enhanced tool visibility and controls
  • Bug Fixes

    • Improved error handling and timeout management for tool execution
  • UI/UX

    • Added copy-to-clipboard functionality for request bodies in logs
    • Enhanced MCP client configuration UI with code mode toggle and tool execution settings

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This pull request introduces a comprehensive code-mode execution system for MCP, enabling TypeScript code evaluation within a sandboxed VM with tool bindings. It refactors tool management from a default handler to a pluggable ToolsHandler with ClientManager interface, adds support for code-mode client flagging, and includes extensive testing and UI updates for the new MCP Gateway.

Changes

Cohort / File(s) Summary
Dependencies
core/go.mod
Added direct dependencies: go-typescript v0.7.0, goja v0.0.0-20251103141225-af2ceb9156d7 (JavaScript VM engine); added indirect dependencies for regexp2, sourcemap, pprof support.
Code Execution Engine
core/mcp/codemode_executecode.go
New module for sandboxed TypeScript-to-JS transpilation and execution in goja VM; defines ExecutionResult, ExecutionError, ExecutionEnvironment structures; handles imports/exports stripping, tool binding, console capture, and per-tool timeouts with comprehensive error hints.
Code Mode Tooling
core/mcp/codemode_listfiles.go, core/mcp/codemode_readfile.go
New tools for listing and reading virtual tool definition files; listToolFiles outputs server tree structure; readToolFile generates TypeScript type definitions with line-range slicing support, JSON schema conversion, and robust error handling.
Tool Manager Refactor
core/mcp/toolmanager.go
New configurable ToolsHandler with ClientManager interface, ParseAndAddToolsToRequest for multi-client tool aggregation, ExecuteTool with timeout control and code-mode routing, ExecuteAgent delegation, and buildAllowedAutoExecutionTools for per-client auto-execution validation.
Tool Manager Removal
core/mcp/default_toolmanager.go
Removed entire file; eliminates DefaultToolsHandler type and all associated public/internal methods for tool management (NewDefaultToolsHandler, SetupCompleted, tool fetchers, ParseAndAddToolsToRequest, ExecuteTool, ExecuteAgent).
MCP Core Integration
core/mcp/mcp.go
Updated MCPManager.toolsHandler from schemas.MCPToolHandler to *ToolsHandler; replaced DefaultToolsHandler initialization with NewToolsHandler; removed per-client setup wiring (SetToolsFetcherFunc, SetClientForToolFetcherFunc, SetFetchNewRequestIDFunc); simplified AddToolsToRequest and ExecuteTool delegation; changed BifrostMCPClientKey value from "bifrost-internal" to "bifrostInternal".
Tool & Client Utilities
core/mcp/utils.go
Refactored getAvailableTools to return map[string][]ChatTool (per-client); renamed findMCPClientForTool to GetClientForTool (public); added GetClientByName; updated shouldIncludeClient and shouldSkipToolForRequest to use clientName; introduced parseToolName, extractToolCallsFromCode, isToolCallAllowedForCodeMode for code-mode validation.
Agent Mode
core/mcp/agent.go
Replaced clientForToolFetcherFunc with clientManager parameter; added code-mode auto-execution flow with tool call extraction and validation; introduced buildAllowedAutoExecutionTools; new finish_reason handling for non-auto-executable tools to prevent re-processing.
Client Manager Updates
core/mcp/clientmanager.go
Propagate IsCodeModeClient from updatedConfig in EditClient; initialize local in-process MCP client ExecutionConfig with ID (BifrostMCPClientKey), Name, and ToolsToExecute=["*"] for all-tool access.
Schema Changes
core/schemas/mcp.go, core/schemas/bifrost.go, core/schemas/utils.go
Removed MCPToolHandler interface entirely; added IsCodeModeClient bool field to MCPClientConfig; removed omitempty from BifrostError.ExtraFields JSON tag; added DeepCopyChatTool function for nested tool copying.
Core Bifrost
core/bifrost.go
ExecuteMCPTool: suppress original error in BifrostError when MCP execution fails; AddMCPClient: initialize MCP manager and delegate if not yet configured.
Chatbot Logic
core/chatbot_test.go
Simplified tool usage check to rely solely on len(ToolCalls); removed nil guards using len() semantics.
Database Migrations
framework/configstore/migrations.go
Added migrationAddIsCodeModeClientColumn to create is_code_mode_client column on config_mcp_clients, initialized to false, with rollback support.
Database Layer
framework/configstore/rdb.go, framework/configstore/tables/mcp.go, framework/configstore/utils.go
GetMCPConfig and CreateMCPClientConfig now propagate IsCodeModeClient; UpdateMCPClientConfig restores redacted headers and ensures IsCodeModeClient is written; updated substituteMCPClientEnvVars to accept optional existingHeaders; added IsCodeModeClient field to TableMCPClient.
API Transport
transports/bifrost-http/handlers/mcp.go, transports/bifrost-http/lib/config.go
Updated error messages from "add MCP client" to "connect MCP client"; error message wording changed to "failed to connect MCP client"; propagate IsCodeModeClient in addMCPClient and EditMCPClient; include IsCodeModeClient in RedactMCPClientConfig.
UI Components - MCP Gateway
ui/app/workspace/mcp-gateway/page.tsx, ui/app/workspace/mcp-gateway/views/mcpClientForm.tsx, ui/app/workspace/mcp-gateway/views/mcpClientSheet.tsx, ui/app/workspace/mcp-gateway/views/mcpClientsTable.tsx
New MCP Gateway UI replacing mcp-clients; added Code Mode Client switch to forms; updated labels from "Client" to "Server"; enhanced table with Code Mode, Enabled Tools, Auto-execute Tools columns; added per-row reconnect and delete actions; improved form initialization and submission handling.
UI Navigation & Types
ui/components/sidebar.tsx, ui/lib/types/mcp.ts, ui/lib/types/schemas.ts
Updated sidebar item from "MCP Tools" to "MCP Gateway" with new URL; added is_code_mode_client optional field to MCPClientConfig, CreateMCPClientRequest, UpdateMCPClientRequest interfaces and mcpClientUpdateSchema.
UI Log Views
ui/app/workspace/logs/views/logChatMessageView.tsx, ui/app/workspace/logs/views/logDetailsSheet.tsx
Added break-words class to chat message container for text wrapping; added copy-to-clipboard button for request body JSON with comprehensive type handling and toast notifications.
UI Headers
ui/components/ui/headersTable.tsx
Changed TableRow key assignment to use row index for all rows instead of conditional rowKey.
Test Infrastructure
tests/core-mcp/README.md, tests/core-mcp/fixtures.go, tests/core-mcp/go.mod, tests/core-mcp/setup.go, tests/core-mcp/utils.go
New comprehensive MCP test suite with README documentation, CodeFixtures and ExpectedResults for test payloads, setup utilities for Bifrost initialization in default and code-mode configurations, test helpers for tool registration and MCP client management.
Integration Tests
tests/core-mcp/mcp_connection_test.go, tests/core-mcp/client_config_test.go, tests/core-mcp/auto_execute_config_test.go
Extensive tests covering MCP manager initialization, local tool registration, tool discovery via listToolFiles/readToolFile, tool execution with timeouts/errors, multiple server scenarios, client configuration updates, IsCodeModeClient persistence, ToolsToExecute and ToolsToAutoExecute behavior validation.
Code Mode Tests
tests/core-mcp/codemode_auto_execute_test.go, tests/core-mcp/edge_cases_test.go, tests/core-mcp/tool_execution_test.go, tests/core-mcp/agent_mode_test.go, tests/core-mcp/integration_test.go
Comprehensive code-mode test suites validating TypeScript execution, auto-execute configurations, edge cases (syntax errors, timeouts, invalid args), multi-client scenarios, agent mode with max-depth, and complex integration workflows.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant API
    participant MCPManager
    participant ToolsHandler
    participant CodeExecutor
    participant goja as goja VM
    participant ToolServer

    User->>UI: Request code execution with tools
    UI->>API: POST /execute-mcp-tool (code payload)
    API->>MCPManager: ExecuteMCPTool(toolCall)
    MCPManager->>ToolsHandler: ExecuteTool(executeToolCode)
    
    alt Code Mode Tool
        ToolsHandler->>CodeExecutor: Execute code with tool bindings
        CodeExecutor->>CodeExecutor: Strip imports/exports
        CodeExecutor->>CodeExecutor: Transpile TypeScript → ES5 JS
        CodeExecutor->>goja: Initialize VM with tool proxies
        CodeExecutor->>CodeExecutor: Bind serverName.toolName() → async calls
        
        loop For each tool call in code
            goja->>ToolServer: Invoke tool via proxy
            ToolServer-->>goja: Return result
            goja->>CodeExecutor: Capture console logs
        end
        
        CodeExecutor-->>ToolsHandler: ExecutionResult (result, logs, environment)
    else Non-Code-Mode Tool
        ToolsHandler->>ToolServer: Direct tool execution
        ToolServer-->>ToolsHandler: Tool result
    end
    
    ToolsHandler-->>MCPManager: ChatMessage (tool response)
    MCPManager-->>API: ChatMessage
    API-->>UI: Tool result + logs
    UI-->>User: Display execution result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Code Execution Engine (core/mcp/codemode_executecode.go): Dense logic for TypeScript transpilation, goja VM setup, tool binding proxies, and per-tool timeout management with promise-like async handling.
  • Tool Manager Refactor (core/mcp/toolmanager.go, core/mcp/default_toolmanager.go removal, core/mcp/mcp.go updates): Significant architectural shift from default handler to pluggable interface; verify all wiring paths properly initialize and error handling is preserved.
  • Agent Mode Updates (core/mcp/agent.go): Code-mode auto-execution validation with tool call extraction and per-client allowance checking introduces new control flow; verify finish_reason semantics and re-processing prevention.
  • Utility Refactoring (core/mcp/utils.go): Map-based tool organization and clientName-based filtering replace old clientID-based approach; ensure all callers adapt correctly.
  • Schema Removal (core/schemas/mcp.go): MCPToolHandler interface removal impacts all implementations; verify no orphaned references and new ToolsHandler is properly wired everywhere.
  • Database & ORM (framework/configstore/): IsCodeModeClient persistence across migrations, table definitions, and CRUD operations; verify field selection in updates and defaults in inserts.
  • Comprehensive Test Suite: High volume of new tests with multiple helpers and fixtures; verify test coverage is exhaustive for code-mode paths and edge cases.

Poem

🐰 Hop, hop—TypeScript now runs in goja's VM!
Code mode blooms with tool bindings fine,
Gateway refactored, tests align,
From schema to sandbox, the pieces meld—
Bifrost's code execution gently held.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add codemode to mcp' clearly and concisely summarizes the main feature addition of CodeMode support to the MCP integration, matching the primary objective of this PR.
Description check ✅ Passed The PR description comprehensively covers all required template sections: Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, and Security considerations, with detailed explanations of the CodeMode feature implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 11-18-feat_added_codemode_to_mcp

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (19)
core/mcp/codemode_utils.go (1)

34-46: Consider optimizing the consecutive hyphen check.

The current implementation calls result.String()[result.Len()-1] on each iteration (line 41), which creates a new string allocation every time. For better performance, track the last written character in a variable:

 	// Process remaining characters
+	var lastRune rune
 	for i := 1; i < len(runes); i++ {
 		r := runes[i]
 		if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '$' {
-			result.WriteRune(unicode.ToLower(r))
+			lastRune = unicode.ToLower(r)
+			result.WriteRune(lastRune)
 		} else if unicode.IsSpace(r) || r == '-' {
 			// Replace spaces and existing hyphens with single hyphen
 			// Avoid consecutive hyphens
-			if result.Len() > 0 && result.String()[result.Len()-1] != '-' {
+			if result.Len() > 0 && lastRune != '-' {
+				lastRune = '-'
 				result.WriteRune('-')
 			}
 		}
 		// Skip other invalid characters
 	}
core/mcp/utils.go (1)

28-78: Confirm consistency of client key usage between filters and returned map

getAvailableTools now returns map[string][]schemas.ChatTool keyed by client.ExecutionConfig.Name, while includeClients / includeTools filtering still uses the id from m.clientMap when calling shouldIncludeClient and shouldSkipToolForRequest. This is fine as long as:

  • m.clientMap is consistently keyed by the same logical identifier that callers use in MCPContextKeyIncludeClients / MCPContextKeyIncludeTools, and
  • ExecutionConfig.Name is guaranteed unique and aligned with how server keys are surfaced in CodeMode (e.g., __MCP_ENV__.serverKeys / BifrostClient).

If ID and Name can diverge, it would be good to:

  • Document which identifier is used where (config filters vs. JS-visible server keys), or
  • Rename id in the loop to make the distinction obvious, and ensure tests cover mixed ID/Name usage across both modes.
core/schemas/mcp.go (1)

27-47: Clarify default semantics for MCPConfig.Mode

MCPMode and the Mode MCPMode "json:\"mode\"" field look good for switching between default and code modes. Please ensure the initialization logic treats the zero value ("", e.g., configs that omit mode) as MCPModeDefault so existing configurations remain valid without specifying a mode.

It may be worth:

  • Documenting that any value other than MCPModeCodeMode is treated as default, or
  • Normalizing Mode once (e.g., in MCP manager construction) to either MCPModeDefault or MCPModeCodeMode.
tests/core-mcp/utils.go (2)

14-29: Handle JSON marshal errors in createToolCall to avoid masking bad fixtures

createToolCall ignores the json.Marshal error, which could silently turn malformed arguments into an empty string. In tests this is cheap to guard:

func createToolCall(toolName string, arguments map[string]interface{}) schemas.ChatAssistantMessageToolCall {
-	argsJSON, _ := json.Marshal(arguments)
+	argsJSON, err := json.Marshal(arguments)
+	require.NoError(t, err, "failed to marshal tool arguments")

You’d need to thread t *testing.T into this helper, but it will surface broken fixtures immediately instead of giving misleading tool-call behavior.


31-58: Make assertExecutionResult error checks less brittle

The success/error detection currently hinges on case-sensitive substring checks for "error" and a free-form expectedErrorKind. This may be fragile if messages contain words like ReferenceError / TypeError, or if success messages incidentally contain "error".

Consider:

  • Normalizing to lower case before checking for "error", and/or
  • Matching against a more structured part of the response (if ExecutionError.Kind is serialized in a predictable way), rather than generic free-text.

This will make the tests more resilient to small wording changes in error messages.

tests/core-mcp/codemode_test.go (3)

18-220: Factor out common CodeMode test setup to reduce duplication

Almost every test repeats:

ctx, cancel := context.WithTimeout(context.Background(), TestTimeout)
defer cancel()

b, err := setupTestBifrostWithCodeMode(ctx)
require.NoError(t, err)
err = registerTestTools(b)
require.NoError(t, err)

You could introduce a small helper like:

func setupCodeModeTest(t *testing.T) (context.Context, context.CancelFunc, *bifrost.Bifrost) {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), TestTimeout)
	b, err := setupTestBifrostWithCodeMode(ctx)
	require.NoError(t, err)
	require.NoError(t, registerTestTools(b))
	return ctx, cancel, b
}

and call it from tests to cut repetition and make future changes to setup cheaper.


670-764: Strengthen environment tests by asserting actual result values

TestNoBrowserAPIs, TestNoNodeAPIs, and TestServerKeysAvailable currently only assert successful execution, even though comments say “Should return true (…)”. To fully lock in sandbox semantics:

  • Assert that the response actually represents true (e.g., via assertResultContains or a small helper that inspects the returned value), similar to how other tests check for specific substrings or values.

This will better catch regressions where the code runs but the environment exposure changes.


708-726: Align TestNoAsyncAwait expectations with documented semantics

The comment says “async/await syntax should cause a syntax error in goja”, while assertExecutionResult only checks for a generic "runtime" error kind. If the implementation distinguishes syntax vs runtime errors, you may want to:

  • Either adjust the expected kind (and hint text) to explicitly assert a syntax error, or
  • Relax the comment to say “should fail to execute” if you don’t care which phase reports the error.

Keeping comment and assertions in sync will avoid confusion for future maintainers.

core/mcp/default_toolmanager.go (2)

68-155: Per-client tool flattening and de‑duplication are correct but assume globally-unique tool names

ParseAndAddToolsToRequest correctly flattens map[string][]ChatTool into a slice, preserves existing caller-supplied tools, and de‑duplicates by Function.Name for both Chat and Responses requests while avoiding nil/empty names. The only behavioral assumption is that tool names are globally unique across MCP clients; if two clients expose the same function name, only the first will be surfaced. If that scenario is not supported by design, this is fine; otherwise, you may eventually need to key deduplication by (clientName, toolName) instead of toolName alone.


172-196: New permission check in ExecuteTool changes MCPToolHandler contract semantics

The added loop over m.toolsFetcherFunc(ctx) to gate execution ensures only tools present in the available-tools map can be called, which is a good hardening step and matches the PR’s security goals. However, schemas.MCPToolHandler.ExecuteTool is documented as not doing permission checks and leaving that to the caller; this implementation now effectively enforces availability/permission inside the default handler. Consider updating the interface comment (and any related docs) to reflect that handlers may perform their own checks, or explicitly note that the default handler enforces availability while other handlers may not.

tests/core-mcp/setup.go (2)

16-38: Consider failing fast or skipping tests when OPENAI_API_KEY is missing

TestAccount.GetKeysForProvider always returns a single key using os.Getenv("OPENAI_API_KEY"), even if the env var is empty. That can lead to confusing auth failures at runtime rather than a clear “test not configured” signal. You might want to either:

  • return an error when the env var is empty so callers can skip or fail tests explicitly, or
  • return an empty key slice and let higher-level setup decide how to handle “no keys”.

50-106: MCP test setup correctly normalizes mode and request‑ID function, but comments are slightly outdated

setupTestBifrostWithMode and setupTestBifrostWithMCPConfig correctly:

  • ensure MCPConfig.Mode is set (defaulting to MCPModeDefault), and
  • ensure FetchNewRequestIDFunc is non‑nil, which is passed into the configured tools handler.

Given DefaultToolsHandler.SetupCompleted no longer requires fetchNewRequestIDFunc, the comments stating “This is required for the tools handler to be fully setup” are now misleading. Consider rephrasing to “recommended for agent mode” or similar so future changes don’t over-assume this invariant.

core/mcp/codemode_readfile.go (2)

162-291: Type definition generation is functionally correct; sorting can be simplified

generateTypeDefinitions builds a clear header, per‑tool input and response interfaces, and export async function declarations based on ChatTool schemas. Property handling respects Required vs optional fields and uses jsonSchemaToTypeScript for type mapping, which is appropriate.

The manual nested-loop sort of propNames works but could be simplified and made more idiomatic with sort.Strings(propNames) from the standard library. That would reduce code and potential for future mistakes without changing behavior.


293-328: jsonSchemaToTypeScript covers common cases; note that enums are always rendered as string literals

The JSON‑schema → TS mapping handles primitives, arrays (with recursive item typing), objects, null, and enums, and falls back to any when there’s insufficient information. One behavioral nuance: enums are always rendered as quoted string literals via fmt.Sprintf("%q", e), even if the enum values are numbers or booleans. If numeric enums are ever used, you may want to special‑case those to avoid coercing them into string literal unions in the generated TS.

tests/core-mcp/mcp_connection_test.go (2)

176-211: Unreachable setup code after t.Skip in TestExternalMCPConnection

Because t.Skip is the first statement, the context creation and setupTestBifrost call are never executed. For now it’s harmless but dead; when you eventually enable this test, you’ll want to:

  • Move the skip behind the env/config checks (or remove it).
  • Capture the returned b from setupTestBifrost so the commented connectExternalMCP(b, ...) and b.GetMCPClients() pattern compiles once uncommented.

Right now it’s just placeholder/debug scaffolding and can be cleaned up or documented as such.


213-236: Clarify what TestToolExecutionTimeout is asserting

This test currently only asserts that the slow_tool call takes at least 100ms, which validates the tool’s delay behavior but not an upper bound or that a configured timeout is enforced. If the intent is to verify Bifrost/MCP timeout semantics (vs just “slow tool works”), consider:

  • Configuring an explicit, short execution timeout and asserting the call fails or returns within a bounded window, or
  • Renaming/commenting the test to reflect that it’s checking minimum delay rather than timeout enforcement.

Not a blocker, but tightening the contract will make future regressions easier to spot.

core/mcp/codemode_toolmanager.go (2)

11-41: Guard against missing dependency injection on CodeModeToolsHandler

CodeModeToolsHandler relies on injected functions (toolsFetcherFunc, clientForToolFetcher, fetchNewRequestIDFunc) but public methods like ExecuteAgent and the code‑execution path (in other files) call them directly without nil checks. If wiring code ever forgets to call the setters, you’ll get a panic rather than a structured error.

Given you already have SetupCompleted, consider one of:

  • Enforce construction via a factory that takes the required funcs, avoiding the “partially configured” state, or
  • Add defensive checks in public entrypoints (ExecuteAgent, code‑execution helpers) that return a BifrostError / error when setup isn’t complete, instead of assuming the setters have been called.

This will make the handler safer to use and future refactors less fragile.

Also applies to: 43-64, 189-199


66-139: Clean up ParseAndAddToolsToRequest docs and unused ctx parameter

The implementation looks correct in terms of:

  • Preserving existing tools and caller‑supplied Params.
  • Avoiding duplicate registration for both Chat and Responses flows.

Two small nits worth addressing:

  • The doc comment still mentions an availableToolsPerClient parameter that the function no longer takes.
  • The ctx argument is currently unused; if you don’t plan to use it (e.g., for context‑sensitive tool injection), rename it to _ context.Context or remove it to avoid vet/linters complaining.

Both are minor, but cleaning them up will make the API surface clearer.

core/mcp/codemode_executecode.go (1)

649-752: addAutoReturnIfNeeded relies on global context and unguarded toolsFetcherFunc

The auto‑return logic is generally helpful, but there are a couple of robustness issues:

  • It calls m.toolsFetcherFunc(context.Background()) with no nil check. If the handler hasn’t been fully wired (or in tests), this will panic instead of simply skipping MCP‑aware auto‑return.
  • Using context.Background() ignores the caller’s cancellation/deadlines; if toolsFetcherFunc ever becomes context‑sensitive (e.g., remote discovery), this could hang independently of the main execution timeout.

Consider:

  • Guarding against a nil toolsFetcherFunc and falling back to non‑MCP expression detection when it’s unavailable.
  • Threading the request context from executeCode into addAutoReturnIfNeeded (e.g., addAutoReturnIfNeeded(ctx, code)), so any discovery is properly bound to the execution timeout or caller’s cancellation.

These changes would make auto‑return safer without changing its visible behavior.

Also applies to: 755-785, 787-882

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38c9253 and 0750d39.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • tests/core-mcp/go.sum is excluded by !**/*.sum
📒 Files selected for processing (23)
  • core/go.mod (2 hunks)
  • core/mcp/clientmanager.go (1 hunks)
  • core/mcp/codemode_executecode.go (1 hunks)
  • core/mcp/codemode_listfiles.go (1 hunks)
  • core/mcp/codemode_readfile.go (1 hunks)
  • core/mcp/codemode_toolmanager.go (1 hunks)
  • core/mcp/codemode_utils.go (1 hunks)
  • core/mcp/default_toolmanager.go (4 hunks)
  • core/mcp/mcp.go (1 hunks)
  • core/mcp/utils.go (4 hunks)
  • core/schemas/mcp.go (2 hunks)
  • core/schemas/utils.go (1 hunks)
  • tests/core-mcp/README.md (1 hunks)
  • tests/core-mcp/codemode_test.go (1 hunks)
  • tests/core-mcp/fixtures.go (1 hunks)
  • tests/core-mcp/go.mod (1 hunks)
  • tests/core-mcp/mcp_connection_test.go (1 hunks)
  • tests/core-mcp/setup.go (1 hunks)
  • tests/core-mcp/utils.go (1 hunks)
  • transports/bifrost-http/handlers/mcp.go (1 hunks)
  • transports/bifrost-http/lib/config.go (12 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/components/ui/headersTable.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (16)
core/schemas/utils.go (2)
core/schemas/chatcompletions.go (6)
  • ChatTool (201-205)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatToolCustom (225-227)
  • ChatToolCustomFormat (229-232)
  • ChatToolCustomGrammarFormat (235-238)
transports/bifrost-http/lib/config.go (1)
  • DeepCopy (2964-2972)
core/mcp/utils.go (2)
core/mcp/mcp.go (1)
  • MCPManager (41-56)
core/schemas/chatcompletions.go (1)
  • ChatTool (201-205)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/handlers/utils.go (2)
  • SendError (35-44)
  • SendJSON (16-22)
core/schemas/mcp.go (1)
core/schemas/chatcompletions.go (1)
  • ChatTool (201-205)
core/mcp/clientmanager.go (1)
core/mcp/mcp.go (2)
  • BifrostMCPClientKey (22-22)
  • BifrostMCPClientName (21-21)
core/mcp/default_toolmanager.go (3)
core/schemas/chatcompletions.go (1)
  • ChatTool (201-205)
core/schemas/bifrost.go (1)
  • BifrostRequest (144-154)
core/mcp/mcp.go (1)
  • MCPLogPrefix (23-23)
core/mcp/codemode_readfile.go (3)
core/mcp/codemode_toolmanager.go (1)
  • CodeModeToolsHandler (11-17)
core/schemas/chatcompletions.go (5)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatAssistantMessageToolCall (483-489)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/mcp/codemode_listfiles.go (3)
core/mcp/codemode_toolmanager.go (1)
  • CodeModeToolsHandler (11-17)
core/schemas/chatcompletions.go (5)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatAssistantMessageToolCall (483-489)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/mcp/mcp.go (3)
core/schemas/mcp.go (3)
  • MCPToolHandler (11-19)
  • MCPModeDefault (30-30)
  • MCPModeCodeMode (31-31)
core/mcp/default_toolmanager.go (2)
  • NewDefaultToolsHandler (26-43)
  • DefaultHandlerConfig (21-24)
core/mcp/codemode_toolmanager.go (2)
  • NewCodeModeToolsHandler (24-41)
  • CodeModeHandlerConfig (19-22)
tests/core-mcp/mcp_connection_test.go (2)
tests/core-mcp/setup.go (1)
  • TestTimeout (14-14)
core/schemas/mcp.go (2)
  • MCPClient (119-123)
  • MCPConnectionStateConnected (93-93)
tests/core-mcp/utils.go (1)
core/schemas/chatcompletions.go (2)
  • ChatAssistantMessageToolCall (483-489)
  • ChatAssistantMessageToolCallFunction (492-495)
tests/core-mcp/codemode_test.go (2)
tests/core-mcp/setup.go (1)
  • TestTimeout (14-14)
tests/core-mcp/fixtures.go (1)
  • CodeFixtures (4-103)
tests/core-mcp/setup.go (7)
core/schemas/bifrost.go (1)
  • OpenAI (35-35)
core/schemas/account.go (2)
  • Key (8-17)
  • Account (55-71)
core/bifrost.go (1)
  • Bifrost (46-65)
core/schemas/mcp.go (7)
  • MCPModeDefault (30-30)
  • MCPModeCodeMode (31-31)
  • MCPMode (27-27)
  • MCPConfig (36-47)
  • MCPClientConfig (50-71)
  • MCPConnectionTypeHTTP (77-77)
  • MCPConnectionTypeSSE (79-79)
core/logger.go (1)
  • NewDefaultLogger (40-49)
core/schemas/chatcompletions.go (4)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/mcp/codemode_executecode.go (4)
core/mcp/codemode_toolmanager.go (1)
  • CodeModeToolsHandler (11-17)
core/schemas/chatcompletions.go (5)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatAssistantMessageToolCall (483-489)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/mcp.go (1)
  • MCPClientState (100-107)
transports/bifrost-http/lib/config.go (2)
core/schemas/mcp.go (1)
  • MCPConfig (36-47)
framework/configstore/clientconfig.go (1)
  • GovernanceConfig (66-73)
core/mcp/codemode_toolmanager.go (5)
core/schemas/mcp.go (2)
  • MCPClientState (100-107)
  • BifrostLLMCaller (23-25)
core/schemas/chatcompletions.go (5)
  • ChatTool (201-205)
  • ChatParameters (154-183)
  • ChatAssistantMessageToolCall (483-489)
  • BifrostChatRequest (11-18)
  • BifrostChatResponse (25-40)
core/schemas/bifrost.go (1)
  • ChatCompletionRequest (89-89)
core/schemas/responses.go (1)
  • ResponsesParameters (84-111)
core/mcp/agent.go (1)
  • ExecuteAgent (13-183)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (18)
ui/app/workspace/logs/views/logChatMessageView.tsx (1)

119-119: LGTM! Good consistency improvement.

Adding break-words to the content container ensures long text wraps properly, consistent with the thought section (line 89), refusal section (line 112), and nested content text (line 134).

transports/bifrost-http/lib/config.go (1)

1964-1964: LGTM: Error message update improves clarity.

The terminology change from "add" to "connect" better reflects the actual operation being performed and aligns with similar updates in the HTTP handlers layer (transports/bifrost-http/handlers/mcp.go lines 197, 202).

core/schemas/utils.go (1)

703-786: LGTM: Comprehensive deep copy implementation.

The DeepCopyChatTool function follows the established pattern used by DeepCopyChatMessage and DeepCopyResponsesMessage in this file. The implementation properly handles:

  • Nil pointer safety for all optional fields
  • Deep copying of nested structures (Function, Custom, Parameters, Format, Grammar)
  • Use of the existing DeepCopy helper for the Properties map (line 740)
core/mcp/clientmanager.go (1)

560-562: LGTM: Enhanced local client configuration.

The addition of ID, Name, and ToolsToExecute fields properly configures the internal MCP client with full tool access (via wildcard "*"), which is appropriate for the Bifrost internal client that should have unrestricted access to registered tools.

transports/bifrost-http/handlers/mcp.go (1)

197-203: LGTM: Consistent terminology update.

The message changes from "add" to "connect" align with the similar update in transports/bifrost-http/lib/config.go (line 1964) and better reflect the actual MCP client connection operation.

core/mcp/codemode_listfiles.go (1)

12-52: LGTM: Clean tool implementation.

The listToolFiles tool is well-implemented with:

  • Clear, comprehensive description explaining the virtual file structure and naming conventions
  • Proper empty-state handling with informative messaging
  • Simple, readable tree structure generation
tests/core-mcp/go.mod (1)

1-62: LGTM: Standard test module configuration.

The test module setup follows Go best practices with:

  • Proper replace directive for local core module development
  • Appropriate test dependencies (testify)
  • Correct transitive dependency declarations
core/go.mod (1)

11-11: No action required—Goja dependency is correctly specified.

The dop251/goja project has no GitHub releases and is typically consumed via commits/pseudo-versions. The version v0.0.0-20251103141225-af2ceb9156d7 follows Go's standard pseudo-version format for this project and is the appropriate way to depend on it.

tests/core-mcp/README.md (1)

1-162: README gives a clear map of the CodeMode test surface

The structure (modes, setup files, test categories A–H, and how to run tests) is clear and aligns with the codemode tests and fixtures. No issues from a test-maintenance perspective.

core/schemas/mcp.go (1)

11-19: Verify whether MCPToolHandler is a public extension point

Review raised a concern about breaking external implementations, but investigation shows only two internal implementations exist (DefaultToolsHandler and CodeModeToolsHandler in core/mcp/), both already using the interface signature func(ctx context.Context) map[string][]ChatTool. No external implementations were found. If MCPToolHandler is internal-only, this is not a breaking change for external users. If it's documented as a public extension point, the concern is valid but needs clarification on the repository's public API contract and whether release notes mention this as a breaking change.

core/mcp/default_toolmanager.go (1)

18-54: Constructor and setup readiness changes look sound

Using a map-based toolsFetcherFunc and normalizing DefaultHandlerConfig in NewDefaultToolsHandler avoids error plumbing and gives sane defaults; relaxing SetupCompleted to only require toolsFetcherFunc and clientForToolFetcher matches how the handler is actually used (agent execution can tolerate a nil fetchNewRequestIDFunc as “no override”). I don’t see correctness issues here.

tests/core-mcp/setup.go (2)

108-326: Test tool registration is coherent and matches CodeMode expectations

The test tools (echo, add, multiply, get_data, error_tool, slow_tool, complex_args_tool) are wired consistently: they validate argument shapes, handle type assertions defensively, and return strings that are straightforward to consume (or errors where intended). This provides a solid surface for exercising both normal and failure paths in CodeMode tests.


328-368: connectExternalMCP helper is correct and idempotent

The helper cleanly maps "http"/"sse" strings to the respective MCPConnectionType values, avoids duplicates by checking client.Config.ID, and surfaces errors from both GetMCPClients and AddMCPClient with context. Looks good.

core/mcp/mcp.go (1)

94-112: Mode‑based handler selection and wiring look correct

The new switch config.Mode cleanly routes:

  • "" and MCPModeDefault to NewDefaultToolsHandler, and
  • MCPModeCodeMode to NewCodeModeToolsHandler,

and fails fast on unknown modes. Injecting manager.getAvailableTools, manager.findMCPClientForTool, and config.FetchNewRequestIDFunc into the chosen handler matches the MCPToolHandler interface and keeps permission logic encapsulated in the handler. I don’t see functional issues here.

tests/core-mcp/fixtures.go (1)

3-103: Fixture coverage is broad and aligns with registered MCP tools

CodeFixtures exercises a wide range of scenarios: simple expressions, tool calls (BifrostClient.echo/add/multiply/error_tool/complex_args_tool), import/export stripping, expression analysis, various error cases, edge cases, and environment restrictions (fetch, require, __MCP_ENV__). Tool names and shapes match the test tools defined in registerTestTools, which should give CodeMode thorough behavioral coverage.

core/mcp/codemode_readfile.go (2)

12-47: readToolFile tool schema is well‑specified

The tool definition clearly documents fileName and optional startLine/endLine parameters, uses a JSON‑schema object with a properties map, and requires only fileName. This matches the behavior in handleReadToolFile and should produce good TS definitions for clients.


330-348: toPascalCase is reasonable for typical tool names

Splitting on _, -, and space and capitalizing each part gives sensible PascalCase identifiers for most tool names, with a fallback that capitalizes the first rune if all parts are filtered out. This is sufficient for generating interface names like EchoInput and AddResponse.

tests/core-mcp/mcp_connection_test.go (1)

13-174: Solid coverage for MCP manager, local tools, and CodeMode discovery

These tests nicely exercise MCP client initialization, local tool registration, basic tool execution, and CodeMode list/read tooling end‑to‑end through the public surface. Assertions are clear and use require vs assert appropriately; no issues from a correctness standpoint.

Comment on lines +61 to +80
Description: schemas.Ptr(
"Executes JavaScript code inside a sandboxed goja-based VM with access to all connected MCP servers' tools. " +
"All connected servers are exposed as global objects named after their configuration keys, and each server " +
"provides async (Promise-returning) functions for every tool available on that server. The canonical usage " +
"pattern is: <serverName>.<toolName>({ ...args }).then(result => { ... }). Both <serverName> and <toolName> " +
"should be discovered using listToolFiles and readToolFile. " +

"The environment is intentionally minimal and has several constraints: " +
"• ES modules are not supported — any leading import/export statements are automatically stripped and imported symbols will not exist. " +
"• Browser and Node APIs such as fetch, XMLHttpRequest, axios, require, setTimeout, setInterval, window, and document do not exist. " +
"• async/await syntax is not supported by goja; use Promises and .then()/.catch() for async flows. " +
"• Using undefined server names or tool names will result in reference or function errors. " +
"• The VM does not emulate a browser or Node.js environment — no DOM, timers, modules, or network APIs are available. " +
"• Only ES5.1+ features supported by goja are guaranteed to work. " +

"The runtime will automatically attempt to 'auto-return' the last expression in your code unless a top-level " +
"return is present, enabling REPL-like behavior. Console output (log, error, warn, info) is captured and returned. " +
"Long-running or blocked operations are interrupted via execution timeout. " +
"This tool is designed specifically for orchestrating MCP tool calls and lightweight JavaScript computation.",
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Error hints and examples recommend await even though async/await is explicitly rejected

The environment description and executeCode logic explicitly disallow async/await:

  • createExecuteToolCodeTool tells users to “use Promises and .then()/.catch()”.
  • executeCode returns an error when it sees async or await via regex.

However, generateErrorHints returns hints like:

  • Example: await %s.<toolName>({ url: 'https://example.com' })
  • Ensure you're using the correct syntax: await <serverKey>.<toolName>({ ...args })

These suggestions are inconsistent with the actual restrictions and will lead users straight into the “async/await not supported” error they just hit.

I’d strongly recommend updating all hints and examples here to match the supported pattern, e.g.:

  • serverKey.toolName({ ...args }).then(result => { ... }).catch(err => { ... })

so that error guidance is actionable within the sandbox’s constraints.

Also applies to: 245-260, 962-1042


I'll verify the review comment by examining the codebase.


---

🏁 Script executed:

```shell
#!/bin/bash
# First, locate and examine the file at the specified lines
fd -e go -n core/mcp/codemode_executecode.go | head -1 | xargs -I {} sh -c 'echo "=== File found: {} ===" && wc -l {}'

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Examine lines 61-80 to verify the Description content
sed -n '61,80p' core/mcp/codemode_executecode.go

Length of output: 1787


🏁 Script executed:

#!/bin/bash
# Examine lines 245-260 to see what's there
sed -n '245,260p' core/mcp/codemode_executecode.go

Length of output: 715


🏁 Script executed:

#!/bin/bash
# Search for generateErrorHints function and examine lines 962-1042
sed -n '962,1042p' core/mcp/codemode_executecode.go

Length of output: 4670


🏁 Script executed:

#!/bin/bash
# Search for the regex pattern that rejects async/await
rg -n "async|await" core/mcp/codemode_executecode.go -B 2 -A 2 | head -40

Length of output: 2607


🏁 Script executed:

#!/bin/bash
# Search for error hints with await examples
rg -n "await.*toolName|Example:.*await" core/mcp/codemode_executecode.go -B 1 -A 1

Length of output: 576


Update error hints in generateErrorHints to use Promise syntax instead of await

The review comment is accurate. While the environment description and initial error detection correctly reject async/await, the generateErrorHints function provides contradictory suggestions using await syntax at three locations:

  • Line 977: Example: await %s.<toolName>(...)
  • Line 1007: For example: await %s.<toolName>({ ... })
  • Line 1038: Ensure you're using the correct syntax: await <serverKey>.<toolName>({ ...args })

Update these hints to use the supported Promise pattern: <serverKey>.<toolName>({ ...args }).then(result => { ... }).catch(err => { ... }) to match the actual sandbox constraints.

🤖 Prompt for AI Agents
In core/mcp/codemode_executecode.go around lines 61-80, update
generateErrorHints so that any example suggestions using async/await are
replaced with the supported Promise pattern; specifically change the three
hinted examples at the referenced locations (previously "await
%s.<toolName>(...)" / "await %s.<toolName>({ ... })" / "await
<serverKey>.<toolName>({ ...args })") to use "<serverKey>.<toolName>({ ...args
}).then(result => { ... }).catch(err => { ... })" (preserve formatting and
string interpolation for server/tool placeholders and ensure message text
matches the sandbox constraints).

Comment on lines +309 to +377
for serverKey, tools := range bindings {
serverObj := vm.NewObject()
for toolName, toolInfo := range tools {
// Get the actual tool name and client name
toolInfoMap := toolInfo.(map[string]string)
toolNameCopy := toolInfoMap["toolName"]
clientNameCopy := toolInfoMap["clientName"]

// Create the async function that returns a Promise
// Capture variables for closure
toolNameFinal := toolNameCopy
clientNameFinal := clientNameCopy

serverObj.Set(toolName, func(call goja.FunctionCall) goja.Value {
args := call.Argument(0).Export()

// Create promise first so we can reject it on error
promise, resolve, reject := vm.NewPromise()

// Convert args to map[string]interface{}
argsMap, ok := args.(map[string]interface{})
if !ok {
logger.Debug(fmt.Sprintf("%s Invalid args type for %s.%s: expected object, got %T",
CodeModeLogPrefix, clientNameFinal, toolNameFinal, args))
err := fmt.Errorf("expected object argument, got %T", args)
reject(vm.ToValue(err))
return vm.ToValue(promise)
}

// Call tool asynchronously with timeout context and panic recovery
go func() {
defer func() {
if r := recover(); r != nil {
logger.Debug(fmt.Sprintf("%s Panic in tool call goroutine for %s.%s: %v",
CodeModeLogPrefix, clientNameFinal, toolNameFinal, r))
reject(vm.ToValue(fmt.Errorf("tool call panic: %v", r)))
}
}()

// Check if context is already cancelled before starting
select {
case <-timeoutCtx.Done():
reject(vm.ToValue(fmt.Errorf("execution timeout")))
return
default:
}

result, err := m.callMCPTool(timeoutCtx, clientNameFinal, toolNameFinal, argsMap, logs)

// Check if context was cancelled during execution
select {
case <-timeoutCtx.Done():
reject(vm.ToValue(fmt.Errorf("execution timeout")))
return
default:
}

if err != nil {
logger.Debug(fmt.Sprintf("%s Tool call failed: %s.%s - %v",
CodeModeLogPrefix, clientNameFinal, toolNameFinal, err))
reject(vm.ToValue(err))
return
}
resolve(vm.ToValue(result))
}()

return vm.ToValue(promise)
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Unsafe concurrent use of goja runtime from multiple goroutines

The server tool proxies spawn goroutines that call callMCPTool and then invoke resolve(vm.ToValue(...)) / reject(vm.ToValue(...)) on the same vm instance while vm.RunString(wrappedCode) and subsequent Promise introspection (val.ToObject(vm), AssertFunction, thenFunc(...)) are running on another goroutine.

goja’s Runtime is not designed for concurrent use from multiple goroutines; aside from Interrupt, VM operations (NewPromise, ToValue, resolve/reject, object/property access, etc.) must all run on a single goroutine. This pattern can lead to data races, panics, or corrupted VM state under load and will likely trip go test -race.

You’ll want to rework this so that:

  • All interactions with vm and Promise resolution happen on a dedicated goroutine/event loop, and
  • Asynchronous MCP tool calls communicate results back via channels or goja’s Promise continuation mechanism, with the VM goroutine responsible for calling resolve/reject.

Using eventloop from goja or wiring SetPromiseContinuation to a single‑goroutine loop are common patterns to achieve this.

Also applies to: 389-399, 403-486


🌐 Web query:

Is the `goja.Runtime` safe to call from multiple goroutines concurrently (e.g., `ToValue`, Promise `resolve`/`reject`), and what patterns are recommended for integrating asynchronous operations like Promises?

💡 Result:

Short answer: No — a goja.Runtime (and goja.Promise/resolve/reject) is not goroutine-safe. You must not call Runtime methods (including ToValue) or Promise resolve/reject concurrently from multiple goroutines or while the VM is running on another goroutine. Use a single VM goroutine or otherwise serialize all access. [1][2]

Recommended patterns

  • Run the VM on one dedicated goroutine (an event-loop) and marshal work to it (channels, task queue). Call resolve/reject on that goroutine. This is the simplest and safest approach. [1][2]
  • Use an existing event-loop helper (goja_nodejs / RunOnLoop / NewEventLoop) so background goroutines can schedule work onto the VM and resolve Promises from the VM goroutine. Example usage is shown in the docs. [3][2]
  • If you need parallelism, create a pool of independent Runtime instances and avoid sharing JS values across runtimes; route each request to a runtime from the pool. (Creating many runtimes is heavier than marshaling to one, but avoids cross-goroutine VM access.) [1][2]

References

  • goja (README / docs): “Is it goroutine-safe? No… An instance of goja.Runtime can only be used by a single goroutine at a time.”; NewPromise/Promise warnings. [1][2]
  • goja_nodejs / event-loop example showing RunOnLoop + resolving promises on the VM loop. [3]

If you want, I can paste a minimal example showing (a) a small event-loop goroutine with a channel and (b) how to create a Promise and resolve it via RunOnLoop.


Unsafe concurrent use of goja runtime — verified critical issue

Confirmed via official goja documentation: goja.Runtime (and its Promise resolve/reject methods) is not goroutine-safe and must not be called concurrently from multiple goroutines or while the VM is running on another goroutine.

The current code violates this at lines 309–377 (and 389–399, 403–486) by spawning goroutines that call vm.ToValue(...), resolve(...), and reject(...) while vm.RunString(wrappedCode) and Promise introspection are running on the main goroutine. This will cause data races and VM corruption under load.

Fix: Use one of these patterns—

  1. Event-loop (recommended): Run the VM on a dedicated goroutine with a channel-based task queue. Spawn MCP tool calls as background tasks; return results via channels and have the VM goroutine call resolve/reject. Use goja_nodejs / RunOnLoop or SetPromiseContinuation to schedule work onto the VM goroutine from background tasks.

  2. Runtime pool: Create independent Runtime instances (one per request or pool) to avoid cross-goroutine VM access altogether.

Refactor so all vm.ToValue, Promise creation, and resolve/reject happen on a single goroutine.

🤖 Prompt for AI Agents
In core/mcp/codemode_executecode.go around lines 309-377, the code spawns
goroutines that call vm.NewPromise, vm.ToValue, resolve and reject from
background goroutines which is unsafe because goja.Runtime methods are not
goroutine-safe; refactor so all VM interactions (creating promises, calling
vm.ToValue, and calling resolve/reject) occur on a single goroutine: implement
an event-loop/task-queue where the VM runs on its own goroutine and background
tool calls send results/errors back over channels to the VM goroutine which then
performs vm.ToValue and invokes resolve/reject, or alternatively use a
per-request Runtime/pool so no Runtime is shared across goroutines; ensure
promise creation remains visible to the JS side immediately but
resolution/rejection is performed only by the VM goroutine after receiving the
background task result, and remove any direct vm.* calls from background
goroutines.

Comment thread core/mcp/codemode_executecode.go
Comment on lines +50 to +109
// handleReadToolFile handles the readToolFile tool call
func (m *CodeModeToolsHandler) handleReadToolFile(ctx context.Context, toolCall schemas.ChatAssistantMessageToolCall) (*schemas.ChatMessage, error) {
// Parse tool arguments
var arguments map[string]interface{}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &arguments); err != nil {
return nil, fmt.Errorf("failed to parse tool arguments: %v", err)
}

fileName, ok := arguments["fileName"].(string)
if !ok || fileName == "" {
return nil, fmt.Errorf("fileName parameter is required and must be a string")
}

// Get available tools per client
availableToolsPerClient := m.toolsFetcherFunc(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
for clientName, tools := range availableToolsPerClient {
if len(tools) == 0 {
continue
}
clientNameLower := strings.ToLower(clientName)
if clientNameLower == baseName {
if matchedClientName != "" {
// 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 {
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 matchedClientName == "" {
availableFiles := make([]string, 0, len(availableToolsPerClient))
for name := range availableToolsPerClient {
availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name))
}
errorMsg := fmt.Sprintf("No server found matching filename '%s'. Available virtual files are:\n", fileName)
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
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unused availableFiles variable causes a compile‑time error in the multiple‑match branch

In the “multiple servers match filename” branch you allocate availableFiles and append to it but never read it before returning, which will cause a Go compile error (“availableFiles declared and not used”). Since you already iterate over availableToolsPerClient again to build the error message, availableFiles can simply be removed.

-            if matchedClientName != "" {
-                // Multiple matches found
-                availableFiles := make([]string, 0, len(availableToolsPerClient))
-                for name := range availableToolsPerClient {
-                    availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name))
-                }
+            if matchedClientName != "" {
+                // Multiple matches found
                 errorMsg := fmt.Sprintf("Multiple servers match filename '%s':\n", fileName)
                 for name := range availableToolsPerClient {
                     if strings.ToLower(name) == baseName {
                         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
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// handleReadToolFile handles the readToolFile tool call
func (m *CodeModeToolsHandler) handleReadToolFile(ctx context.Context, toolCall schemas.ChatAssistantMessageToolCall) (*schemas.ChatMessage, error) {
// Parse tool arguments
var arguments map[string]interface{}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &arguments); err != nil {
return nil, fmt.Errorf("failed to parse tool arguments: %v", err)
}
fileName, ok := arguments["fileName"].(string)
if !ok || fileName == "" {
return nil, fmt.Errorf("fileName parameter is required and must be a string")
}
// Get available tools per client
availableToolsPerClient := m.toolsFetcherFunc(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
for clientName, tools := range availableToolsPerClient {
if len(tools) == 0 {
continue
}
clientNameLower := strings.ToLower(clientName)
if clientNameLower == baseName {
if matchedClientName != "" {
// 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 {
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 matchedClientName == "" {
availableFiles := make([]string, 0, len(availableToolsPerClient))
for name := range availableToolsPerClient {
availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name))
}
errorMsg := fmt.Sprintf("No server found matching filename '%s'. Available virtual files are:\n", fileName)
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
}
// handleReadToolFile handles the readToolFile tool call
func (m *CodeModeToolsHandler) handleReadToolFile(ctx context.Context, toolCall schemas.ChatAssistantMessageToolCall) (*schemas.ChatMessage, error) {
// Parse tool arguments
var arguments map[string]interface{}
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &arguments); err != nil {
return nil, fmt.Errorf("failed to parse tool arguments: %v", err)
}
fileName, ok := arguments["fileName"].(string)
if !ok || fileName == "" {
return nil, fmt.Errorf("fileName parameter is required and must be a string")
}
// Get available tools per client
availableToolsPerClient := m.toolsFetcherFunc(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
for clientName, tools := range availableToolsPerClient {
if len(tools) == 0 {
continue
}
clientNameLower := strings.ToLower(clientName)
if clientNameLower == baseName {
if matchedClientName != "" {
// Multiple matches found
errorMsg := fmt.Sprintf("Multiple servers match filename '%s':\n", fileName)
for name := range availableToolsPerClient {
if strings.ToLower(name) == baseName {
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 matchedClientName == "" {
availableFiles := make([]string, 0, len(availableToolsPerClient))
for name := range availableToolsPerClient {
availableFiles = append(availableFiles, fmt.Sprintf("%s.d.ts", name))
}
errorMsg := fmt.Sprintf("No server found matching filename '%s'. Available virtual files are:\n", fileName)
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
}
🤖 Prompt for AI Agents
In core/mcp/codemode_readfile.go around lines 50 to 109, the multiple-match
branch declares and appends to availableFiles but never uses it, causing a
compile error; remove the unused availableFiles variable and its allocation in
that branch and rely on the existing loop over availableToolsPerClient to build
the errorMsg (leave the other branch that builds and uses availableFiles
unchanged) so the function compiles without unused variables.

Comment on lines +111 to +157
// Generate TypeScript definitions
fileContent := m.generateTypeDefinitions(matchedClientName, matchedTools)
lines := strings.Split(fileContent, "\n")
totalLines := len(lines)

// Handle line slicing if provided
var startLine, endLine *int
if sl, ok := arguments["startLine"].(float64); ok {
slInt := int(sl)
startLine = &slInt
}
if el, ok := arguments["endLine"].(float64); ok {
elInt := int(el)
endLine = &elInt
}

if startLine != nil || endLine != nil {
start := 1
if startLine != nil {
start = *startLine
}
end := totalLines
if endLine != nil {
end = *endLine
}

// Validate line numbers
if start < 1 || start > totalLines {
errorMsg := fmt.Sprintf("Invalid startLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%v, totalLines=%d",
start, totalLines, start, endLine, totalLines)
return createToolResponseMessage(toolCall, errorMsg), nil
}
if end < 1 || end > totalLines {
errorMsg := fmt.Sprintf("Invalid endLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%d, totalLines=%d",
end, totalLines, start, end, totalLines)
return createToolResponseMessage(toolCall, errorMsg), nil
}
if start > end {
errorMsg := fmt.Sprintf("Invalid line range: startLine (%d) must be less than or equal to endLine (%d). Total lines in file: %d",
start, end, totalLines)
return createToolResponseMessage(toolCall, errorMsg), nil
}

// Slice lines (convert to 0-based indexing)
selectedLines := lines[start-1 : end]
fileContent = strings.Join(selectedLines, "\n")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Line‑range slicing logic is correct; minor improvement to error message formatting

The conversion from 1‑based inclusive startLine/endLine to 0‑based slice indices (lines[start-1 : end]) is correct and the validation guards against out‑of‑range and inverted ranges. However, in the startLine validation error you print endLine with %v, which will render a pointer value (*int) rather than the numeric end line:

errorMsg := fmt.Sprintf("... Provided: startLine=%d, endLine=%v, totalLines=%d", start, endLine, totalLines)

Consider dereferencing or explicitly handling nil for clarity:

var endVal any = nil
if endLine != nil {
    endVal = *endLine
}
errorMsg := fmt.Sprintf(
    "Invalid startLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%v, totalLines=%d",
    start, totalLines, start, endVal, totalLines,
)

This will produce more useful diagnostics for callers.

🤖 Prompt for AI Agents
In core/mcp/codemode_readfile.go around lines 111 to 157, the startLine
validation error currently logs endLine using %v on a *int which prints a
pointer; change the message to print a numeric value by preparing a local
variable (e.g., endVal) that is nil or the dereferenced *endLine and use that in
the fmt.Sprintf, or explicitly branch to include either "<nil>" or *endLine in
the formatted string so the error shows the actual numeric end line (or clear
nil) instead of a pointer.

Comment on lines +105 to +116
// ExpectedResults contains expected results for validation
var ExpectedResults = struct {
SimpleExpressionResult interface{}
EchoResult string
AddResult float64
MultiplyResult float64
}{
SimpleExpressionResult: float64(2),
EchoResult: "hello",
AddResult: float64(3),
MultiplyResult: float64(6),
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Double‑check numeric result expectations against tool return shapes

ExpectedResults models AddResult and MultiplyResult as float64, while the corresponding test tools format numeric results using fmt.Sprintf("%.0f", ...) (i.e., strings). If the CodeMode bridge coerces these string payloads back into numbers before exposing them to JS, this is fine; otherwise, you may want to align either the tool implementations or these expected types. Worth quickly confirming to avoid assertion mismatches in tests.

🤖 Prompt for AI Agents
In tests/core-mcp/fixtures.go around lines 105-116, ExpectedResults currently
types AddResult and MultiplyResult as float64 but the corresponding tools return
numeric results formatted with fmt.Sprintf("%.0f") (strings); update the fixture
to match the runtime shape by changing AddResult and MultiplyResult to string
values (e.g. "3", "6") or, alternatively, convert the tool outputs to numbers
before assertions—pick one approach and make the types and test assertions
consistent so assertions won’t fail due to string/number mismatches.

Comment on lines +98 to +100
// Use index as key to maintain stable identity during edits
return (
<TableRow key={rowKey} className="border-b last:border-0">
<TableRow key={index} className="border-b last:border-0">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Revert to key-based React keys to prevent focus loss and unnecessary re-renders.

The comment and implementation are misleading. Index-based keys do not maintain stable identity—they do the opposite. When a header row is deleted, all subsequent rows shift indices, causing React to unmount and remount components unnecessarily. This can result in focus loss while editing and poor rendering performance.

The previous approach (using the header key with a fallback) provided true stable identity. Each header row had a consistent key regardless of position changes.

Apply this diff to restore stable keys:

-			// Use index as key to maintain stable identity during edits
 			return (
-				<TableRow key={index} className="border-b last:border-0">
+				<TableRow key={key || `empty-${index}`} className="border-b last:border-0">

This approach ensures that:

  • Each header with a non-empty key maintains its identity across re-renders
  • The empty row (or multiple empty rows if they somehow exist) gets a stable fallback key
  • Focus is preserved when deleting or reordering headers
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use index as key to maintain stable identity during edits
return (
<TableRow key={rowKey} className="border-b last:border-0">
<TableRow key={index} className="border-b last:border-0">
return (
<TableRow key={key || `empty-${index}`} className="border-b last:border-0">
🤖 Prompt for AI Agents
In ui/components/ui/headersTable.tsx around lines 98–100, the row key currently
uses the array index which causes identity shifts and focus loss when rows are
removed; replace the index-based key with the header's stable identifier (e.g.,
header.key) and, for headers that lack a key, generate and attach a stable
fallback id (e.g., header._tempId) when the header is created/loaded so it
persists across renders; then use key={header.key || header._tempId} for the
TableRow to preserve identity and prevent unnecessary unmounts.

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 11-18-feat_added_codemode_to_mcp branch from 0750d39 to 511c8c9 Compare November 25, 2025 13:29
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 11-18-feat_mcp_core_refactor_and_agent_mode_added branch from 38c9253 to 81d6388 Compare November 25, 2025 13:29
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/bifrost.go (1)

1196-1208: Confirmed: Duplicate client registration when initializing MCP manager.

When bifrost.mcpManager is nil, NewMCPManager is passed ClientConfigs: []schemas.MCPClientConfig{config}. Inside NewMCPManager (core/mcp/mcp.go:114-116), the function explicitly iterates through config.ClientConfigs and calls manager.AddClient(clientConfig) for each entry during initialization. Then line 1207 calls AddClient(config) unconditionally again, causing the same client to be registered twice.

The suggested fix is correct—return early after successful manager initialization:

 func (bifrost *Bifrost) AddMCPClient(config schemas.MCPClientConfig) error {
 	if bifrost.mcpManager == nil {
 		mcpManager, err := mcp.NewMCPManager(bifrost.ctx, schemas.MCPConfig{
 			ClientConfigs: []schemas.MCPClientConfig{config},
 		}, bifrost.logger)
 		if err != nil {
 			return fmt.Errorf("failed to initialize MCP manager: %v", err)
 		}
 		bifrost.mcpManager = mcpManager
+		return nil
 	}
 
 	return bifrost.mcpManager.AddClient(config)
 }
♻️ Duplicate comments (4)
core/mcp/codemode_readfile.go (2)

83-97: Unused availableFiles variable causes compile error.

The availableFiles slice is declared and populated at lines 85-88 but never used before the function returns at line 96. This will cause a Go compile error.

Apply this diff to remove the unused variable:

 			if matchedClientName != "" {
 				// 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)

143-146: Format specifier %v on *int prints pointer, not value.

When endLine is non-nil, %v will print the pointer representation rather than the numeric value, making the error message confusing for users.

Consider dereferencing for clarity:

+		var endVal interface{} = "<not specified>"
+		if endLine != nil {
+			endVal = *endLine
+		}
 		if start < 1 || start > totalLines {
-			errorMsg := fmt.Sprintf("Invalid startLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%v, totalLines=%d",
-				start, totalLines, start, endLine, totalLines)
+			errorMsg := fmt.Sprintf("Invalid startLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%v, totalLines=%d",
+				start, totalLines, start, endVal, totalLines)
 			return createToolResponseMessage(toolCall, errorMsg), nil
 		}
tests/core-mcp/fixtures.go (1)

300-311: Verify numeric result type consistency.

As noted in a previous review, ExpectedResults defines AddResult and MultiplyResult as float64, but the corresponding test tools format results using fmt.Sprintf("%.0f", ...) (strings). Verify that the code-mode bridge properly coerces these values, or align the types to prevent assertion mismatches.

Run the following to check how the test tools return numeric results:

#!/bin/bash
# Search for tool implementations that return numeric results
rg -n -A 5 'func.*\(add|multiply\)' tests/core-mcp/setup.go
core/mcp/codemode_executecode.go (1)

425-479: Critical: Unsafe concurrent access to goja.Runtime persists.

This was flagged in a previous review and remains unaddressed. The code spawns goroutines (line 442) that call vm.ToValue() and resolve()/reject() (lines 447, 454, 464, 472, 475) while the main goroutine continues to interact with the same vm instance.

Per goja documentation, Runtime is not goroutine-safe—all VM operations must occur on a single goroutine. This will cause data races and potential crashes under concurrent load.

Recommended fix: Use an event-loop pattern where the VM runs on a dedicated goroutine and tool results are communicated via channels:

// Sketch of event-loop approach
type vmTask struct {
    resolve func(interface{})
    reject  func(error)
    result  interface{}
    err     error
}

taskChan := make(chan vmTask)

// All resolve/reject calls go through the channel
go func() {
    result, err := m.callMCPTool(...)
    taskChan <- vmTask{result: result, err: err}
}()

// VM goroutine processes tasks
select {
case task := <-taskChan:
    if task.err != nil {
        reject(vm.ToValue(task.err))
    } else {
        resolve(vm.ToValue(task.result))
    }
// ...
}
🧹 Nitpick comments (17)
core/mcp/codemode_readfile.go (2)

230-236: Consider using sort.Strings instead of manual bubble sort.

The manual bubble sort implementation works but is less idiomatic. The standard library provides a cleaner solution.

+import "sort"
+
 			// Sort properties for consistent output
 			propNames := make([]string, 0, len(props))
 			for name := range props {
 				propNames = append(propNames, name)
 			}
-			// Simple alphabetical sort
-			for i := 0; i < len(propNames)-1; i++ {
-				for j := i + 1; j < len(propNames); j++ {
-					if propNames[i] > propNames[j] {
-						propNames[i], propNames[j] = propNames[j], propNames[i]
-					}
-				}
-			}
+			sort.Strings(propNames)

335-353: toPascalCase lowercases non-initial characters, which may not preserve acronyms.

Line 346 uses strings.ToLower(part[1:]) which converts "myAPI" to "Myapi" rather than "MyAPI". This is a common convention choice, but worth noting if acronym preservation is desired.

If preserving acronyms is preferred:

 		if len(part) > 0 {
-			result += strings.ToUpper(part[:1]) + strings.ToLower(part[1:])
+			result += strings.ToUpper(part[:1]) + part[1:]
 		}
tests/core-mcp/edge_cases_test.go (1)

205-210: Repeated pattern of conditional assertions across multiple tests.

Tests TestCodeWithRuntimeErrors, TestCodeCallingToolsWithInvalidArguments, and TestUndefinedVariableError all use the same if bifrostErr == nil pattern. Consider either:

  1. Consistently using requireNoBifrostError if the MCP call should succeed
  2. Adding explicit failure assertions if bifrostErr is expected to be non-nil

This pattern appears in 5 tests total and should be addressed consistently.

Also applies to: 226-231, 292-297

tests/core-mcp/agent_mode_test.go (1)

54-74: Test doesn't verify MaxAgentDepth is actually configured.

The test comment says "Verify max depth is configured" but the assertion only checks that clients exist. Consider adding an assertion that verifies the max depth configuration is accessible or affects behavior as expected.

 	// Verify max depth is configured
 	clients, err := b.GetMCPClients()
 	require.NoError(t, err)
 	assert.NotNil(t, clients, "Should have clients")
+	
+	// TODO: Add assertion to verify MaxAgentDepth is respected
+	// This may require exposing the config or testing agent mode behavior
framework/configstore/migrations.go (1)

1094-1097: Consider adding NOT NULL constraint for PostgreSQL.

For consistency with migrationAddMCPClientIDColumn (lines 895-899), consider adding a NOT NULL constraint for PostgreSQL after populating existing rows:

 			// Initialize existing rows with false (default value)
 			if err := tx.Exec("UPDATE config_mcp_clients SET is_code_mode_client = false WHERE is_code_mode_client IS NULL").Error; err != nil {
 				return fmt.Errorf("failed to initialize is_code_mode_client: %w", err)
 			}
+			// Enforce NOT NULL in Postgres to guarantee value presence on new rows
+			if tx.Dialector.Name() == "postgres" {
+				if err := tx.Exec("ALTER TABLE config_mcp_clients ALTER COLUMN is_code_mode_client SET NOT NULL").Error; err != nil {
+					return fmt.Errorf("failed to set is_code_mode_client NOT NULL: %w", err)
+				}
+			}

This is optional since the GORM default handles new rows, but adds an extra layer of data integrity.

tests/core-mcp/tool_execution_test.go (2)

110-115: Conditional assertion may mask test failures.

This pattern silently passes if bifrostErr is non-nil. Either assert the expected error explicitly or require success. Same applies to TestListToolFilesWithOnlyNonCodeModeClients (lines 138-145).

-	// listToolFiles should still work but return empty/no servers message
-	if bifrostErr == nil && result != nil {
-		responseText := *result.Content.ContentStr
-		assert.Contains(t, responseText, "No servers", "Should indicate no servers")
-	}
+	// listToolFiles should still work but return empty/no servers message
+	requireNoBifrostError(t, bifrostErr)
+	require.NotNil(t, result)
+	require.NotNil(t, result.Content.ContentStr)
+	responseText := *result.Content.ContentStr
+	assert.Contains(t, responseText, "No servers", "Should indicate no servers")

160-163: Add nil checks before dereferencing ContentStr.

Multiple tests in this file (lines 161, 181, 200, 222) dereference ContentStr without nil checks. Consider adding guards or using a helper function that handles this consistently.

tests/core-mcp/codemode_auto_execute_test.go (2)

20-28: Consider extracting repeated client lookup to a helper.

This pattern of finding the bifrostInternal client by ID is repeated in multiple tests (lines 20-28, 60-68, 100-108, and in other test files). Extract to a helper for DRYer tests.

// In setup.go or utils.go
func findBifrostInternalConfig(t *testing.T, b *bifrost.Bifrost) *schemas.MCPClientConfig {
	t.Helper()
	clients, err := b.GetMCPClients()
	require.NoError(t, err)
	for _, client := range clients {
		if client.Config.ID == "bifrostInternal" {
			return &client.Config
		}
	}
	t.Fatal("bifrostInternal client not found")
	return nil
}

208-213: Clarify expected behavior for error scenarios.

The test expects either a bifrostErr or a failed execution result with "runtime" hint. Consider making this explicit rather than conditionally asserting.

 	result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
-	// Should fail with runtime error
-	if bifrostErr == nil {
-		assertExecutionResult(t, result, false, nil, "runtime")
-	}
+	// Should fail with either a BifrostError or a runtime execution error
+	if bifrostErr != nil {
+		// Error at tool execution level - acceptable
+		return
+	}
+	// Error within code execution - check result
+	assertExecutionResult(t, result, false, nil, "runtime")
tests/core-mcp/setup.go (3)

13-14: Consider reducing test timeout.

10 minutes is quite long for unit tests. This could hide hung tests or performance regressions. Consider a shorter default (e.g., 30-60 seconds) unless specific tests require longer durations.

 // TestTimeout defines the maximum duration for MCP tests
-const TestTimeout = 10 * time.Minute
+const TestTimeout = 60 * time.Second

358-371: Consider adding a maximum delay for slow_tool.

The slow_tool accepts an unbounded delay_ms parameter. While this is test code, adding a reasonable maximum (e.g., 5000ms) would prevent accidentally long-running tests.

 	if err := b.RegisterMCPTool("slow_tool", "A tool that takes time to execute", func(args any) (string, error) {
 		argsMap, ok := args.(map[string]interface{})
 		if !ok {
 			return "", fmt.Errorf("invalid args type")
 		}
 		delayMs, ok := argsMap["delay_ms"].(float64)
 		if !ok {
 			return "", fmt.Errorf("delay_ms field is required")
 		}
+		// Cap delay to prevent accidentally long-running tests
+		const maxDelayMs = 5000
+		if delayMs > maxDelayMs {
+			delayMs = maxDelayMs
+		}
 		time.Sleep(time.Duration(delayMs) * time.Millisecond)
 		return fmt.Sprintf("Completed after %v ms", delayMs), nil
 	}, slowToolSchema); err != nil {

23-31: Test may fail silently if OPENAI_API_KEY is not set.

GetKeysForProvider reads from environment without validation. Consider logging a warning or documenting that this env var is required for tests that use real API calls.

 func (a *TestAccount) GetKeysForProvider(ctx *context.Context, providerKey schemas.ModelProvider) ([]schemas.Key, error) {
+	apiKey := os.Getenv("OPENAI_API_KEY")
+	// Note: Tests using real API calls require OPENAI_API_KEY to be set
 	return []schemas.Key{
 		{
-			Value:  os.Getenv("OPENAI_API_KEY"),
+			Value:  apiKey,
 			Models: []string{},
 			Weight: 1.0,
 		},
 	}, nil
 }
core/mcp/agent.go (2)

68-145: Consider adding error handling for edge cases in code-mode validation.

The code-mode validation logic is comprehensive, but consider the following edge cases:

  1. Escaped newline handling (lines 93-96): The conversion of \n to actual newlines might not handle all escape sequences (e.g., \t, \r). Consider using a more robust approach if additional escape sequences are expected.

  2. Tool call extraction error handling (lines 99-104): When extractToolCallsFromCode fails, the error is logged but the tool is marked non-auto-executable. Consider whether more specific error messages would help debugging.

  3. Validation failure messaging (lines 121-123): The debug logs are helpful, but consider whether more context (e.g., which specific tool failed validation) would aid troubleshooting.


452-499: Consider adding documentation for the buildAllowedAutoExecutionTools function.

The function is well-implemented, but would benefit from additional inline documentation explaining:

  1. The relationship between allClientNames and allowedTools return values
  2. Why only code-mode clients are included
  3. How the wildcard "*" is handled vs. specific tool names

This would make the function easier to understand and maintain.

core/mcp/toolmanager.go (2)

54-76: Consider validating injected functions are non-nil before first use.

The setter methods allow injecting nil functions which would cause panics at runtime when called (e.g., in ParseAndAddToolsToRequest at line 88). Consider adding nil checks either in setters or at usage points.

Example validation in ParseAndAddToolsToRequest:

if m.toolsFetcherFunc == nil {
    return req // or return error
}

217-229: Tool availability check has O(n×m) complexity.

The nested loop iterates over all clients and their tools to check availability. For a small number of tools this is fine, but if tool counts grow significantly, consider maintaining a pre-built flat map of available tools.

This is a minor optimization opportunity - acceptable as-is for typical workloads.

core/mcp/codemode_executecode.go (1)

273-283: Async IIFE detection is fragile.

The check strings.HasPrefix(trimmedLower, "(async") with strings.Contains(trimmedCode, ")()") may not correctly identify all async IIFE patterns (e.g., with whitespace variations or different formatting). Consider a more robust regex pattern if this edge case matters.

This is a minor edge case - the current implementation handles the common patterns.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0750d39 and 511c8c9.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • tests/core-mcp/go.sum is excluded by !**/*.sum
📒 Files selected for processing (34)
  • core/bifrost.go (1 hunks)
  • core/chatbot_test.go (2 hunks)
  • core/go.mod (2 hunks)
  • core/mcp/agent.go (7 hunks)
  • core/mcp/agent_test.go (2 hunks)
  • core/mcp/clientmanager.go (2 hunks)
  • core/mcp/codemode_executecode.go (1 hunks)
  • core/mcp/codemode_listfiles.go (1 hunks)
  • core/mcp/codemode_readfile.go (1 hunks)
  • core/mcp/mcp.go (3 hunks)
  • core/mcp/toolmanager.go (1 hunks)
  • core/mcp/utils.go (6 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mcp.go (1 hunks)
  • core/schemas/utils.go (1 hunks)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (3 hunks)
  • framework/configstore/tables/mcp.go (1 hunks)
  • framework/configstore/utils.go (1 hunks)
  • tests/core-mcp/README.md (1 hunks)
  • tests/core-mcp/agent_mode_test.go (1 hunks)
  • tests/core-mcp/auto_execute_config_test.go (1 hunks)
  • tests/core-mcp/client_config_test.go (1 hunks)
  • tests/core-mcp/codemode_auto_execute_test.go (1 hunks)
  • tests/core-mcp/edge_cases_test.go (1 hunks)
  • tests/core-mcp/fixtures.go (1 hunks)
  • tests/core-mcp/go.mod (1 hunks)
  • tests/core-mcp/integration_test.go (1 hunks)
  • tests/core-mcp/mcp_connection_test.go (1 hunks)
  • tests/core-mcp/setup.go (1 hunks)
  • tests/core-mcp/tool_execution_test.go (1 hunks)
  • tests/core-mcp/utils.go (1 hunks)
  • transports/bifrost-http/handlers/mcp.go (1 hunks)
  • transports/bifrost-http/lib/config.go (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • core/chatbot_test.go
  • tests/core-mcp/README.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • transports/bifrost-http/handlers/mcp.go
  • core/schemas/mcp.go
  • core/go.mod
  • core/mcp/codemode_listfiles.go
  • core/mcp/utils.go
🧰 Additional context used
🧬 Code graph analysis (13)
tests/core-mcp/setup.go (1)
core/schemas/mcp.go (2)
  • MCPConfig (19-29)
  • MCPClientConfig (32-54)
framework/configstore/rdb.go (1)
framework/configstore/tables/mcp.go (2)
  • TableMCPClient (12-31)
  • TableMCPClient (34-34)
core/mcp/clientmanager.go (1)
core/mcp/mcp.go (2)
  • BifrostMCPClientKey (22-22)
  • BifrostMCPClientName (21-21)
core/mcp/agent_test.go (1)
core/mcp/agent.go (1)
  • ExecuteAgent (14-267)
transports/bifrost-http/lib/config.go (1)
core/schemas/mcp.go (1)
  • MCPConfig (19-29)
framework/configstore/utils.go (3)
core/schemas/mcp.go (1)
  • MCPClientConfig (32-54)
ui/lib/types/mcp.ts (1)
  • MCPClientConfig (13-23)
framework/configstore/clientconfig.go (1)
  • EnvKeyInfo (20-26)
core/mcp/codemode_readfile.go (5)
core/mcp/toolmanager.go (2)
  • ToolsHandler (14-22)
  • ToolTypeReadToolFile (31-31)
core/schemas/chatcompletions.go (4)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
ui/lib/types/logs.ts (1)
  • Function (141-146)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/mcp/mcp.go (1)
  • MCPLogPrefix (23-23)
core/mcp/agent.go (5)
core/schemas/chatcompletions.go (1)
  • ChatTool (201-205)
core/schemas/mcp.go (1)
  • MCPClientState (83-90)
core/mcp/toolmanager.go (3)
  • ToolTypeListToolFiles (30-30)
  • ToolTypeReadToolFile (31-31)
  • ToolTypeExecuteToolCode (32-32)
core/mcp/codemode_executecode.go (1)
  • CodeModeLogPrefix (60-60)
core/providers/gemini/types.go (1)
  • FinishReason (18-18)
core/mcp/toolmanager.go (6)
core/schemas/mcp.go (1)
  • MCPClientState (83-90)
core/schemas/chatcompletions.go (4)
  • ChatTool (201-205)
  • ChatParameters (154-183)
  • BifrostChatRequest (11-18)
  • BifrostChatResponse (25-40)
core/schemas/bifrost.go (1)
  • ResponsesRequest (91-91)
core/mcp/mcp.go (1)
  • MCPLogPrefix (23-23)
core/schemas/responses.go (1)
  • ResponsesParameters (84-111)
core/mcp/agent.go (1)
  • ExecuteAgent (14-267)
core/mcp/mcp.go (2)
core/mcp/toolmanager.go (3)
  • ToolsHandler (14-22)
  • NewToolsHandler (35-52)
  • ToolsHandlerConfig (24-27)
core/schemas/mcp.go (1)
  • MCPClientState (83-90)
framework/configstore/migrations.go (2)
framework/migrator/migrator.go (3)
  • New (131-149)
  • DefaultOptions (100-106)
  • Migration (62-69)
framework/configstore/tables/mcp.go (2)
  • TableMCPClient (12-31)
  • TableMCPClient (34-34)
core/schemas/utils.go (2)
core/schemas/chatcompletions.go (6)
  • ChatTool (201-205)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatToolCustom (225-227)
  • ChatToolCustomFormat (229-232)
  • ChatToolCustomGrammarFormat (235-238)
transports/bifrost-http/lib/config.go (1)
  • DeepCopy (2999-3007)
core/mcp/codemode_executecode.go (4)
core/mcp/toolmanager.go (2)
  • ToolsHandler (14-22)
  • ToolTypeExecuteToolCode (32-32)
core/schemas/chatcompletions.go (3)
  • ChatTool (201-205)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/schemas/mcp.go (1)
  • MCPClientState (83-90)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (42)
core/schemas/utils.go (1)

703-786: Implementation looks correct.

The deep copy logic properly handles all nested fields of ChatTool, including proper nil checks, slice copies, and map deep copies. The implementation follows the established pattern of other deep copy functions in this file.

However, the AI summary mentions "two identical DeepCopyChatTool implementations are present in the patch," but only one implementation is visible in the provided code. If duplication exists elsewhere, please consolidate to avoid maintenance burden.

tests/core-mcp/go.mod (2)

3-3: Verify Go version 1.24.3 is intentional and project-compatible.

Go 1.24.3 is beyond the standard release cycle knowledge available in this review. Confirm that this version is correct for your project and compatible with all CI/CD pipelines and team development environments.


38-38: The original review comment is based on incorrect assumptions about goja's versioning strategy.

dop251/goja does not publish formal GitHub releases or tagged versions—the project uses commits instead. This means using the repository (latest tag/commit) as the source is the standard approach. The pseudo-version v0.0.0-20251103141225-af2ceb9156d7 correctly pins to a specific commit, which is the proper way to depend on goja in Go and actually improves build reproducibility by locking to an exact commit hash. No action is needed on this dependency.

Likely an incorrect or invalid review comment.

core/schemas/bifrost.go (1)

362-362: Removing omitempty ensures consistent JSON response structure.

This change means extra_fields will always appear in JSON output, even when empty. This provides a more predictable API contract for consumers. The tradeoff is slightly larger response payloads when the field is empty.

core/mcp/codemode_readfile.go (1)

298-333: TypeScript type generation handles basic JSON Schema types well.

The implementation correctly maps string, number, integer, boolean, array, object, null, and enum types. The fallback to any is reasonable for unsupported constructs like anyOf, oneOf, or allOf.

framework/configstore/tables/mcp.go (1)

16-16: Clean addition of IsCodeModeClient field with appropriate defaults.

The field follows existing patterns with proper GORM tagging (default:false for backward compatibility) and snake_case JSON naming convention consistent with other fields.

core/mcp/agent_test.go (1)

181-181: LGTM - Test correctly passes nil for code-mode fetchers.

Since this test validates the "no tool calls" path where extractToolCalls returns empty and the agent exits immediately, the nil fetcher functions are never invoked.

tests/core-mcp/edge_cases_test.go (1)

1-297: LGTM on overall test coverage.

The test suite provides comprehensive edge-case coverage for the new code mode functionality, including:

  • Cross-client tool access restrictions
  • Tool execution permissions (ToolsToExecute)
  • Timeout and error propagation
  • Various code execution scenarios (empty, syntax errors, runtime errors, comments-only)
  • Auto-execution behavior

The test structure and use of helper functions is clean and maintainable.

tests/core-mcp/agent_mode_test.go (1)

12-52: LGTM on agent mode configuration test.

The test properly validates auto-execute configuration by:

  1. Setting up tools with specific auto-execute permissions
  2. Locating the correct MCP client
  3. Verifying canAutoExecuteTool returns expected results for different tools

Good use of the canAutoExecuteTool helper for validation.

core/mcp/clientmanager.go (2)

150-158: LGTM on IsCodeModeClient propagation in EditClient.

The addition correctly propagates the IsCodeModeClient flag from updatedConfig to the client's ExecutionConfig, ensuring the code-mode status is preserved during client configuration updates.


556-570: LGTM on local MCP client initialization.

The updated initialization properly sets:

  • ID using BifrostMCPClientKey constant
  • Name using BifrostMCPClientName constant
  • ToolsToExecute to ["*"] for unrestricted internal tool access

This aligns with the per-client tool organization introduced in this PR and ensures the internal client has appropriate identifiers and permissions.

framework/configstore/migrations.go (1)

1083-1115: LGTM on migration for is_code_mode_client column.

The migration correctly:

  1. Checks for column existence before adding
  2. Initializes existing rows with false to prevent NULL values
  3. Provides a rollback that drops the column
  4. Uses a unique migration ID

The pattern is consistent with other migrations in this file.

transports/bifrost-http/lib/config.go (3)

1994-1998: LGTM on improved error message.

The error message change from "failed to add MCP client" to "failed to connect MCP client" more accurately describes the failure point - the client config was added to the in-memory list but the connection attempt failed.


2144-2149: LGTM on IsCodeModeClient propagation in EditMCPClient.

Correctly updates the in-memory config with IsCodeModeClient from the processed configuration, ensuring consistency between the config store and runtime state.


2181-2192: LGTM on IsCodeModeClient in RedactMCPClientConfig.

The IsCodeModeClient field is correctly included in the redacted copy. This field indicates whether a client operates in code mode and is not sensitive information, so it's appropriate to include it in the redacted output.

framework/configstore/utils.go (2)

186-202: LGTM - Header restoration logic is well-structured.

The logic correctly handles the case where the UI sends redacted headers back. The conditional checks ensure that:

  1. Only redacted values (containing ****) are restored
  2. Values already in env. format are preserved
  3. Existing non-env headers are properly restored

228-238: Clear handling of three header value states.

The logic correctly distinguishes between:

  • env.VAR format → update to correct env var
  • Redacted **** values → restore to env.VAR format
  • Plain values → leave unchanged

This ensures the substitution doesn't accidentally overwrite user-entered plain values.

tests/core-mcp/codemode_auto_execute_test.go (1)

166-170: Good test coverage for meta-tool sandboxing.

These tests verify that listToolFiles and readToolFile cannot be called from within executeToolCode, which is an important security constraint. The comment clearly documents the expected behavior.

tests/core-mcp/auto_execute_config_test.go (3)

12-44: Comprehensive configuration test coverage.

Good coverage of the ToolsToExecute and ToolsToAutoExecute semantics. The test verifies that a tool must be in ToolsToExecute to be executable, regardless of ToolsToAutoExecute settings.


210-216: Nil-vs-empty test logic is correct.

The if/else pattern correctly tests that nil ToolsToAutoExecute is treated the same as an empty slice, matching the schema documentation: "nil/omitted => treated as [] (no tools)".


252-284: Edge case: Verify ToolsToAutoExecute cannot bypass ToolsToExecute.

This test correctly verifies that even with wildcard ToolsToAutoExecute, an empty ToolsToExecute means no tools can be auto-executed. This is important for the deny-by-default security model.

tests/core-mcp/setup.go (2)

408-448: LGTM - External MCP connection helper is well-implemented.

The connectExternalMCP function:

  • Handles both HTTP and SSE connection types
  • Has an idempotent check to prevent duplicate client additions
  • Returns appropriate errors for unsupported connection types

141-186: Based on the ripgrep search results, the four helper functions (setupCodeModeClient, setupNonCodeModeClient, setupClientWithAutoExecute, setupClientWithToolFilter) are only found at their definition locations in tests/core-mcp/setup.go and have no call sites anywhere in the codebase.

These helper functions appear to be unused and should be removed as dead code.

The initial verification conclusively shows these are not being called anywhere. Since no calls to these functions were detected by ripgrep across the entire codebase, they are indeed unused.

tests/core-mcp/client_config_test.go (1)

1-337: LGTM! Comprehensive test coverage for MCP client configuration.

The test suite thoroughly exercises client configuration scenarios including code-mode vs non-code-mode clients, configuration updates, tool filtering, and mixed client scenarios. Tests follow consistent patterns with proper setup, assertions, and cleanup.

framework/configstore/rdb.go (3)

670-670: LGTM! IsCodeModeClient properly populated from database.

The field is correctly read from the database record and mapped to the schema.


706-713: LGTM! IsCodeModeClient properly persisted during creation.

The field is correctly set on the database record, and the comment clarifies the env var handling for create operations.


748-761: LGTM! Correct handling of IsCodeModeClient updates with Select.

The use of Select is essential to ensure IsCodeModeClient (including false values) is persisted correctly, as GORM's Updates() skips zero values by default. The comment clearly explains this behavior.

tests/core-mcp/integration_test.go (1)

1-221: LGTM! Comprehensive integration tests for code-mode workflows.

The test suite provides excellent coverage of code-mode execution scenarios including tool discovery, execution, error handling, and complex multi-tool workflows. Tests properly use fixtures and helper assertions.

tests/core-mcp/utils.go (1)

1-283: LGTM! Well-designed test helper utilities.

The helper functions provide comprehensive support for constructing tool calls, validating execution results, and asserting agent mode behavior. All functions are properly documented and follow consistent patterns.

core/mcp/mcp.go (2)

94-112: LGTM! ToolsHandler initialization with proper fetcher wiring.

The initialization correctly sets up the ToolsHandler with all necessary fetchers, including the new client-by-name fetcher that enables code-mode tool discovery. The lambda closure properly captures the manager's clientMap access.


124-129: LGTM! Clean delegation to ToolsHandler.

The refactoring to delegate tool management to ToolsHandler simplifies the manager's responsibilities and centralizes tool handling logic.

tests/core-mcp/mcp_connection_test.go (1)

1-285: LGTM! Thorough MCP connection and tool execution tests.

The test suite provides excellent coverage of MCP initialization, tool registration, discovery, and execution scenarios including timeout and error handling. The skip for external MCP connection tests is appropriate without credentials.

core/mcp/agent.go (2)

23-24: LGTM! New fetcher parameters enable code-mode validation.

The addition of toolsFetcherFunc and clientByNameFetcherFunc parameters enables the agent to validate code-mode tool calls against allowed auto-execution configurations.


428-432: LGTM! finish_reason change correctly prevents agent loop retries.

Setting finish_reason to "stop" for non-auto-executable tools is the correct approach. This ensures hasToolCalls returns false and the agent loop exits properly, while still including the tool calls in the response for the caller to handle.

core/mcp/toolmanager.go (2)

87-103: LGTM: Tool aggregation logic is well-structured.

The separation of code-mode clients (which expose only the three code-mode tools) from regular clients is clear, and the deduplication approach using maps is efficient.


298-345: LGTM: Auto-execution tool mapping logic is clear.

The function correctly separates the concern of collecting all client names from building the auto-executable tools mapping for code-mode clients only. The wildcard handling is straightforward.

core/mcp/codemode_executecode.go (6)

63-105: LGTM: Tool definition correctly documents async/await support.

The description accurately states that async/await syntax is supported and automatically transpiled to Promise chains, which aligns with the TypeScript-to-ES5 transpilation implementation.


107-233: LGTM: Comprehensive response formatting with good UX.

The handler provides detailed feedback in all scenarios (error, no-data, success) including environment context and actionable hints. The "no data" case handling helps users debug silent execution issues.


490-501: LGTM: Interrupt handler correctly uses vm.Interrupt().

The interrupt goroutine only calls vm.Interrupt(), which is documented as the one goja method safe to call from another goroutine. The buffered channels for promise results (lines 540-541) prevent blocking.


746-776: LGTM: Import/export stripping handles common cases.

The regex-based approach handles standard single-line import/export statements. Multi-line imports would require more complex parsing, but given that imports are stripped and symbols won't exist anyway, the current approach is pragmatic.


814-894: LGTM: Error hints now correctly suggest await syntax.

The previous review flagged inconsistency between rejecting async/await and suggesting it in hints. The current implementation supports async/await via TypeScript transpilation, so the hints are now consistent with the actual capabilities.


22-26: toolCallInfo is actively used in core/mcp/utils.go; the original concern is invalid.

The struct is referenced in the extractToolCallsFromCode function in core/mcp/utils.go (lines 380, 381, 398) and should not be removed.

Likely an incorrect or invalid review comment.

Comment thread core/mcp/agent_test.go Outdated
Comment on lines +249 to +252
ctx := context.Background()

// Execute agent mode - should hit max depth and return error
result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, nil)
result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, nil, nil, nil)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Passing nil for clientForToolFetcherFunc will cause panic when tool calls are processed.

The test response contains tool calls (e.g., "test_tool"), which triggers the agent loop. In ExecuteAgent, line ~65 calls clientForToolFetcherFunc(toolName). Since nil is passed for this parameter, this will result in a nil function call panic.

Provide a stub function that returns nil to indicate "client not found":

 	ctx := context.Background()
 
 	// Execute agent mode - should hit max depth and return error
-	result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, nil, nil, nil)
+	result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, 
+		func(toolName string) *schemas.MCPClientState { return nil }, // Stub: no client found
+		nil, nil)

🏁 Script executed:

#!/bin/bash
# Verify if this test currently passes or panics
cd tests/core-mcp 2>/dev/null || cd .
# Look for any nil checks on clientForToolFetcherFunc in agent.go
rg -n "clientForToolFetcherFunc" --type go -A2 -B2

Length of output: 0


I need to locate the correct files and verify the implementation. Let me search more broadly:




---

🏁 Script executed:

```shell
#!/bin/bash
# Find agent.go and agent_test.go locations
fd -e go -type f | grep -E "(agent\.go|agent_test\.go)" | head -20

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for clientForToolFetcherFunc in all go files
rg -l "clientForToolFetcherFunc" --type go

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Look for ExecuteAgent function definition
rg -n "func ExecuteAgent" --type go -A 15

Length of output: 1293





---

🏁 Script executed:

```shell
#!/bin/bash
# Find where clientForToolFetcherFunc is actually called
rg -n "clientForToolFetcherFunc\(" core/mcp/agent.go -A2 -B2

Length of output: 172


🏁 Script executed:

#!/bin/bash
# Look at the test file around lines 249-252
sed -n '240,260p' core/mcp/agent_test.go

Length of output: 574


🏁 Script executed:

#!/bin/bash
# Check the initialResponse definition in the test
rg -n "initialResponse.*:=" core/mcp/agent_test.go -B10 | tail -50

Length of output: 287



---

🏁 Script executed:

```shell
#!/bin/bash
# Verify createToolCallResponse includes tool calls
rg -n "func createToolCallResponse" core/mcp/agent_test.go -A 30

Length of output: 0





🏁 Script executed:

#!/bin/bash
# Search for createToolCallResponse definition more broadly
rg -n "createToolCallResponse" core/mcp/agent_test.go

Length of output: 294


🏁 Script executed:

#!/bin/bash
# Search the entire test file for the function
rg -n "createToolCallResponse" core/mcp/agent_test.go -B5 -A20 | head -60

Length of output: 1886


Passing nil for clientForToolFetcherFunc will cause panic when tool calls are processed.

The initialResponse contains tool calls (FinishReason: "tool_calls", ToolCalls array with "test_tool"), which triggers the agent loop at agent.go line 66. This calls clientForToolFetcherFunc(toolName), which will panic since nil is passed for this parameter.

Provide a stub function that returns nil:

 	ctx := context.Background()
 
 	// Execute agent mode - should hit max depth and return error
-	result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, nil, nil, nil)
+	result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, 
+		func(toolName string) *schemas.MCPClientState { return nil }, // Stub: no client found
+		nil, nil)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ctx := context.Background()
// Execute agent mode - should hit max depth and return error
result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, nil)
result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil, nil, nil, nil)
ctx := context.Background()
// Execute agent mode - should hit max depth and return error
result, err := ExecuteAgent(&ctx, maxDepth, originalReq, initialResponse, llmCaller, nil, nil,
func(toolName string) *schemas.MCPClientState { return nil }, // Stub: no client found
nil, nil)
🤖 Prompt for AI Agents
In core/mcp/agent_test.go around lines 249 to 252, the test passes nil for
clientForToolFetcherFunc which causes a panic when processing tool calls;
replace the nil with a stub function matching the clientForToolFetcherFunc
signature that accepts a toolName (string) and returns (ToolClient, error) (or
the correct types used in code) and have it simply return nil, nil (or the zero
value and nil error) so tool call handling in the agent loop won't panic.

Comment on lines +683 to +686
// Create timeout context
toolCtx, cancel := context.WithTimeout(ctx, m.toolExecutionTimeout)
defer cancel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Nested timeout context may cause unexpected behavior.

The ctx parameter passed to callMCPTool is already a timeout context (timeoutCtx from line 459). Creating another WithTimeout on line 684 results in nested timeouts, where the effective timeout is min(remaining_parent_timeout, toolExecutionTimeout).

If this is intentional (to have per-tool timeouts within the overall execution timeout), consider documenting it. Otherwise, use the passed context directly.

-	// Create timeout context
-	toolCtx, cancel := context.WithTimeout(ctx, m.toolExecutionTimeout)
-	defer cancel()
-
-	toolResponse, callErr := client.Conn.CallTool(toolCtx, callRequest)
+	// Use the provided context (already has timeout from executeCode)
+	toolResponse, callErr := client.Conn.CallTool(ctx, callRequest)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Create timeout context
toolCtx, cancel := context.WithTimeout(ctx, m.toolExecutionTimeout)
defer cancel()
// Use the provided context (already has timeout from executeCode)
toolResponse, callErr := client.Conn.CallTool(ctx, callRequest)
🤖 Prompt for AI Agents
In core/mcp/codemode_executecode.go around lines 683-686, the function creates a
nested timeout context using context.WithTimeout(toolExecutionTimeout) while the
incoming ctx is already a timeout context, causing the effective timeout to be
min(parentRemaining, toolExecutionTimeout); to fix this either remove the inner
WithTimeout and use the passed ctx directly (replace toolCtx/cancel with ctx and
drop defer cancel), or if a per-tool timeout is intended, add a clear comment
above these lines documenting that behavior and why nested timeouts are
required; pick one approach and apply it consistently in this function.

Comment thread core/mcp/toolmanager.go
Comment on lines +14 to +27
type ToolsHandler struct {
toolExecutionTimeout time.Duration
maxAgentDepth int
fetchNewRequestIDFunc func(ctx context.Context) string
clientForToolFetcher func(toolName string) *schemas.MCPClientState
clientByNameFetcher func(clientName string) *schemas.MCPClientState
toolsFetcherFunc func(ctx context.Context) map[string][]schemas.ChatTool
logMu sync.Mutex // Protects concurrent access to logs slice
}

type ToolsHandlerConfig struct {
toolExecutionTimeout time.Duration
maxAgentDepth int
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

ToolsHandlerConfig fields are unexported, preventing external configuration.

The ToolsHandlerConfig struct has unexported fields (toolExecutionTimeout, maxAgentDepth), but it's used as a parameter in the public NewToolsHandler constructor. External callers cannot set these fields when instantiating the config.

Either export the fields or provide a builder/factory pattern:

 type ToolsHandlerConfig struct {
-	toolExecutionTimeout time.Duration
-	maxAgentDepth        int
+	ToolExecutionTimeout time.Duration
+	MaxAgentDepth        int
 }

And update NewToolsHandler accordingly:

 func NewToolsHandler(config *ToolsHandlerConfig) *ToolsHandler {
 	if config == nil {
 		config = &ToolsHandlerConfig{
-			toolExecutionTimeout: 30 * time.Second,
-			maxAgentDepth:        10,
+			ToolExecutionTimeout: 30 * time.Second,
+			MaxAgentDepth:        10,
 		}
 	}
-	if config.maxAgentDepth <= 0 {
-		config.maxAgentDepth = 10
+	if config.MaxAgentDepth <= 0 {
+		config.MaxAgentDepth = 10
 	}
-	if config.toolExecutionTimeout <= 0 {
-		config.toolExecutionTimeout = 30 * time.Second
+	if config.ToolExecutionTimeout <= 0 {
+		config.ToolExecutionTimeout = 30 * time.Second
 	}
 	return &ToolsHandler{
-		toolExecutionTimeout: config.toolExecutionTimeout,
-		maxAgentDepth:        config.maxAgentDepth,
+		toolExecutionTimeout: config.ToolExecutionTimeout,
+		maxAgentDepth:        config.MaxAgentDepth,
 	}
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In core/mcp/toolmanager.go around lines 14 to 27, the ToolsHandlerConfig struct
uses unexported fields so callers cannot configure it when calling
NewToolsHandler; change the API so callers can set config values by exporting
the fields (rename toolExecutionTimeout -> ToolExecutionTimeout and
maxAgentDepth -> MaxAgentDepth) or implement a constructor/builder that accepts
the desired values and maps them to the internal struct, and update
NewToolsHandler signature and usage to consume the exported fields or the new
builder so external code can supply configuration.

Comment thread core/mcp/toolmanager.go Outdated
Comment on lines +26 to +31
result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
// Should fail with runtime error
if bifrostErr == nil {
assertExecutionResult(t, result, false, nil, "runtime")
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent test pass when bifrostErr is non-nil.

The pattern if bifrostErr == nil { assertExecutionResult(...) } silently skips the assertion when bifrostErr is non-nil. If the expectation is that execution should succeed (no bifrostErr) but return a runtime error in the result, then bifrostErr != nil indicates an unexpected failure that should fail the test.

Consider asserting the expected behavior explicitly:

 	result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
-	// Should fail with runtime error
-	if bifrostErr == nil {
-		assertExecutionResult(t, result, false, nil, "runtime")
-	}
+	// Should succeed at the MCP level but return runtime error in result
+	requireNoBifrostError(t, bifrostErr)
+	require.NotNil(t, result)
+	assertExecutionResult(t, result, false, nil, "runtime")

Alternatively, if bifrostErr is expected to be non-nil in some cases, add an explicit assertion for that scenario.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
// Should fail with runtime error
if bifrostErr == nil {
assertExecutionResult(t, result, false, nil, "runtime")
}
}
result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
// Should succeed at the MCP level but return runtime error in result
requireNoBifrostError(t, bifrostErr)
require.NotNil(t, result)
assertExecutionResult(t, result, false, nil, "runtime")
}
🤖 Prompt for AI Agents
In tests/core-mcp/edge_cases_test.go around lines 26 to 31, the current
conditional `if bifrostErr == nil { assertExecutionResult(...) }` silently skips
the assertion when `bifrostErr` is non-nil; change this so the test explicitly
fails or asserts on bifrostErr: assert that bifrostErr is nil before calling
assertExecutionResult (and then call assertExecutionResult(t, result, false,
nil, "runtime")), or if a non-nil bifrostErr is an expected outcome for some
cases, add an explicit assertion for that path (e.g., assert bifrostErr != nil
and return) so the test does not silently pass.

Comment on lines +47 to +52
result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
// Should fail with runtime error
if bifrostErr == nil {
assertExecutionResult(t, result, false, nil, "runtime")
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same silent assertion skip pattern.

This test has the same issue as TestCodeModeClientCallingNonCodeModeTool. The assertion is skipped if bifrostErr is non-nil, potentially hiding test failures.

🤖 Prompt for AI Agents
In tests/core-mcp/edge_cases_test.go around lines 47 to 52, the test currently
skips the runtime assertion when bifrostErr is non-nil which can hide failures;
change the control flow so the assertion always runs (or explicitly fail the
test when bifrostErr != nil) — e.g., if bifrostErr != nil call t.Fatalf or
t.Errorf with the error, otherwise run assertExecutionResult(..., false, nil,
"runtime"); ensure there is no early skip that prevents the assertion from
executing.

Comment on lines +36 to +39
require.NotNil(t, result)

responseText := *result.Content.ContentStr
assert.Equal(t, "test message", responseText)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential nil pointer dereference on ContentStr.

If result.Content or result.Content.ContentStr is nil, this will panic. Consider adding a nil check before dereferencing.

 	requireNoBifrostError(t, bifrostErr)
 	require.NotNil(t, result)
+	require.NotNil(t, result.Content)
+	require.NotNil(t, result.Content.ContentStr)

 	responseText := *result.Content.ContentStr
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require.NotNil(t, result)
responseText := *result.Content.ContentStr
assert.Equal(t, "test message", responseText)
require.NotNil(t, result)
require.NotNil(t, result.Content)
require.NotNil(t, result.Content.ContentStr)
responseText := *result.Content.ContentStr
assert.Equal(t, "test message", responseText)
🤖 Prompt for AI Agents
In tests/core-mcp/tool_execution_test.go around lines 36 to 39, the test
dereferences result.Content.ContentStr without nil checks which can panic; add
explicit checks (e.g., require.NotNil for result, result.Content, and
result.Content.ContentStr) before dereferencing, then use the safe dereferenced
value for the assertion, or alternatively assert against a getter/zero value
that handles nils.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
core/mcp/codemode_readfile.go (2)

83-100: Unused availableFiles variable causes compile error - flagged in previous review.

The availableFiles slice declared at line 85-88 is allocated and populated but never used before returning. This will cause a Go compile error. As noted in the previous review, this variable should be removed since the error message is built by iterating over availableToolsPerClient directly.

 			if matchedClientName != "" {
 				// 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)

143-146: Error message prints pointer value instead of numeric endLine - flagged in previous review.

At line 145, endLine is *int and using %v will print the pointer address (or <nil>) rather than the numeric value. This was noted in the previous review.

+		var endVal interface{} = "<not specified>"
+		if endLine != nil {
+			endVal = *endLine
+		}
 		if start < 1 || start > totalLines {
-			errorMsg := fmt.Sprintf("Invalid startLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%v, totalLines=%d",
-				start, totalLines, start, endLine, totalLines)
+			errorMsg := fmt.Sprintf("Invalid startLine: %d. Must be between 1 and %d (total lines in file). Provided: startLine=%d, endLine=%v, totalLines=%d",
+				start, totalLines, start, endVal, totalLines)
 			return createToolResponseMessage(toolCall, errorMsg), nil
 		}
core/mcp/codemode_executecode.go (1)

387-482: Unsafe concurrent access to goja Runtime and Promises

The current integration uses goja from multiple goroutines:

  • vm.NewPromise() is called on the VM goroutine, but resolve(vm.ToValue(...)) / reject(vm.ToValue(...)) are invoked from a background goroutine in the tool proxy (Lines 441–476).
  • Those goroutines also call vm.ToValue to wrap errors and results.
  • Meanwhile, vm.RunString(wrappedCode) and subsequent Promise .then wiring (val.ToObject(vm), AssertFunction, etc.) run on the main goroutine (Lines 506–599).
  • callMCPTool runs independently and feeds results back into these goroutines.

goja’s Runtime is not goroutine‑safe; aside from Interrupt, all VM interactions (including ToValue, Promise creation, and resolving/rejecting Promises) must occur on a single goroutine. Calling resolve/reject and vm.ToValue from background goroutines while the VM is executing on another goroutine risks data races and VM corruption and will likely fail under go test -race.

You’ll need to rework this so that:

  • All operations that touch vm or JS values happen on a single goroutine (the “VM loop”).
  • Background goroutines for MCP calls communicate back via channels or a task queue, and the VM goroutine is solely responsible for:
    • Converting Go results/errors with vm.ToValue.
    • Invoking Promise resolve/reject.

A common pattern is:

  1. Run the goja Runtime on a dedicated goroutine (event loop).
  2. In the tool proxy:
    • Immediately create and return a Promise to JS (from the VM goroutine).
    • Kick off the MCP call on a Go goroutine.
    • When the goroutine completes, send the result/error on a channel.
    • On the VM goroutine, consume the channel and call resolve/reject there.

Alternatively, consider a per‑request Runtime or a small Runtime pool and keep each Runtime strictly single‑goroutine.

Until access is serialized like this, this code is not safe to ship in a concurrent environment.

Also applies to: 490-607, 645-718

🧹 Nitpick comments (17)
core/schemas/bifrost.go (1)

355-363: Confirm intentional wire-format change for extra_fields on BifrostError

Removing omitempty from ExtraFields means all BifrostError JSON payloads will now always include an extra_fields object, and because BifrostErrorExtraFields fields lack omitempty, they will appear with zero/empty values when unset. That’s a behavior change vs. “field absent when unused” and could affect existing consumers that distinguish between missing vs. present-but-empty.

If the goal is to guarantee the object’s presence for CodeMode and future clients, this is fine, but please double‑check any external consumers and schema/docs that might assume extra_fields can be omitted. Optionally, you might consider adding omitempty to inner fields if you want extra_fields present but as compact as possible.

core/go.mod (1)

11-12: CodeMode dependencies match the new JS/TS execution pipeline

Adding github.com/clarkmcc/go-typescript and github.com/dop251/goja (plus regexp2/sourcemap/pprof) is consistent with the new CodeMode VM and tooling requirements; the wiring looks fine from the module’s perspective. It’s worth double-checking these specific versions for compatibility and any known issues with your Go toolchain and sandboxing assumptions.

Also applies to: 40-42

framework/configstore/rdb.go (1)

667-677: MCP client IsCodeModeClient propagation and update behavior look correct

  • GetMCPConfig now surfaces IsCodeModeClient from TableMCPClient into schemas.MCPClientConfig, which is required for the new code-mode-aware tooling.
  • CreateMCPClientConfig correctly persists IsCodeModeClient alongside the existing MCP client fields and still routes env-var handling through substituteMCPClientEnvVars.
  • UpdateMCPClientConfig:
    • Copies IsCodeModeClient from the incoming config into existingClient.
    • Uses Select(...) on Updates(&existingClient) so the bool flag and JSON-backed fields are updated even when set to their zero values, which avoids the usual GORM “skip zero” pitfall.

One minor clarity tweak you might consider is using struct field names in Select (e.g., "Name", "IsCodeModeClient", "ToolsToExecuteJSON", ...) instead of column names to keep this resilient to future naming-strategy changes, but functionally this is fine with the current defaults.

Also applies to: 705-721, 747-761

tests/core-mcp/client_config_test.go (1)

1-337: Good coverage of MCP client config + code‑mode behavior, with a couple of minor tightening opportunities

This suite does a nice job validating:

  • IsCodeModeClient defaults and toggling via EditMCPClient (single/multiple/mixed clients).
  • Connection states from GetMCPClients.
  • ToolsToExecute semantics for empty, wildcard, and specific tool lists.
  • Persistence/round‑trip of config updates through the underlying store.

Two small polish suggestions:

  • In TestClientWithNoTools, you currently only assert clients is non‑nil; if the intention is to guarantee that bifrostInternal exists even without tools, consider also asserting len(clients) > 0 and/or that a client with Config.ID == "bifrostInternal" is present.
  • All the EditMCPClient calls pass partially populated MCPClientConfig values (only the fields being changed). That’s fine as long as EditMCPClient performs a merge rather than replacing the full config; if it ever changes semantics, these tests could inadvertently zero out other fields. If not already covered elsewhere, an explicit test around “editing one field does not clear others” could guard against regressions.

Overall, the tests align well with the new CodeMode config surface.

tests/core-mcp/agent_mode_test.go (1)

54-74: Consider adding verification of MaxAgentDepth configuration.

The test creates an MCP config with MaxAgentDepth: 2 but only asserts that clients exist. To properly verify the configuration was applied, consider adding assertions that validate the max depth setting was respected.

 	// Verify max depth is configured
 	clients, err := b.GetMCPClients()
 	require.NoError(t, err)
 	assert.NotNil(t, clients, "Should have clients")
+	
+	// TODO: Add assertion to verify MaxAgentDepth is applied
+	// This may require exposing the config or testing via agent execution behavior
tests/core-mcp/auto_execute_config_test.go (1)

32-38: Consider extracting client lookup to a helper function.

The pattern to find bifrostInternal client is repeated across all tests. Consider adding a helper in utils.go:

func findMCPClientByID(clients []schemas.MCPClient, clientID string) *schemas.MCPClient {
    for i := range clients {
        if clients[i].Config.ID == clientID {
            return &clients[i]
        }
    }
    return nil
}

This is a minor suggestion as the current approach is clear and works correctly.

tests/core-mcp/tool_execution_test.go (2)

99-115: Consider making test assertions more explicit.

The conditional if bifrostErr == nil && result != nil may silently pass if bifrostErr is non-nil or result is nil. If listToolFiles should succeed even with no clients, consider asserting explicitly:

-	// listToolFiles should still work but return empty/no servers message
-	if bifrostErr == nil && result != nil {
-		responseText := *result.Content.ContentStr
-		assert.Contains(t, responseText, "No servers", "Should indicate no servers")
-	}
+	// listToolFiles should still work but return empty/no servers message
+	requireNoBifrostError(t, bifrostErr)
+	require.NotNil(t, result)
+	responseText := *result.Content.ContentStr
+	assert.Contains(t, responseText, "No servers", "Should indicate no servers")

If the tool may legitimately fail in this scenario, add a comment explaining the expected behavior.


117-146: Same concern with conditional assertion.

Similar to the previous test, the conditional assertion on lines 138-145 may hide unexpected failures. Consider either:

  1. Asserting explicitly that the operation succeeds/fails.
  2. Adding a comment explaining why both outcomes are acceptable.
-	if bifrostErr == nil && result != nil {
-		responseText := *result.Content.ContentStr
-		// Should indicate no servers or empty list
-		assert.True(t,
-			len(responseText) == 0 ||
-				strings.Contains(responseText, "No servers") || strings.Contains(responseText, "servers/"),
-			"Should return empty or no servers message")
-	}
+	// When no code mode clients exist, listToolFiles may not be registered
+	// If it is called and succeeds, verify appropriate response
+	if bifrostErr != nil {
+		t.Logf("listToolFiles not available when no code mode clients: %v", bifrostErr)
+		return
+	}
+	require.NotNil(t, result)
+	responseText := *result.Content.ContentStr
+	assert.True(t,
+		len(responseText) == 0 ||
+			strings.Contains(responseText, "No servers") || strings.Contains(responseText, "servers/"),
+		"Should return empty or no servers message")
tests/core-mcp/codemode_auto_execute_test.go (1)

19-49: Consider extracting config fetch pattern to a helper.

The pattern for fetching current config (lines 20-29) is repeated in multiple tests. Consider extracting to a helper function:

func getClientConfig(t *testing.T, b *bifrost.Bifrost, clientID string) *schemas.MCPClientConfig {
	t.Helper()
	clients, err := b.GetMCPClients()
	require.NoError(t, err)
	for _, client := range clients {
		if client.Config.ID == clientID {
			return &client.Config
		}
	}
	t.Fatalf("client %s not found", clientID)
	return nil
}

This would reduce duplication across TestExecuteToolCodeWithAutoExecuteTool, TestExecuteToolCodeWithNonAutoExecuteTool, and TestExecuteToolCodeWithMixedAutoExecute.

core/mcp/utils.go (2)

378-406: Regex-based tool call extraction has limitations.

The regex pattern works for simple cases but may produce false positives:

  • Tool calls inside string literals: "serverName.toolName()"
  • Tool calls in comments: // serverName.toolName()
  • Method chaining: result.then() would match as server=result, tool=then

Since this is used for security validation (determining allowed tool calls), consider adding a comment documenting these limitations, or using the actual Goja AST for more accurate extraction.

 // extractToolCallsFromCode extracts tool calls from TypeScript code
 // Tool calls are in the format: serverName.toolName(...) or await serverName.toolName(...)
+// Note: This is a best-effort regex-based extraction that may have false positives
+// (e.g., method calls in strings/comments, chained method calls). False positives
+// are handled gracefully by downstream validation against known client names.
 func extractToolCallsFromCode(code string) ([]toolCallInfo, error) {

354-361: Minor: Potential inefficiency in hyphen check.

The check result.String()[result.Len()-1] != '-' creates a new string on each iteration. For better performance:

-		} else if unicode.IsSpace(r) || r == '-' {
-			// Replace spaces and existing hyphens with single hyphen
-			// Avoid consecutive hyphens
-			if result.Len() > 0 && result.String()[result.Len()-1] != '-' {
-				result.WriteRune('-')
-			}
+		} else if unicode.IsSpace(r) || r == '-' {
+			// Replace spaces and existing hyphens with single hyphen
+			// Avoid consecutive hyphens
+			str := result.String()
+			if len(str) > 0 && str[len(str)-1] != '-' {
+				result.WriteRune('-')
+			}

However, since tool names are typically short, this is a minor optimization.

tests/core-mcp/setup.go (1)

13-14: Consider reducing the test timeout.

A 10-minute timeout for MCP tests seems excessive. Most unit/integration tests should complete within seconds to a couple of minutes. Consider reducing to a more reasonable value (e.g., 2-3 minutes) unless there's a specific reason for such a long timeout, as it can slow down CI feedback loops when tests hang.

tests/core-mcp/mcp_connection_test.go (1)

207-230: Test name doesn't match behavior: tests execution time, not timeout.

The test TestToolExecutionTimeout doesn't actually test timeout behavior - it tests that a slow tool takes at least 100ms to execute. To test actual timeout behavior, you'd need to configure a short timeout and verify the tool execution is terminated/errors when exceeding it.

Consider either:

  1. Renaming to TestSlowToolExecution to match what it actually tests
  2. Adding a true timeout test that verifies execution is terminated when exceeding the configured ToolExecutionTimeout
tests/core-mcp/utils.go (1)

15-30: Silently ignoring JSON marshal error and weak ID generation.

Two issues in createToolCall:

  1. The json.Marshal error is silently ignored. If marshaling fails, tests will use malformed JSON.
  2. The ID is based on len(argsStr), which can cause collisions for different arguments with the same serialized length.
 func createToolCall(toolName string, arguments map[string]interface{}) schemas.ChatAssistantMessageToolCall {
-	argsJSON, _ := json.Marshal(arguments)
+	argsJSON, err := json.Marshal(arguments)
+	if err != nil {
+		panic(fmt.Sprintf("failed to marshal tool arguments: %v", err))
+	}
 	argsStr := string(argsJSON)
-	id := fmt.Sprintf("test-tool-call-%d", len(argsStr))
+	id := fmt.Sprintf("test-tool-call-%s-%d", toolName, time.Now().UnixNano())
 	toolType := "function"

Note: You'll need to add "time" to imports.

core/mcp/agent.go (1)

68-145: Complex nested logic could benefit from extraction into helper functions.

This code block handles three distinct code-mode tool types with deeply nested conditions. Consider extracting the ToolTypeExecuteToolCode handling (lines 73-144) into a separate helper function like handleExecuteToolCodeAutoExecution to improve readability and testability.

Additionally, the string replacement at line 93 (strings.ReplaceAll(code, "\\n", "\n")) converts escaped newlines but may cause issues if the JavaScript code legitimately contains the literal string "\\n" (e.g., in a regex or string comparison). This is likely acceptable for the code-mode use case, but worth documenting.

core/mcp/codemode_readfile.go (1)

224-236: Use sort.Strings instead of manual bubble sort.

The manual bubble sort implementation can be replaced with the standard library's sort.Strings for cleaner and more efficient code.

+	"sort"
...
 			// Sort properties for consistent output
 			propNames := make([]string, 0, len(props))
 			for name := range props {
 				propNames = append(propNames, name)
 			}
-			// Simple alphabetical sort
-			for i := 0; i < len(propNames)-1; i++ {
-				for j := i + 1; j < len(propNames); j++ {
-					if propNames[i] > propNames[j] {
-						propNames[i], propNames[j] = propNames[j], propNames[i]
-					}
-				}
-			}
+			sort.Strings(propNames)
core/mcp/toolmanager.go (1)

14-21: Defensive checks for callback setters to avoid nil panics

ToolsHandler assumes all injected callbacks (toolsFetcherFunc, clientForToolFetcher, fetchNewRequestIDFunc, clientByNameFetcher) are non‑nil when used in ParseAndAddToolsToRequest, ExecuteTool, ExecuteAgent, and buildAllowedAutoExecutionTools. If any setter is forgotten or wired late, these will panic with a nil function call.

Consider adding light defensive checks so failures are clearer and happen closer to construction:

  • In NewToolsHandler, optionally accept a fully-populated config or additional options and fail fast (e.g., return error) if required callbacks are missing.
  • Or, in the first use sites, detect nil and return a descriptive error / log.Fatalf rather than panicking on a nil call.

This keeps the current design but makes misconfiguration easier to diagnose.

Also applies to: 35-76, 78-186, 201-295, 301-345

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0750d39 and 511c8c9.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • tests/core-mcp/go.sum is excluded by !**/*.sum
📒 Files selected for processing (34)
  • core/bifrost.go (1 hunks)
  • core/chatbot_test.go (2 hunks)
  • core/go.mod (2 hunks)
  • core/mcp/agent.go (7 hunks)
  • core/mcp/agent_test.go (2 hunks)
  • core/mcp/clientmanager.go (2 hunks)
  • core/mcp/codemode_executecode.go (1 hunks)
  • core/mcp/codemode_listfiles.go (1 hunks)
  • core/mcp/codemode_readfile.go (1 hunks)
  • core/mcp/mcp.go (3 hunks)
  • core/mcp/toolmanager.go (1 hunks)
  • core/mcp/utils.go (6 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mcp.go (1 hunks)
  • core/schemas/utils.go (1 hunks)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (3 hunks)
  • framework/configstore/tables/mcp.go (1 hunks)
  • framework/configstore/utils.go (1 hunks)
  • tests/core-mcp/README.md (1 hunks)
  • tests/core-mcp/agent_mode_test.go (1 hunks)
  • tests/core-mcp/auto_execute_config_test.go (1 hunks)
  • tests/core-mcp/client_config_test.go (1 hunks)
  • tests/core-mcp/codemode_auto_execute_test.go (1 hunks)
  • tests/core-mcp/edge_cases_test.go (1 hunks)
  • tests/core-mcp/fixtures.go (1 hunks)
  • tests/core-mcp/go.mod (1 hunks)
  • tests/core-mcp/integration_test.go (1 hunks)
  • tests/core-mcp/mcp_connection_test.go (1 hunks)
  • tests/core-mcp/setup.go (1 hunks)
  • tests/core-mcp/tool_execution_test.go (1 hunks)
  • tests/core-mcp/utils.go (1 hunks)
  • transports/bifrost-http/handlers/mcp.go (1 hunks)
  • transports/bifrost-http/lib/config.go (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • core/bifrost.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/core-mcp/README.md
  • transports/bifrost-http/handlers/mcp.go
  • core/mcp/codemode_listfiles.go
  • core/schemas/utils.go
  • core/mcp/mcp.go
  • transports/bifrost-http/lib/config.go
🧰 Additional context used
🧬 Code graph analysis (15)
tests/core-mcp/agent_mode_test.go (3)
tests/core-mcp/setup.go (1)
  • TestTimeout (14-14)
core/schemas/mcp.go (3)
  • MCPClientConfig (32-54)
  • MCPClient (102-106)
  • MCPConfig (19-29)
ui/lib/types/mcp.ts (2)
  • MCPClientConfig (13-23)
  • MCPClient (25-29)
core/mcp/utils.go (3)
core/mcp/mcp.go (1)
  • MCPManager (41-56)
transports/bifrost-http/handlers/mcp.go (1)
  • MCPManager (19-23)
core/schemas/chatcompletions.go (1)
  • ChatTool (201-205)
core/mcp/codemode_readfile.go (4)
core/mcp/toolmanager.go (1)
  • ToolsHandler (14-22)
core/schemas/chatcompletions.go (4)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/mcp/mcp.go (1)
  • MCPLogPrefix (23-23)
tests/core-mcp/auto_execute_config_test.go (1)
core/schemas/mcp.go (2)
  • MCPClientConfig (32-54)
  • MCPClient (102-106)
tests/core-mcp/setup.go (3)
core/schemas/account.go (2)
  • Key (8-17)
  • Account (55-71)
core/schemas/mcp.go (2)
  • MCPConfig (19-29)
  • MCPClientConfig (32-54)
core/schemas/chatcompletions.go (4)
  • ChatTool (201-205)
  • ChatToolTypeFunction (196-196)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
core/mcp/toolmanager.go (4)
core/schemas/mcp.go (1)
  • MCPClientState (83-90)
core/schemas/chatcompletions.go (5)
  • ChatTool (201-205)
  • ChatParameters (154-183)
  • ChatAssistantMessageToolCall (483-489)
  • BifrostChatRequest (11-18)
  • BifrostChatResponse (25-40)
core/mcp/mcp.go (1)
  • MCPLogPrefix (23-23)
core/schemas/responses.go (1)
  • ResponsesParameters (84-111)
framework/configstore/rdb.go (1)
framework/configstore/tables/mcp.go (2)
  • TableMCPClient (12-31)
  • TableMCPClient (34-34)
framework/configstore/utils.go (3)
core/schemas/mcp.go (1)
  • MCPClientConfig (32-54)
ui/lib/types/mcp.ts (1)
  • MCPClientConfig (13-23)
framework/configstore/clientconfig.go (1)
  • EnvKeyInfo (20-26)
tests/core-mcp/client_config_test.go (4)
tests/core-mcp/setup.go (1)
  • TestTimeout (14-14)
core/schemas/mcp.go (3)
  • MCPClient (102-106)
  • MCPConnectionStateConnected (76-76)
  • MCPClientConfig (32-54)
ui/lib/types/mcp.ts (2)
  • MCPClient (25-29)
  • MCPClientConfig (13-23)
transports/bifrost-http/lib/config.go (1)
  • Config (140-170)
tests/core-mcp/tool_execution_test.go (4)
tests/core-mcp/setup.go (1)
  • TestTimeout (14-14)
core/schemas/mcp.go (1)
  • MCPClientConfig (32-54)
ui/lib/types/mcp.ts (1)
  • MCPClientConfig (13-23)
tests/core-mcp/fixtures.go (1)
  • CodeFixtures (4-298)
core/mcp/agent_test.go (1)
core/mcp/agent.go (1)
  • ExecuteAgent (14-267)
framework/configstore/migrations.go (2)
framework/migrator/migrator.go (3)
  • New (131-149)
  • DefaultOptions (100-106)
  • Migration (62-69)
framework/configstore/tables/mcp.go (2)
  • TableMCPClient (12-31)
  • TableMCPClient (34-34)
core/mcp/agent.go (4)
core/schemas/chatcompletions.go (2)
  • ChatTool (201-205)
  • BifrostResponseChoice (501-509)
core/mcp/toolmanager.go (3)
  • ToolTypeListToolFiles (30-30)
  • ToolTypeReadToolFile (31-31)
  • ToolTypeExecuteToolCode (32-32)
core/mcp/codemode_executecode.go (1)
  • CodeModeLogPrefix (60-60)
core/providers/gemini/types.go (1)
  • FinishReason (18-18)
core/mcp/codemode_executecode.go (4)
core/mcp/toolmanager.go (2)
  • ToolsHandler (14-22)
  • ToolTypeExecuteToolCode (32-32)
core/schemas/chatcompletions.go (4)
  • ChatTool (201-205)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatAssistantMessageToolCall (483-489)
core/schemas/utils.go (1)
  • Ptr (14-16)
core/mcp/mcp.go (1)
  • MCPLogPrefix (23-23)
core/mcp/clientmanager.go (1)
core/mcp/mcp.go (2)
  • BifrostMCPClientKey (22-22)
  • BifrostMCPClientName (21-21)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (53)
core/schemas/mcp.go (1)

35-54: IsCodeModeClient flag addition looks consistent

The new IsCodeModeClient field on MCPClientConfig is straightforward and lines up with the DB/table and UI types; default false behavior is reasonable for non-code-mode clients.

core/mcp/agent_test.go (1)

181-181: ExecuteAgent callsites match the extended signature; validate nil handler expectations

The updated calls pass nil for the new handler/fetcher parameters in the correct order, which is appropriate for these tests that don’t exercise tool execution paths. Just ensure ExecuteAgent defensively handles nil handlers in any branches that could be reached here (e.g., when tool calls are present), so future test changes don’t accidentally panic.

Also applies to: 252-252

framework/configstore/tables/mcp.go (1)

16-31: DB persistence for IsCodeModeClient is wired correctly

Adding IsCodeModeClient with gorm:"default:false" cleanly extends TableMCPClient to persist the new code-mode flag and aligns with the core schema.

core/chatbot_test.go (2)

518-520: Simplified tool-call and content-block checks are idiomatic

Using len(assistantMessage.ToolCalls) > 0 and len(assistantMessage.Content.ContentBlocks) > 0 relies on Go’s len(nil)==0 behavior and removes redundant nil checks without changing semantics; this is a nice cleanup given the surrounding assumptions about non-nil Content.

Also applies to: 523-527


636-637: Synthesis request Input construction remains unchanged semantically

The Input: conversationWithSynthesis assignment is consistent with the prior behavior of appending the synthesis prompt; no issues here.

tests/core-mcp/go.mod (1)

1-63: Test module wiring for core‑mcp looks appropriate

Defining a separate tests/core-mcp module with a replace to the local core module and adding testify matches the intended usage pattern for these integration tests; the indirect dependencies are consistent with the core module’s set.

framework/configstore/migrations.go (2)

82-84: LGTM!

The new migration call is correctly placed in the migration sequence, after migrationAddToolsToAutoExecuteJSONColumn and before migrationAddLogRetentionDaysColumn, maintaining logical ordering for the code-mode feature additions.


1083-1115: LGTM!

The migration implementation is well-structured:

  • Correctly checks for column existence before adding
  • Initializes existing rows with a sensible default (false)
  • Provides a clean rollback by dropping the column
  • Follows the established migration pattern used throughout the file

The implementation aligns with the TableMCPClient struct definition which has IsCodeModeClient bool with gorm:"default:false".

tests/core-mcp/integration_test.go (5)

12-48: LGTM!

This test provides good end-to-end coverage of the code mode workflow:

  1. Lists available tool files
  2. Reads TypeScript definitions
  3. Executes code using discovered tools

The test properly validates each step's output and uses appropriate fixtures.


50-83: LGTM!

Good test coverage for mixed auto-execute configurations. The test correctly validates that tools in ToolsToAutoExecute are auto-executable while those not listed are not.


85-118: LGTM!

Good test for verifying tool filtering with specific ToolsToExecute configuration. Correctly asserts that only configured tools (echo, add) are in the execution list.


120-147: LGTM!

The comment on lines 144-147 helpfully documents the expected behavior: code mode clients expose tools via executeToolCode rather than directly. This clarifies the intentional design.


149-220: LGTM!

Good coverage of various code execution scenarios:

  • Multiple tool calls with chaining
  • Error handling within code
  • Async/await patterns
  • Long-running code execution

All tests properly use fixtures and assertion helpers.

tests/core-mcp/agent_mode_test.go (1)

12-52: LGTM!

Good test for agent mode configuration, verifying the auto-execute semantics:

  • echo is correctly identified as auto-executable
  • add and multiply are correctly identified as not auto-executable

The comment on lines 12-14 appropriately notes that full agent mode flow testing requires LLM integration.

tests/core-mcp/auto_execute_config_test.go (6)

12-44: LGTM!

Good test validating that a tool in ToolsToExecute but not in ToolsToAutoExecute is correctly identified as not auto-executable.


46-78: LGTM!

Correctly verifies that a tool present in both ToolsToExecute and ToolsToAutoExecute is auto-executable.


80-111: LGTM!

Important edge case test: verifies that a tool in ToolsToAutoExecute but not in ToolsToExecute is correctly rejected, matching the documented semantics in MCPClientConfig.


113-145: LGTM!

Good test for wildcard behavior in both ToolsToExecute and ToolsToAutoExecute.


147-216: LGTM!

Good coverage of empty and nil cases for ToolsToAutoExecute. The conditional check on line 210-215 properly handles both nil and normalized-to-empty scenarios.


218-322: LGTM!

Comprehensive tests for:

  • Mixed auto-execute configurations with multiple tools
  • Empty ToolsToExecute (deny-by-default)
  • Nil ToolsToExecute (treated as empty)

These tests validate the documented semantics that auto-execution requires the tool to also be in ToolsToExecute.

tests/core-mcp/edge_cases_test.go (9)

12-31: LGTM!

Good edge case test for calling non-code mode client tools from code mode context. The conditional check on line 28-30 properly handles both error return paths.


33-52: LGTM!

Good test for attempting to call a non-existent client from code execution context.


54-79: LGTM!

Important test verifying that tools not in ToolsToExecute cannot be executed, enforcing the deny-by-default security model.


81-127: LGTM!

Good coverage of timeout behavior and error propagation:

  • Slow tool test verifies completion within timeout
  • Error tool test verifies error message is propagated in result content

129-145: LGTM!

Good test for empty code handling. Correctly expects an error with a helpful message about the required code parameter.


147-191: LGTM!

Good test coverage for syntax and TypeScript compilation errors. The comment on lines 186-188 appropriately notes that TypeScript type errors may not be caught if type checking is disabled, which is acceptable behavior.


193-231: LGTM!

Good tests for runtime errors and invalid tool arguments. The conditional error checking pattern properly handles both error return paths.


233-258: LGTM!

Good test verifying that code mode tools (listToolFiles, readToolFile) are always executable regardless of auto-execute configuration. The comment on lines 251-253 explains the testing limitation.


260-297: LGTM!

Good edge case tests for:

  • Comments-only code (should execute and return null)
  • Undefined variable reference (should fail with runtime error)

These complete the error handling coverage for the code execution feature.

framework/configstore/utils.go (2)

186-202: LGTM on the redacted header restoration logic.

The pre-substitution step correctly handles the case where the UI sends back redacted headers (containing ****) that aren't environment variables. The logic properly:

  1. Checks if the value is redacted and not an env var reference.
  2. Restores the original value only if it existed and wasn't an env var.

This prevents data loss when editing MCP client configurations through the UI.


208-243: Good improvement using ID-based client matching.

Switching from name-based to ID-based matching is more robust since IDs are unique identifiers. The header substitution logic correctly handles all three cases:

  • Existing env.VAR references are updated to the correct env var.
  • Redacted values (****) are restored to env.VAR format.
  • Plain values are preserved unchanged.
core/mcp/clientmanager.go (2)

150-158: LGTM on IsCodeModeClient propagation.

The IsCodeModeClient flag is now correctly propagated when editing a client, ensuring the code-mode configuration is preserved across edits.


556-570: Good initialization of internal client ExecutionConfig.

Setting explicit ID, Name, and ToolsToExecute: []string{"*"} for the internal Bifrost client ensures:

  1. The internal client has a proper identity for per-client tool organization.
  2. All locally registered tools are accessible to the internal client.

This aligns with the code-mode tooling requirements where tools are organized per-client.

tests/core-mcp/tool_execution_test.go (1)

1-40: Good test structure and coverage.

The test file follows good practices:

  • Proper context management with timeout and defer cancel.
  • Clear test naming convention.
  • Use of shared test helpers for setup and assertions.
  • Coverage of both code-mode and non-code-mode execution paths.
tests/core-mcp/codemode_auto_execute_test.go (2)

149-193: Good test coverage for meta-tool behavior.

The tests correctly verify that listToolFiles and readToolFile cannot be called from within executeToolCode and expect a runtime error. The comments clearly explain the expected behavior.


195-233: LGTM on undefined server/tool tests.

The conditional assertions here are appropriate since the tests are specifically verifying failure behavior for undefined servers and tools. The tests correctly check for runtime errors.

core/mcp/utils.go (3)

30-80: Good refactor to per-client tool organization.

The change from []schemas.ChatTool to map[string][]schemas.ChatTool enables the code-mode feature to organize tools by client name, which is essential for the serverName.toolName() invocation pattern in the JavaScript sandbox.


324-376: Solid implementation of parseToolName.

The function handles edge cases well:

  • Empty string returns empty.
  • Invalid first character gets underscore prefix.
  • Spaces and hyphens are normalized.
  • Trailing hyphens are trimmed.
  • Fallback to "tool" for edge cases.

One minor note: the lowercase conversion may be surprising if tool names are expected to preserve case. Verify this is the intended behavior for JavaScript compatibility.


408-434: Verify the "unknown server returns true" behavior.

On line 411-414, when serverName is not in allClientNames, the function returns true with the comment that it might be a built-in JS object. This means:

  1. Legitimate JS method calls (console.log, JSON.parse, result.then) correctly pass through.
  2. Typos in server names will also pass through and fail at runtime.

This is reasonable fail-open behavior for usability, but ensure the downstream Goja execution properly handles the runtime error case with a clear message. The comment is helpful in explaining this design choice.

tests/core-mcp/setup.go (2)

16-38: LGTM!

The TestAccount implementation correctly satisfies the schemas.Account interface with appropriate default configurations for testing.


408-448: LGTM!

The connectExternalMCP helper properly handles HTTP and SSE connection types, checks for existing clients to avoid duplicates, and returns appropriate errors for unsupported connection types.

tests/core-mcp/mcp_connection_test.go (3)

77-97: LGTM!

The tool discovery tests properly verify both listToolFiles and readToolFile functionality, checking for expected content in the responses including server listings and TypeScript interface definitions.


232-256: LGTM!

Good test coverage for tool error handling. The test correctly verifies that tool execution errors are captured in the result content rather than returned as a BifrostError, which aligns with the expected behavior for tool-level failures.


258-285: LGTM!

The complex arguments test properly verifies that nested object structures are correctly passed through and processed by the tool execution pipeline.

tests/core-mcp/utils.go (2)

152-187: Verify assertion for finish_reason when all tools are auto-execute.

The logic at lines 182-186 asserts finish_reason should be "tool_calls" when all tools are auto-execute and expectedNonAutoExecuteCount is 0. However, if the agent loop completes normally (no more tool calls), the finish reason might not be "tool_calls". The condition if choice.FinishReason != nil makes this assertion optional, which may silently pass in cases where verification should occur.

Consider adding a comment explaining when this branch is expected to be hit, or making the assertion more explicit about expected conditions.


253-273: LGTM!

The canAutoExecuteTool function correctly implements the semantics documented in the MCPClientConfig struct: nil/empty ToolsToExecute means no tools allowed, wildcard * allows all, and then checks ToolsToAutoExecute for auto-execution permission.

core/mcp/agent.go (4)

162-163: LGTM!

Good addition of debug logging for the counts of auto-executable and non-auto-executable tools, which aids in debugging agent mode behavior.


277-281: LGTM - Intentional design for non-auto-executable tool handling.

The logic to return false for finish_reason == "stop" is correct. This ensures the agent loop exits when non-auto-executable tools are returned, preventing infinite loops. The comment clearly explains the purpose.


427-437: LGTM - Good documentation of the finish_reason design decision.

The comment block (lines 428-432) clearly explains why finish_reason is set to "stop" instead of "tool_calls" for non-auto-executable tools, which is crucial for understanding the agent loop termination behavior.


451-499: LGTM!

The buildAllowedAutoExecutionTools function correctly:

  1. Filters to only code-mode clients
  2. Handles the wildcard * case for auto-executable tools
  3. Uses parseToolName for consistent tool name formatting
  4. Returns both the client names list and the per-client allowed tools map
core/mcp/codemode_readfile.go (2)

298-333: LGTM!

The jsonSchemaToTypeScript function handles the common JSON Schema types appropriately, including primitives, arrays with item types, objects, null, and enums. Defaulting to any for unknown types is a reasonable fallback.


335-353: Edge case: toPascalCase lowercases subsequent characters.

The implementation at line 346 (strings.ToUpper(part[:1]) + strings.ToLower(part[1:])) will lowercase characters after the first one in each part. This could incorrectly transform acronyms like "API" → "Api" or "HTTPClient" → "Httpclient". If tool names contain acronyms, this may produce unexpected TypeScript interface names.

Consider whether this behavior is intentional or if you want to preserve existing casing for acronyms.

tests/core-mcp/fixtures.go (1)

3-311: Fixtures and expected results look consistent with the execution pipeline

The fixture set is comprehensive and the ExpectedResults types (notably the numeric float64 values) line up with how executeCode and callMCPTool export JSON/JS numbers from goja. I don’t see issues here from a behavior or maintainability standpoint.

Comment on lines +746 to +776
// stripImportsAndExports strips import and export statements from code
func (m *ToolsHandler) stripImportsAndExports(code string) (string, []int) {
lines := strings.Split(code, "\n")
keptLines := []string{}
strippedLineNumbers := []int{}

importExportRegex := regexp.MustCompile(`^\s*(import|export)\b`)

for i, line := range lines {
trimmed := strings.TrimSpace(line)

// Skip empty lines
if trimmed == "" {
keptLines = append(keptLines, line)
continue
}

// Check if this is an import or export statement
isImportOrExport := importExportRegex.MatchString(line)

if isImportOrExport {
strippedLineNumbers = append(strippedLineNumbers, i+1) // 1-based line numbers
continue // Skip import/export lines
}

// Keep comment lines and all other non-import/export lines
keptLines = append(keptLines, line)
}

return strings.Join(keptLines, "\n"), strippedLineNumbers
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Import/export stripping drops same-line executable code

stripImportsAndExports removes an entire line whenever it starts with import or export:

importExportRegex := regexp.MustCompile(`^\s*(import|export)\b`)
...
if isImportOrExport {
    strippedLineNumbers = append(strippedLineNumbers, i+1)
    continue
}

This means snippets like the ImportStatement fixture:

import { something } from "module"; return 1 + 1

are stripped entirely, including the return 1 + 1 part, so executeCode sees empty code and treats it as “no data”.

If you intend to support “import + code” on the same line (as the fixtures suggest), consider narrowing the stripping logic, e.g.:

  • Only strip up to the first semicolon on import/export lines and keep any trailing code; or
  • Require imports/exports to appear on their own lines and reflect that in docs/fixtures.

Right now this behavior is surprising and makes those fixtures effectively no‑ops.

🤖 Prompt for AI Agents
In core/mcp/codemode_executecode.go around lines 746-776, the current logic
drops an entire line whenever it begins with import/export, which removes any
same-line executable code (e.g., `import ...; return 1+1`). Change the stripping
so only the import/export statement is removed and any trailing code on the same
line is preserved: detect and remove the leading import/export clause (for
example using a regex that matches `^\s*(import|export)\b[^;]*;?\s*` and
replaces it with empty string) or split on the first semicolon and keep the
remainder; append the modified non-empty remainder to keptLines (and only record
the line number in strippedLineNumbers when an import/export fragment was
removed), ensuring blank or comment-only results are handled the same as before.

Comment thread tests/core-mcp/setup.go
Comment on lines +244 to +260
if err := b.RegisterMCPTool("add", "Adds two numbers", func(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid args type")
}
a, ok := argsMap["a"].(float64)
if !ok {
return "", fmt.Errorf("a field is required")
}
b, ok := argsMap["b"].(float64)
if !ok {
return "", fmt.Errorf("b field is required")
}
return fmt.Sprintf("%.0f", a+b), nil
}, addSchema); err != nil {
return fmt.Errorf("failed to register add tool: %w", err)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Variable shadowing: b shadows the function parameter.

The variable b on line 253 shadows the function parameter b *bifrost.Bifrost from line 244. This is a common source of bugs where the outer b is unintentionally not used for subsequent operations.

-		b, ok := argsMap["b"].(float64)
+		bVal, ok := argsMap["b"].(float64)
 		if !ok {
 			return "", fmt.Errorf("b field is required")
 		}
-		return fmt.Sprintf("%.0f", a+b), nil
+		return fmt.Sprintf("%.0f", a+bVal), nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := b.RegisterMCPTool("add", "Adds two numbers", func(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid args type")
}
a, ok := argsMap["a"].(float64)
if !ok {
return "", fmt.Errorf("a field is required")
}
b, ok := argsMap["b"].(float64)
if !ok {
return "", fmt.Errorf("b field is required")
}
return fmt.Sprintf("%.0f", a+b), nil
}, addSchema); err != nil {
return fmt.Errorf("failed to register add tool: %w", err)
}
if err := b.RegisterMCPTool("add", "Adds two numbers", func(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid args type")
}
a, ok := argsMap["a"].(float64)
if !ok {
return "", fmt.Errorf("a field is required")
}
bVal, ok := argsMap["b"].(float64)
if !ok {
return "", fmt.Errorf("b field is required")
}
return fmt.Sprintf("%.0f", a+bVal), nil
}, addSchema); err != nil {
return fmt.Errorf("failed to register add tool: %w", err)
}
🤖 Prompt for AI Agents
In tests/core-mcp/setup.go around lines 244 to 260 the local variable named `b`
inside the tool handler shadows the function parameter `b *bifrost.Bifrost`;
rename the inner variable to a non-conflicting name (e.g., bVal or bNum) and
update all uses in the handler to that name so the outer `b` parameter remains
accessible and no shadowing occurs.

Comment thread tests/core-mcp/setup.go
Comment on lines +284 to +300
if err := b.RegisterMCPTool("multiply", "Multiplies two numbers", func(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid args type")
}
a, ok := argsMap["a"].(float64)
if !ok {
return "", fmt.Errorf("a field is required")
}
b, ok := argsMap["b"].(float64)
if !ok {
return "", fmt.Errorf("b field is required")
}
return fmt.Sprintf("%.0f", a*b), nil
}, multiplySchema); err != nil {
return fmt.Errorf("failed to register multiply tool: %w", err)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Variable shadowing: b shadows the function parameter again.

Same issue as the add tool - the variable b on line 293 shadows the function parameter.

-		b, ok := argsMap["b"].(float64)
+		bVal, ok := argsMap["b"].(float64)
 		if !ok {
 			return "", fmt.Errorf("b field is required")
 		}
-		return fmt.Sprintf("%.0f", a*b), nil
+		return fmt.Sprintf("%.0f", a*bVal), nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := b.RegisterMCPTool("multiply", "Multiplies two numbers", func(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid args type")
}
a, ok := argsMap["a"].(float64)
if !ok {
return "", fmt.Errorf("a field is required")
}
b, ok := argsMap["b"].(float64)
if !ok {
return "", fmt.Errorf("b field is required")
}
return fmt.Sprintf("%.0f", a*b), nil
}, multiplySchema); err != nil {
return fmt.Errorf("failed to register multiply tool: %w", err)
}
if err := b.RegisterMCPTool("multiply", "Multiplies two numbers", func(args any) (string, error) {
argsMap, ok := args.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid args type")
}
a, ok := argsMap["a"].(float64)
if !ok {
return "", fmt.Errorf("a field is required")
}
bVal, ok := argsMap["b"].(float64)
if !ok {
return "", fmt.Errorf("b field is required")
}
return fmt.Sprintf("%.0f", a*bVal), nil
}, multiplySchema); err != nil {
return fmt.Errorf("failed to register multiply tool: %w", err)
}
🤖 Prompt for AI Agents
In tests/core-mcp/setup.go around lines 284 to 300 the anonymous tool handler
uses a local variable named `b` which shadows the function parameter `b`; rename
the local variable to a non-conflicting name (e.g., `bVal` or `bArg`) and update
all uses and error messages accordingly so the parameter is not shadowed and the
code compiles and behaves as intended.

@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from 11-18-feat_mcp_core_refactor_and_agent_mode_added to graphite-base/903 November 25, 2025 14:51
@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from graphite-base/903 to 11-18-feat_mcp_core_refactor_and_agent_mode_added November 25, 2025 14:57
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 11-18-feat_added_codemode_to_mcp branch from 511c8c9 to 786f7f4 Compare November 25, 2025 17:04
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui/app/workspace/mcp-gateway/views/mcpClientsTable.tsx (1)

90-95: Fix getConnectionDisplay to prevent runtime error and ensure fallback works correctly

The code has two issues:

  1. Runtime error: When stdio_config is undefined, the optional chaining stops at stdio_config?.args (returning undefined), then .join(" ") is called on that undefined value, causing a thrown error.

  2. Broken fallback: The template string ${undefined} ${undefined} becomes the string "undefined undefined", and any non-empty string is truthy, so || "STDIO" never executes.

Apply the proposed fix to safely access both properties and call .join() with optional chaining, then use the fallback only when both are missing.

♻️ Duplicate comments (6)
core/mcp/codemode_executecode.go (2)

242-268: Import/export stripping still drops same-line executable code

stripImportsAndExports currently removes entire lines that start with import or export:

importExportRegex := regexp.MustCompile(`^\s*(import|export)\b`)
...
if isImportOrExport {
    strippedLineNumbers = append(strippedLineNumbers, i+1)
    continue // Skip import/export lines
}

This means code like:

import { something } from "module"; return 1 + 1;

is stripped entirely, including return 1 + 1, so after preprocessing trimmedCode can become empty and the actual logic never runs (or is treated as “no data”).

If you want to support “import + code” on the same line, consider stripping only the import/export fragment and preserving any trailing code, e.g.:

-		isImportOrExport := importExportRegex.MatchString(line)
-
-		if isImportOrExport {
-			strippedLineNumbers = append(strippedLineNumbers, i+1)
-			continue
-		}
+		if loc := importExportRegex.FindStringIndex(line); loc != nil {
+			strippedLineNumbers = append(strippedLineNumbers, i+1)
+			rest := strings.TrimSpace(line[loc[1]:])
+			if rest == "" {
+				continue
+			}
+			keptLines = append(keptLines, rest)
+			continue
+		}

(Adjust exact regex and trimming as needed.)

Also applies to: 748-778


417-482: Unsafe concurrent use of goja vm from multiple goroutines

The server bindings resolve/reject Promises and call vm.ToValue from background goroutines while the same vm is executing RunString and Promise then handlers:

  • vm.NewPromise() and vm.ToValue(...) (Lines 429, 437, 438, 447, 455, 472, 475, 478)
  • Goroutine around h.callMCPTool(...) with reject(vm.ToValue(...)) / resolve(vm.ToValue(...)) (Lines 442–476)
  • Concurrent main goroutine running vm.RunString(wrappedCode) and Promise introspection/handlers (Lines 508–607)

goja.Runtime is not goroutine-safe; aside from Interrupt, all interactions with a Runtime (including ToValue, Promise resolve/reject, property access, etc.) must be serialized on a single goroutine. Calling these from multiple goroutines risks data races, panics, and VM corruption.

You’ll need to restructure so that:

  • All vm operations (including creating Promises and calling resolve/reject) happen on one dedicated VM goroutine/event loop.
  • Background MCP tool calls communicate results back via channels, and the VM goroutine then invokes resolve/reject inside the JS event loop.

A typical pattern is:

  • Run the VM on a dedicated goroutine (or use goja’s event-loop helpers).
  • From the stub, immediately create a Promise and return it to JS, but schedule any future resolve/reject calls onto the VM goroutine (e.g., via a task queue processed by that goroutine).
  • Background goroutines performing MCP calls only send plain Go data back over channels; no direct vm.* calls.
Is `goja.Runtime` (including methods like `ToValue` and Promise `resolve`/`reject`) safe to use from multiple goroutines concurrently, and what patterns are recommended for integrating async operations and Promises with goja?

Also applies to: 492-607

tests/core-mcp/edge_cases_test.go (1)

26-31: Avoid silently skipping assertions when bifrostErr is non-nil

These tests only assert on the runtime result when bifrostErr == nil, which means they will silently pass if ExecuteMCPTool returns a non-nil bifrostErr (the very failure these cases aim to catch):

  • TestCodeModeClientCallingNonCodeModeClientTool (Lines 26–31)
  • TestNonCodeModeClientToolCalledFromExecuteToolCode (Lines 47–52)
  • TestCodeWithRuntimeErrors (Lines 205–210)
  • TestCodeCallingToolsWithInvalidArguments (Lines 226–230)
  • TestUndefinedVariableError (Lines 292–296)

Prefer making the expectation explicit, e.g.:

-	result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
-	// Should fail with runtime error
-	if bifrostErr == nil {
-		assertExecutionResult(t, result, false, nil, "runtime")
-	}
+	result, bifrostErr := b.ExecuteMCPTool(ctx, toolCall)
+	// Should succeed at MCP layer and surface a runtime error in the result
+	requireNoBifrostError(t, bifrostErr)
+	require.NotNil(t, result)
+	assertExecutionResult(t, result, false, nil, "runtime")

or, if a non-nil bifrostErr is actually the expected outcome in a specific test, assert that explicitly and return early.

Also applies to: 47-52, 205-210, 226-230, 292-296

tests/core-mcp/fixtures.go (1)

300-311: Type mismatch concern already flagged.

The AddResult and MultiplyResult fields are typed as float64, but the corresponding test tools return formatted strings. This was already noted in a previous review.

core/mcp/toolmanager.go (2)

28-31: Unexported fields in ToolsHandlerConfig prevent external configuration.

This was flagged in a previous review. The struct has unexported fields that external callers cannot set.


272-283: Passing pointer to context (&ctx) is an anti-pattern.

This was flagged in a previous review. Contexts should be passed by value, not pointer.

🧹 Nitpick comments (9)
ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

50-164: Review the request body reconstruction logic for edge cases.

The copyRequestBody function reconstructs OpenAI-compatible request bodies from log entries. While the implementation is comprehensive, consider these points:

  1. Embedding input handling (lines 123-134): The logic extracts texts from all messages and uses a single string if only one text is found, otherwise an array. Verify this matches OpenAI's embedding API expectations.

  2. Error handling: The function silently returns on unsupported request types or missing data. Consider adding more specific error messages to help users understand why copying failed.

  3. Tools/instructions handling (lines 149-156): Tools and instructions are only added for specific request types. Ensure this aligns with OpenAI schema requirements for all supported request types.

Consider these refinements:

  // Skip if not a supported request type
  if (!isChat && !isResponses && !isSpeech && !isTextCompletion && !isEmbedding) {
-   toast.error("Copy request body is only available for chat, responses, speech, text completion, and embedding requests");
+   toast.error(`Copy request body is not available for ${object} requests`);
    return;
  }

Additionally, for embedding input extraction, verify the API accepts both single strings and arrays based on OpenAI's documentation.

tests/core-mcp/utils.go (1)

214-251: Review the JSON extraction logic for robustness.

The extractExecutedToolResults function extracts JSON from content by finding the first { and last }. This approach may fail if:

  1. The content contains multiple JSON objects
  2. The content has unbalanced braces in non-JSON text

Consider making the extraction more robust:

  // Try to extract JSON from content
  // Content format: "The Output from allowed tools calls is - {...}\n\nNow I shall call these tools next..."
  jsonStart := strings.Index(content, "{")
  if jsonStart == -1 {
    return nil
  }

- jsonEnd := strings.LastIndex(content, "}")
+ // Find matching closing brace for the first opening brace
+ braceCount := 0
+ jsonEnd := -1
+ for i := jsonStart; i < len(content); i++ {
+   if content[i] == '{' {
+     braceCount++
+   } else if content[i] == '}' {
+     braceCount--
+     if braceCount == 0 {
+       jsonEnd = i
+       break
+     }
+   }
+ }
  if jsonEnd == -1 || jsonEnd < jsonStart {
    return nil
  }

However, if the content format is well-defined and controlled by the codebase, the current implementation may be sufficient for test purposes.

tests/core-mcp/integration_test.go (1)

120-147: Consider adding an explicit assertion for the expected behavior.

The comment on lines 144-147 explains that code mode clients don't expose direct tools, but there's no assertion verifying this behavior. Adding an explicit check would strengthen the test.

Consider adding an assertion to verify the expected behavior:

 	// Code mode tools should be available
 	listCall := createToolCall("listToolFiles", map[string]interface{}{})
 	result, bifrostErr := b.ExecuteMCPTool(ctx, listCall)
 	requireNoBifrostError(t, bifrostErr)
 	require.NotNil(t, result)
 
 	// Direct tools should also be available (when not in code mode)
 	// But when client is code mode, direct tools are not exposed
 	// This is expected behavior - code mode clients expose tools via executeToolCode
+
+	// Verify direct tool calls are not available for code mode clients
+	directCall := createToolCall("echo", map[string]interface{}{"message": "test"})
+	_, directErr := b.ExecuteMCPTool(ctx, directCall)
+	assert.NotNil(t, directErr, "Direct tool calls should not be available for code mode clients")
core/mcp/codemode_listfiles.go (1)

44-56: Make listToolFiles output deterministic by sorting server keys

availableToolsPerClient is a map, so iterating with for clientName := range availableToolsPerClient will produce non-deterministic ordering of servers/<name>.d.ts, which can lead to flaky tests and confusing UX.

Consider collecting the keys, sorting them, and then appending to treeLines:

-	treeLines := []string{"servers/"}
-	for clientName := range availableToolsPerClient {
+	treeLines := []string{"servers/"}
+	clientNames := make([]string, 0, len(availableToolsPerClient))
+	for clientName := range availableToolsPerClient {
+		clientNames = append(clientNames, clientName)
+	}
+	sort.Strings(clientNames)
+	for _, clientName := range clientNames {
 		client := h.clientManager.GetClientByName(clientName)

You’ll need to import sort.

tests/core-mcp/agent_mode_test.go (1)

54-74: Strengthen MaxAgentDepth test with a behavior-based assertion

TestAgentModeMaxDepthConfiguration only checks that clients are non-nil, not that MaxAgentDepth: 2 is actually enforced. If feasible, consider adding a small agent-mode flow that intentionally exceeds depth and asserting it’s rejected, so regressions in depth handling are caught.

ui/app/workspace/mcp-gateway/views/mcpClientsTable.tsx (1)

156-160: Align Enabled/Auto-execute counts and empty-state layout with tool semantics

A couple of minor UI/semantics points:

  1. Counts vs. configuration semantics

enabledToolsCount / autoExecuteToolsCount currently use list lengths and * without considering that ToolsToExecute gates ToolsToAutoExecute (e.g., ToolsToExecute: [], ToolsToAutoExecute: ["*"] will still display tools.length as auto-executable, even though none actually run). If you want the UI to reflect effective behavior, consider computing counts as:

  • Intersection of tools with ToolsToExecute, and
  • Intersection of that set with ToolsToAutoExecute.
  1. Empty state colSpan

The “No clients found” row uses colSpan={5}, but the header defines 8 columns; bumping colSpan to 8 will center the message across the full table width.

These are purely UX/clarity improvements; behavior is otherwise correct.

Also applies to: 164-179, 198-215

tests/core-mcp/mcp_connection_test.go (1)

207-230: Test verifies delay but not timeout behavior.

The test name TestToolExecutionTimeout suggests testing timeout behavior (early termination), but it only verifies that a slow tool takes at least 100ms. Consider adding a test case where the context deadline is shorter than the tool delay to verify proper timeout handling:

func TestToolExecutionTimeoutActualTimeout(t *testing.T) {
	// Create a short timeout context
	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel()

	// ...setup...

	// Call slow_tool with delay longer than context timeout
	slowCall := createToolCall("slow_tool", map[string]interface{}{
		"delay_ms": float64(500),
	})
	_, bifrostErr := b.ExecuteMCPTool(ctx, slowCall)
	// Expect timeout error
	require.NotNil(t, bifrostErr)
}
core/mcp/utils.go (1)

378-383: Inefficient last-character check creates unnecessary string copy.

result.String()[result.Len()-1] creates a full string copy just to check the last character. Consider tracking the last written rune separately for efficiency:

+	var lastRune rune
 	for i := 1; i < len(runes); i++ {
 		r := runes[i]
 		if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '$' {
 			result.WriteRune(unicode.ToLower(r))
+			lastRune = unicode.ToLower(r)
 		} else if unicode.IsSpace(r) || r == '-' {
-			if result.Len() > 0 && result.String()[result.Len()-1] != '-' {
+			if result.Len() > 0 && lastRune != '-' {
 				result.WriteRune('-')
+				lastRune = '-'
 			}
 		}
 	}
core/mcp/toolmanager.go (1)

206-222: O(n×m) tool availability check could be optimized.

The nested loops iterate through all clients and their tools to check if a tool is available. For codebases with many tools, consider building a lookup map once:

-		availableTools := h.clientManager.GetToolPerClient(ctx)
-		toolFound := false
-		for _, tools := range availableTools {
-			for _, mcpTool := range tools {
-				if mcpTool.Function != nil && mcpTool.Function.Name == toolName {
-					toolFound = true
-					break
-				}
-			}
-			if toolFound {
-				break
-			}
-		}
+		// GetClientForTool already performs this lookup efficiently
+		client := h.clientManager.GetClientForTool(toolName)
+		toolFound := client != nil

However, the current implementation also validates against context-filtered tools, which GetClientForTool may not account for. Verify if the context filtering is intentional here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 511c8c9 and 786f7f4.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • tests/core-mcp/go.sum is excluded by !**/*.sum
📒 Files selected for processing (44)
  • core/bifrost.go (1 hunks)
  • core/chatbot_test.go (2 hunks)
  • core/go.mod (2 hunks)
  • core/mcp/agent.go (7 hunks)
  • core/mcp/clientmanager.go (2 hunks)
  • core/mcp/codemode_executecode.go (1 hunks)
  • core/mcp/codemode_listfiles.go (1 hunks)
  • core/mcp/codemode_readfile.go (1 hunks)
  • core/mcp/default_toolmanager.go (0 hunks)
  • core/mcp/mcp.go (3 hunks)
  • core/mcp/toolmanager.go (1 hunks)
  • core/mcp/utils.go (9 hunks)
  • core/schemas/bifrost.go (1 hunks)
  • core/schemas/mcp.go (1 hunks)
  • core/schemas/utils.go (1 hunks)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (3 hunks)
  • framework/configstore/tables/mcp.go (1 hunks)
  • framework/configstore/utils.go (1 hunks)
  • tests/core-mcp/README.md (1 hunks)
  • tests/core-mcp/agent_mode_test.go (1 hunks)
  • tests/core-mcp/auto_execute_config_test.go (1 hunks)
  • tests/core-mcp/client_config_test.go (1 hunks)
  • tests/core-mcp/codemode_auto_execute_test.go (1 hunks)
  • tests/core-mcp/edge_cases_test.go (1 hunks)
  • tests/core-mcp/fixtures.go (1 hunks)
  • tests/core-mcp/go.mod (1 hunks)
  • tests/core-mcp/integration_test.go (1 hunks)
  • tests/core-mcp/mcp_connection_test.go (1 hunks)
  • tests/core-mcp/setup.go (1 hunks)
  • tests/core-mcp/tool_execution_test.go (1 hunks)
  • tests/core-mcp/utils.go (1 hunks)
  • transports/bifrost-http/handlers/mcp.go (1 hunks)
  • transports/bifrost-http/lib/config.go (3 hunks)
  • ui/app/workspace/logs/views/logChatMessageView.tsx (1 hunks)
  • ui/app/workspace/logs/views/logDetailsSheet.tsx (4 hunks)
  • ui/app/workspace/mcp-gateway/page.tsx (1 hunks)
  • ui/app/workspace/mcp-gateway/views/mcpClientForm.tsx (8 hunks)
  • ui/app/workspace/mcp-gateway/views/mcpClientSheet.tsx (6 hunks)
  • ui/app/workspace/mcp-gateway/views/mcpClientsTable.tsx (3 hunks)
  • ui/components/sidebar.tsx (1 hunks)
  • ui/components/ui/headersTable.tsx (1 hunks)
  • ui/lib/types/mcp.ts (3 hunks)
  • ui/lib/types/schemas.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • core/mcp/default_toolmanager.go
✅ Files skipped from review due to trivial changes (1)
  • tests/core-mcp/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • tests/core-mcp/client_config_test.go
  • tests/core-mcp/setup.go
  • framework/configstore/tables/mcp.go
  • core/bifrost.go
  • core/go.mod
  • tests/core-mcp/go.mod
  • tests/core-mcp/tool_execution_test.go
  • ui/app/workspace/logs/views/logChatMessageView.tsx
  • tests/core-mcp/codemode_auto_execute_test.go
  • core/mcp/clientmanager.go
  • core/chatbot_test.go
  • core/mcp/codemode_readfile.go
  • ui/components/ui/headersTable.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/handlers/utils.go (2)
  • SendError (35-44)
  • SendJSON (16-22)
framework/configstore/migrations.go (2)
framework/migrator/migrator.go (3)
  • New (131-149)
  • DefaultOptions (100-106)
  • Migration (62-69)
framework/configstore/tables/mcp.go (2)
  • TableMCPClient (12-31)
  • TableMCPClient (34-34)
tests/core-mcp/utils.go (3)
core/schemas/chatcompletions.go (4)
  • ChatAssistantMessageToolCall (483-489)
  • ChatAssistantMessageToolCallFunction (492-495)
  • ChatNonStreamResponseChoice (524-527)
  • ChatAssistantMessage (460-464)
core/schemas/bifrost.go (1)
  • BifrostError (354-363)
core/schemas/mcp.go (1)
  • MCPClientConfig (32-54)
core/mcp/agent.go (3)
core/mcp/toolmanager.go (4)
  • ClientManager (14-18)
  • ToolTypeListToolFiles (34-34)
  • ToolTypeReadToolFile (35-35)
  • ToolTypeExecuteToolCode (36-36)
core/mcp/codemode_executecode.go (1)
  • CodeModeLogPrefix (60-60)
core/schemas/chatcompletions.go (1)
  • BifrostResponseChoice (501-509)
tests/core-mcp/auto_execute_config_test.go (3)
tests/core-mcp/setup.go (1)
  • TestTimeout (14-14)
core/schemas/mcp.go (2)
  • MCPClientConfig (32-54)
  • MCPClient (102-106)
ui/lib/types/mcp.ts (2)
  • MCPClientConfig (13-23)
  • MCPClient (25-29)
framework/configstore/rdb.go (1)
framework/configstore/tables/mcp.go (2)
  • TableMCPClient (12-31)
  • TableMCPClient (34-34)
framework/configstore/utils.go (3)
core/schemas/mcp.go (1)
  • MCPClientConfig (32-54)
ui/lib/types/mcp.ts (1)
  • MCPClientConfig (13-23)
framework/configstore/clientconfig.go (1)
  • EnvKeyInfo (20-26)
transports/bifrost-http/lib/config.go (1)
core/schemas/mcp.go (1)
  • MCPConfig (19-29)
core/schemas/utils.go (4)
core/schemas/chatcompletions.go (6)
  • ChatTool (201-205)
  • ChatToolFunction (208-213)
  • ToolFunctionParameters (216-223)
  • ChatToolCustom (225-227)
  • ChatToolCustomFormat (229-232)
  • ChatToolCustomGrammarFormat (235-238)
core/providers/gemini/types.go (1)
  • Type (772-772)
ui/lib/types/logs.ts (1)
  • Function (141-146)
transports/bifrost-http/lib/config.go (1)
  • DeepCopy (2999-3007)
ui/app/workspace/mcp-gateway/views/mcpClientsTable.tsx (2)
core/schemas/mcp.go (1)
  • MCPClient (102-106)
ui/lib/constants/config.ts (1)
  • MCP_STATUS_COLORS (57-61)
ui/app/workspace/mcp-gateway/views/mcpClientSheet.tsx (4)
npx/bin.js (1)
  • data (172-172)
ui/components/ui/sheet.tsx (1)
  • SheetDescription (90-90)
ui/components/ui/form.tsx (3)
  • FormItem (161-161)
  • FormLabel (162-162)
  • FormControl (163-163)
ui/components/ui/switch.tsx (1)
  • Switch (36-36)
core/mcp/mcp.go (1)
core/mcp/toolmanager.go (3)
  • ToolsHandler (20-26)
  • NewToolsHandler (39-60)
  • ToolsHandlerConfig (28-31)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (42)
ui/components/sidebar.tsx (1)

356-357: ✓ URL migration verified—all references properly updated.

Verification confirms the sidebar navigation update is clean and complete:

  • Old URL path /workspace/mcp-clients has been fully removed from the codebase
  • New URL /workspace/mcp-gateway is properly referenced in the sidebar and frontend page structure exists
  • Backend API routes (/api/mcp/clients, /v1/mcp/tool/execute) remain unchanged and functional
  • No orphaned references or broken navigation paths detected

The changes are approved and safe to merge.

framework/configstore/migrations.go (2)

82-84: LGTM! Migration wiring is correct.

The migration is properly wired into the sequence after migrationAddToolsToAutoExecuteJSONColumn and before migrationAddLogRetentionDaysColumn, with proper error propagation.


1083-1116: LGTM! Migration implementation follows best practices.

The migration correctly:

  • Checks for column existence before adding
  • Initializes existing rows with the default value (false)
  • Provides a complete rollback implementation
  • Uses proper error wrapping with context

The initialization SQL handles NULL values appropriately, ensuring all existing MCP clients are marked as non-code-mode by default.

ui/app/workspace/logs/views/logDetailsSheet.tsx (2)

178-215: LGTM! UI enhancements improve usability.

The tooltip-wrapped copy button and reorganized action buttons provide clear functionality. The layout groups related actions (copy and delete) together in a logical manner.


431-431: LGTM! Dynamic tool count improves clarity.

Displaying the number of tools in the header (e.g., "Tools (3)") provides immediate context about the number of tools used in the request.

core/schemas/utils.go (1)

703-786: LGTM! Deep copy implementation is thorough and correct.

The DeepCopyChatTool function correctly implements deep copying for all nested structures:

  • Function, Parameters, and their nested fields
  • Properties map using the generic DeepCopy helper
  • Custom format with nested Grammar
  • All pointer fields with proper nil checks
  • Slice fields with proper allocation and copying

This follows the established pattern from DeepCopyChatMessage and ensures no shared references between copies, which is essential for preventing mutation issues in plugin accumulators.

framework/configstore/utils.go (3)

186-202: LGTM! Header restoration logic is well-designed.

The new existingHeaders parameter and restoration logic correctly handles the case where the UI sends back redacted headers (containing ****) for plain (non-env) values. The implementation:

  • Only restores when the current value is redacted and not already an env reference
  • Checks that the old value wasn't an env reference before restoring
  • Properly handles the nil case for existingHeaders

This ensures accurate header values are available before environment variable substitution occurs.


228-238: LGTM! Three-way header substitution logic is correct.

The enhanced header substitution properly handles three cases:

  1. Already env.VAR: Updates to the correct environment variable from envKeys
  2. Redacted (****): Restores to env.VAR format (handles UI sending redacted env vars)
  3. Plain value: Leaves unchanged (allows plain header values alongside env vars)

This provides flexibility while maintaining proper environment variable substitution for both new and existing configs.


209-240: Change verified: ID-based client matching is consistently implemented.

Verification confirms:

  1. Callers provide populated ID fields: ID is auto-generated if empty (bifrost-http/lib/config.go:1947-1948: if clientConfig.ID == "" { clientConfig.ID = uuid.NewString() }), ensuring all configs passed to substituteMCPClientEnvVars have populated IDs.

  2. Environment variable records use client IDs: All ConfigPath construction throughout the codebase uses the ID-based format (e.g., mcp.client_configs.%s.connection_string with clientConfig.ID). No Name-based ConfigPath patterns found anywhere, confirming existing envKey records are already using client IDs.

  3. Consistent matching logic: Both creation and lookup use ID matching—bifrost-http file (line 1922) and utils.go target file (line 214) both compare against clientConfig.ID.

tests/core-mcp/utils.go (5)

15-30: LGTM! Tool call creation helper is straightforward.

The createToolCall function correctly constructs a ChatAssistantMessageToolCall with proper JSON encoding of arguments and required fields.


32-60: LGTM! Execution result assertions are comprehensive.

The assertExecutionResult function properly validates both success and error cases:

  • Success: checks for absence of error indicators while allowing console.error output
  • Failure: checks for error presence and optional error kind
  • Log validation when expected logs are provided

128-138: LGTM! Custom error assertion improves test readability.

The requireNoBifrostError helper provides better error messages by using GetErrorMessage to format the error details, making test failures easier to diagnose.


152-187: LGTM! Agent mode response assertions are thorough.

The assertAgentModeResponse function correctly validates:

  • Executed tool results in message content when tools were executed
  • Tool calls for non-auto-execute tools
  • Finish reason: "stop" when non-auto-execute tools present, "tool_calls" when all tools auto-execute

This properly captures the agent mode execution flow semantics.


253-283: LGTM! Tool execution helpers implement correct logic.

The canAutoExecuteTool and contains helpers correctly implement:

  • Two-stage validation: first check ToolsToExecute (allowed list), then ToolsToAutoExecute
  • Wildcard handling for both lists
  • Proper nil and empty list semantics (deny-by-default)
transports/bifrost-http/handlers/mcp.go (1)

197-202: LGTM! Messaging changes improve clarity.

The updated error and success messages ("connect" instead of "add") better reflect the operation semantics. MCP clients are being connected/disconnected rather than simply added/removed, which aligns with:

  • The reconnectMCPClient handler messaging (line 155-156)
  • The connection lifecycle nature of MCP clients (connection state, reconnection support)

This improves consistency and user understanding of the operation.

ui/app/workspace/mcp-gateway/page.tsx (1)

3-3: LGTM! Import path updated correctly.

The import path change from mcp-clients to mcp-gateway aligns with the directory structure reorganization for the MCP Gateway feature. The component usage remains unchanged.

core/schemas/bifrost.go (1)

362-362: The review comment is based on an incorrect understanding of Go's JSON behavior.

The removal of omitempty from a non-pointer struct field has no practical effect on JSON serialization. According to Go's JSON encoding specification, omitempty only omits fields with values considered "empty" (nil pointers, zero integers, empty strings, etc.), but struct types are never considered empty. A struct field will always serialize as an object regardless of whether omitempty is present or absent.

Since BifrostErrorExtraFields is a non-pointer struct (confirmed by struct definition at line 421-425), the omitempty tag would never have had any effect on serialization. The field serializes regardless of the tag.

The current code already has no omitempty on this field (line 362), matching the HEAD version, so no change occurred here.

However, there is a legitimate API concern: Multiple BifrostError constructions in the codebase (e.g., tests/scripts/1millogs/main.go:546, plugins/governance/main.go:341,382,393,404) don't populate ExtraFields, leaving it as a zero-valued struct. These will serialize as {"provider":"","model_requested":"","request_type":""}. If this breaks client expectations, consider either:

  1. Making ExtraFields a pointer type (*BifrostErrorExtraFields) so omitempty can actually work, or
  2. Ensuring all BifrostError constructions populate this field appropriately.

Likely an incorrect or invalid review comment.

ui/lib/types/schemas.ts (1)

576-578: LGTM! The new is_code_mode_client field is correctly added.

The optional boolean field aligns with the backend schema and other UI type definitions.

core/schemas/mcp.go (1)

35-35: LGTM! The IsCodeModeClient field is properly added to MCPClientConfig.

The field uses correct Go naming conventions and the JSON tag aligns with the frontend expectations.

ui/lib/types/mcp.ts (1)

16-16: LGTM! The is_code_mode_client field additions are consistent across all interfaces.

The optional boolean field is correctly added to MCPClientConfig, CreateMCPClientRequest, and UpdateMCPClientRequest, maintaining alignment with the backend schema.

transports/bifrost-http/lib/config.go (2)

2145-2149: LGTM! Proper propagation of IsCodeModeClient during MCP client edits.

The in-memory config update correctly includes the new IsCodeModeClient field alongside other editable fields like Name, Headers, ToolsToExecute, and ToolsToAutoExecute.


2183-2191: LGTM! IsCodeModeClient correctly included in redacted config output.

The field is properly preserved when creating redacted copies of MCP client configurations for API responses.

ui/app/workspace/mcp-gateway/views/mcpClientSheet.tsx (2)

220-231: LGTM! Well-structured Code Mode Client toggle.

The FormField implementation correctly:

  • Uses the Switch component with proper value binding
  • Handles undefined values with || false fallback
  • Follows the same pattern as other toggle fields in the codebase

332-335: LGTM! The conditional refactor maintains equivalent logic.

The single-line expression is cleaner while preserving the same behavior for determining auto-execute state.

ui/app/workspace/mcp-gateway/views/mcpClientForm.tsx (2)

62-67: LGTM! Type signature correctly extended for boolean values.

The handleChange function now properly accepts boolean in its union type, enabling the Code Mode Client switch to work correctly.


205-212: LGTM! Code Mode Client switch is well-implemented.

The switch control:

  • Uses proper layout with border and padding matching the sheet component
  • Correctly binds to the form state with fallback handling
  • Follows the same pattern as mcpClientSheet.tsx for consistency
tests/core-mcp/integration_test.go (2)

12-48: LGTM! Comprehensive end-to-end workflow test.

The test properly validates the complete code mode workflow:

  1. Listing available tool files
  2. Reading tool file contents (TypeScript definitions)
  3. Executing code using discovered tools

Good use of assertions to verify each step.


186-202: Dismiss the review comment — async/await is transpiled to Promises before Goja execution.

The code in core/mcp/codemode_executecode.go demonstrates that async/await syntax is supported and automatically transpiled to Promise chains compatible with goja. The TypeScript compiler is configured to downlevel async/await to Promise chains (ES5 compatible) before code execution.

The AsyncAwaitTest fixture correctly uses async/await syntax because it is converted to Promise chains during transpilation (via typescript.TranspileString()). Goja never encounters the raw async/await syntax—it only receives the transpiled Promise-based code, which it fully supports. The test expectations are correct, and no adjustment is needed.

Likely an incorrect or invalid review comment.

tests/core-mcp/auto_execute_config_test.go (1)

12-322: Comprehensive coverage of ToolsToExecute/ToolsToAutoExecute semantics

These tests nicely cover the key matrix of configurations (empty vs nil, wildcard, overlap, and mismatches) and align with the documented semantics on MCPClientConfig. No issues from my side here.

framework/configstore/rdb.go (1)

667-677: IsCodeModeClient persistence and mapping look correct

The read/create/update paths for IsCodeModeClient are wired consistently:

  • GetMCPConfig maps dbClient.IsCodeModeClient onto schemas.MCPClientConfig.
  • CreateMCPClientConfig sets IsCodeModeClient on the DB row after env-var substitution.
  • UpdateMCPClientConfig updates existingClient.IsCodeModeClient and uses an explicit Select(...) so false (zero value) is not skipped by GORM.

This should keep the flag round-tripping correctly between API, DB, and MCP runtime.

Also applies to: 697-727, 729-766

core/mcp/mcp.go (1)

18-32: MCPManager → ToolsHandler delegation looks clean and consistent

The refactor to construct ToolsHandler via NewToolsHandler and have MCPManager delegate AddToolsToRequest, ExecuteTool, and CheckAndExecuteAgent keeps concerns nicely separated. The new BifrostMCPClientKey = "bifrostInternal" also lines up with the test usage.

No issues spotted here.

Also applies to: 41-56, 75-110, 112-135

tests/core-mcp/mcp_connection_test.go (1)

1-11: LGTM! Comprehensive MCP integration test suite.

The test file provides good coverage of MCP functionality including initialization, tool registration, discovery, execution, error handling, and edge cases. The use of helper functions (setupTestBifrost, createToolCall, requireNoBifrostError) keeps tests clean and maintainable.

tests/core-mcp/fixtures.go (1)

1-77: Well-organized test fixtures structure.

The CodeFixtures struct provides comprehensive categorized test cases covering basic expressions, MCP tool calls, import/export handling, expression analysis, error cases, edge cases, environment tests, auto-execute validation, and agent mode scenarios. This organization makes it easy to add new test cases and understand coverage.

core/mcp/utils.go (3)

401-429: Regex-based tool call extraction may miss edge cases.

The regex pattern (?:await\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\.\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\( correctly matches basic patterns like await BifrostClient.echo( and Server.tool(, but may produce false positives for:

  1. Method calls on non-server objects (e.g., console.log(, Math.max()
  2. Chained calls (e.g., result.then( after a promise)
  3. Comments containing patterns (e.g., // BifrostClient.echo()

The current implementation handles this at line 434-437 in isToolCallAllowedForCodeMode by allowing calls on unknown server names, which is a reasonable approach.


17-28: LGTM! Clean client lookup implementation.

The GetClientForTool method correctly uses read lock for thread-safe access and iterates through clients to find the one containing the specified tool.


347-399: The review comment is incorrect. Tool names are not broken by case-lowering because the original case is preserved.

The code stores tool names in a two-part pattern: the parsed (lowercase) name is used as a JavaScript property accessor key in the toolFunctions map, while the original tool name is preserved in the toolBinding struct. When tools are executed, the lookup uses the lowercase key to retrieve the binding, which then uses the original name for the actual MCP call—so case-sensitivity is maintained where it matters. Tool name comparisons throughout the codebase use the original names, not the parsed ones.

Likely an incorrect or invalid review comment.

core/mcp/agent.go (3)

66-143: Code-mode validation logic is thorough and security-conscious.

The implementation properly:

  1. Allows listToolFiles and readToolFile without validation (safe read-only operations)
  2. Parses executeToolCode arguments to extract embedded tool calls
  3. Validates each extracted tool call against the allowed auto-execution tools map
  4. Rejects execution if any tool call is not in the allowed list

This aligns well with the security sandbox constraints mentioned in the PR objectives.


275-279: Finish reason "stop" check prevents infinite agent loops.

The change to return false when finish_reason is "stop" properly prevents the agent loop from re-processing non-auto-executable tool calls. This works in conjunction with the logic at lines 426-430 that sets finish_reason to "stop" for responses containing non-auto-executable tools.


449-497: buildAllowedAutoExecutionTools correctly separates client names from code-mode filtering.

The function returns both allClientNames (all clients) and allowedTools (only code-mode clients with auto-execute tools). This distinction is important because:

  1. allClientNames is used to distinguish server names from built-in JS objects
  2. allowedTools is used to validate auto-execution permissions

One minor observation: if availableToolsPerClient is empty, both returned values will be empty, which is handled correctly at line 110-112.

core/mcp/toolmanager.go (3)

76-101: LGTM! Code-mode tool inclusion logic is well-structured.

The implementation correctly:

  1. Iterates through clients to identify code-mode clients
  2. Excludes code-mode client tools from the regular tool list
  3. Adds the three code-mode meta-tools (listToolFiles, readToolFile, executeToolCode) when any code-mode client is present

190-270: Tool execution dispatch handles all cases appropriately.

The ExecuteTool method properly:

  1. Dispatches code-mode tools to their respective handlers
  2. Validates tool availability against context-filtered tools
  3. Uses timeout context for MCP calls
  4. Handles timeout errors distinctly from other call errors

20-26: Review comment is incorrect—logMu is actively used.

The logMu field is used in core/mcp/codemode_executecode.go (lines 392–393) where it's locked and unlocked for synchronization. Removing it would break the concurrent access protection already in place. The field should be kept.

Likely an incorrect or invalid review comment.

Comment on lines +77 to +83
result, bifrostErr := b.ExecuteMCPTool(ctx, listToolCall)
requireNoBifrostError(t, bifrostErr)
require.NotNil(t, result)

responseText := *result.Content.ContentStr
assert.Contains(t, responseText, "servers/", "Should list servers")
assert.Contains(t, responseText, "BifrostClient.d.ts", "Should list BifrostClient server")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential nil pointer dereference on ContentStr.

The code dereferences result.Content.ContentStr directly without checking if ContentStr is nil. While requireNoBifrostError and require.NotNil(t, result) verify the result exists, the ContentStr pointer itself could still be nil if the response uses ContentBlocks instead.

Consider adding a nil check:

+	require.NotNil(t, result.Content)
+	require.NotNil(t, result.Content.ContentStr, "Expected ContentStr to be non-nil")
	responseText := *result.Content.ContentStr

This pattern applies to similar usages at lines 93, 118, 130, 142, 166, 253, and 282.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result, bifrostErr := b.ExecuteMCPTool(ctx, listToolCall)
requireNoBifrostError(t, bifrostErr)
require.NotNil(t, result)
responseText := *result.Content.ContentStr
assert.Contains(t, responseText, "servers/", "Should list servers")
assert.Contains(t, responseText, "BifrostClient.d.ts", "Should list BifrostClient server")
result, bifrostErr := b.ExecuteMCPTool(ctx, listToolCall)
requireNoBifrostError(t, bifrostErr)
require.NotNil(t, result)
require.NotNil(t, result.Content)
require.NotNil(t, result.Content.ContentStr, "Expected ContentStr to be non-nil")
responseText := *result.Content.ContentStr
assert.Contains(t, responseText, "servers/", "Should list servers")
assert.Contains(t, responseText, "BifrostClient.d.ts", "Should list BifrostClient server")
🤖 Prompt for AI Agents
In tests/core-mcp/mcp_connection_test.go around lines 77 to 83 (and similarly at
lines 93, 118, 130, 142, 166, 253, 282), the test dereferences
result.Content.ContentStr without verifying Content or ContentStr are non-nil;
update the test to first assert result.Content is not nil and then check if
result.Content.ContentStr != nil before dereferencing, and if ContentStr is nil
fall back to inspecting result.Content.ContentBlocks (or fail the test with a
clear message) so you avoid a nil pointer dereference and correctly handle
responses that use ContentBlocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant