Skip to content

mcp tool groups#1464

Closed
akshaydeo wants to merge 1 commit intographite-base/1464from
01-28-mcp_tool_groups
Closed

mcp tool groups#1464
akshaydeo wants to merge 1 commit intographite-base/1464from
01-28-mcp_tool_groups

Conversation

@akshaydeo
Copy link
Copy Markdown
Contributor

@akshaydeo akshaydeo commented Jan 27, 2026

Summary

Refactor the MCP (Model Control Protocol) manager to use an interface instead of a concrete implementation, enabling enterprise extensions and custom implementations. Also add new UI components for MCP server and tool selection.

Changes

  • Converted mcpManager field in Bifrost struct to McpManager (exported) with interface type
  • Created new MCPManagerInterface in core/mcp/interface.go to define the contract
  • Updated all references to use the interface methods instead of direct implementation
  • Added new UI components for MCP integration:
    • EntityAssociationSelect: Generic component for selecting entities with metadata
    • MCPServerSelector: Component for selecting MCP servers with status indicators
    • MCPToolSelector: Component for selecting and viewing MCP tools with parameter schemas

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Test the interface implementation by verifying core functionality works as before:

# Core/Transports
go version
go test ./core/mcp/...
go test ./core/...

# UI
cd ui
pnpm i
pnpm build

For UI components, create a test page that uses the new selectors with mock data.

Breaking changes

  • Yes
  • No

Related issues

Enables enterprise extensions to the MCP system while maintaining compatibility.

Security considerations

No security implications as this is a refactoring of existing functionality with new UI components.

Checklist

  • I added/updated tests where appropriate
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 27, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added entity association selector for teams, customers, users, providers, and API keys
    • Introduced MCP server selector for browsing and selecting connected servers
    • Added MCP tool selector with searchable discovery and multi-select across servers
  • Behavior Changes

    • Tool read operation now prepends total line count, clamps line ranges, and warns about pagination
    • Docs retrieval tool may be auto-executed in certain agent flows
  • Documentation

    • Clarified tool signatures and added notes about truncated descriptions and full docs retrieval

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

Walkthrough

Replaces a concrete MCP manager with a new exported interface and updates all usages; adds three new React UI selectors (entity, MCP server, MCP tool); adjusts MCP agent auto-execution set and readToolFile behavior; adds MCP-related types and a new API tag.

Changes

Cohort / File(s) Summary
MCP manager refactor
core/bifrost.go, core/mcp/interface.go
Replaced private concrete *mcp.MCPManager field with exported MCPManager mcp.MCPManagerInterface; added MCPManagerInterface and compile-time assertion; updated initialization, setter signature, nil-checks, and all MCP calls to use the interface.
MCP agent behavior
core/mcp/agent.go
Expanded auto-executable tools set to include the docs tool (ToolTypeGetToolDocs) alongside list/read tools for agent auto-execution paths.
readToolFile (codemode) changes
core/mcp/codemode/starlark/readfile.go
Prepend total-line-count header to file output, clamp start/end line ranges instead of erroring, update parameter descriptions and signature docs to note truncation and pagination guidance.
UI — Entity selector
ui/components/ui/entityAssociationSelect.tsx
New Async multi-select component for entity types with value mapping, custom option rendering, creation support, placeholders, and helper functions for option construction.
UI — MCP server selector
ui/components/ui/mcpServerSelector.tsx
New MCPServerSelector component: builds options from MCP clients, custom option views, selected-server list with badges and actions, and client-side filtering/reload.
UI — MCP tool selector
ui/components/ui/mcpToolSelector.tsx
New MCPToolSelector component: flattens tools across clients into searchable options, selected-tools table with collapsible parameter schema view, removal and filtering logic.
UI types
ui/lib/types/mcp.ts
Added SelectedTool and MCPToolSpec exported interfaces.
API tags
ui/lib/store/apis/baseApi.ts
Added "MCPToolGroups" to baseApi tagTypes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐇 I hopped through code with eager paws,
Interfaces stitched without a pause,
Servers, tools, and entities in line—
Selectors blooming, crisp and fine.
A little rabbit cheers: "Well done!" ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'mcp tool groups' is vague and does not clearly convey the main changes, which involve refactoring the MCP manager to use an interface and adding UI components. Consider a more descriptive title such as 'Refactor MCP manager to use interface and add UI components' or 'Make MCP manager injectable via interface' to better communicate the primary changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive, covering summary, changes, type, affected areas, testing instructions, and breaking changes. It follows the template structure well with most required sections filled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 01-28-mcp_tool_groups

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

Copy link
Copy Markdown
Contributor Author

akshaydeo commented Jan 27, 2026

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.

@akshaydeo akshaydeo marked this pull request as ready for review January 27, 2026 22:10
@akshaydeo akshaydeo mentioned this pull request Jan 27, 2026
16 tasks
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: 4

🤖 Fix all issues with AI agents
In `@core/bifrost.go`:
- Around line 2801-2808: The SetMCPManager method currently assigns whatever is
passed into bifrost.McpManager; add a simple nil guard so that if the incoming
manager is nil (or an untyped nil interface) you explicitly set
bifrost.McpManager = nil, otherwise assign the provided manager; locate the
SetMCPManager function and the MCPManagerInterface type to implement this check
and update the assignment accordingly and optionally add a short comment
documenting that callers must pass a properly initialized MCPManagerInterface.

In `@ui/components/ui/entityAssociationSelect.tsx`:
- Around line 110-135: The equality check in the selectedValues computation
(inside the value.map and options.find) can miss matches when ids are different
types; update the lookup to normalize types before comparing (e.g., compare
String(opt.id) to String(id) or otherwise cast both sides) so existingOption is
found and label/meta from options are preserved for Option<EntityOptionMeta>;
ensure the same normalization is used consistently in selectedValues and
anywhere else you match ids in this component.

In `@ui/components/ui/mcpServerSelector.tsx`:
- Around line 229-232: The href for the registry link is building a query param
with raw server.clientId which can break the URL for special characters; update
the anchor in the mcpServerSelector component to URL-encode the clientId (use
encodeURIComponent on server.clientId) when composing
`${registryPath}?server=...` so it matches the pattern used elsewhere (e.g.,
encodeURIComponent(provider)/encodeURIComponent(token)); ensure target and rel
attributes remain unchanged.

In `@ui/components/ui/mcpToolSelector.tsx`:
- Around line 232-307: The Collapsible is using asChild but its direct child is
a fragment (<>...</>), which prevents props/refs from being forwarded; either
remove the asChild prop on Collapsible so it renders its internal wrapper
normally, or replace the fragment with a single DOM element (e.g., a <tbody> or
<div>) so Collapsible(asChild) can forward props—update the JSX where
Collapsible is declared (the Collapsible open/onOpenChange block) and keep
CollapsibleTrigger and CollapsibleContent usage unchanged.
🧹 Nitpick comments (1)
ui/components/ui/mcpToolSelector.tsx (1)

13-35: Prefer reusing the shared SelectedTool type to avoid drift.

Now that SelectedTool is exported from ui/lib/types/mcp.ts, import/re‑export it here instead of redefining.

♻️ Suggested refactor
 import { ChevronDown, ChevronRight, X } from "lucide-react"
 import { useCallback, useMemo, useState } from "react"
+import type { SelectedTool } from "../../lib/types/mcp"
+export type { SelectedTool } from "../../lib/types/mcp"
@@
-// Types
-
-export interface SelectedTool {
-  mcpClientId: string
-  toolName: string
-}
+// Types

Comment thread core/bifrost.go
Comment on lines 2801 to 2808
// SetMCPManager sets the MCP manager for this Bifrost instance.
// This is primarily used for testing purposes to inject a custom MCP manager.
// This allows injecting a custom MCP manager implementation (e.g., for enterprise features).
//
// Parameters:
// - manager: The MCP manager to set
func (bifrost *Bifrost) SetMCPManager(manager *mcp.MCPManager) {
bifrost.mcpManager = manager
// - manager: The MCP manager to set (must implement MCPManagerInterface)
func (bifrost *Bifrost) SetMCPManager(manager mcp.MCPManagerInterface) {
bifrost.McpManager = manager
}
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

🏁 Script executed:

# First, check if there's information about a PR stack or related changes
git log --oneline -20

Repository: maximhq/bifrost

Length of output: 83


🏁 Script executed:

# Find the SetMCPManager method to see the actual implementation
fd core/bifrost.go -x cat -n {} | head -100

Repository: maximhq/bifrost

Length of output: 41


🏁 Script executed:

# Get more context around lines 2801-2808 in core/bifrost.go
wc -l core/bifrost.go

Repository: maximhq/bifrost

Length of output: 79


🏁 Script executed:

# Search for how McpManager is used in the codebase
rg "McpManager" --type go -A 2 -B 2 | head -100

Repository: maximhq/bifrost

Length of output: 4959


🏁 Script executed:

# Check the MCPManagerInterface definition
rg "MCPManagerInterface" --type go -A 5 -B 2 | head -150

Repository: maximhq/bifrost

Length of output: 2341


🏁 Script executed:

# Search for actual calls to SetMCPManager
rg "SetMCPManager" --type go -B 3 -A 3

Repository: maximhq/bifrost

Length of output: 50371


🏁 Script executed:

# Check if there are any typed-nil patterns or reflect usage in the codebase
rg "reflect\." --type go | head -30

Repository: maximhq/bifrost

Length of output: 2613


🏁 Script executed:

# Get the exact location and context of SetMCPManager in bifrost.go
sed -n '2795,2815p' core/bifrost.go

Repository: maximhq/bifrost

Length of output: 756


🏁 Script executed:

# Check the MCPManagerInterface method signatures to understand what methods are called
fd core/mcp/interface.go -x cat -n | head -200

Repository: maximhq/bifrost

Length of output: 41


🏁 Script executed:

# Search for nil checks patterns in the codebase related to McpManager
rg "McpManager.*!= nil|McpManager.*== nil" --type go -B 2 -A 5

Repository: maximhq/bifrost

Length of output: 6262


🏁 Script executed:

# Check if there are any existing patterns in Go standard library or common patterns for validating interface implementations
rg "reflect.ValueOf.*Kind.*Ptr.*IsNil" --type go || echo "No match found in codebase"

Repository: maximhq/bifrost

Length of output: 85


🏁 Script executed:

# Look at how other setters in the codebase handle similar interface validation
rg "func.*Set.*interface\{}" --type go -A 3 | head -50

Repository: maximhq/bifrost

Length of output: 708


🏁 Script executed:

# Check if there are any other MCPManager validation patterns
fd core/mcp/ -name "*.go" -x cat -n {} | grep -A 10 -B 5 "nil" | head -80

Repository: maximhq/bifrost

Length of output: 289


🏁 Script executed:

# Check the MCPManager implementation to see if it's likely a typed-nil could exist
fd "manager.go" core/mcp/ -x head -100 {}

Repository: maximhq/bifrost

Length of output: 7046


🏁 Script executed:

# Search for how MCPManager is instantiated
rg "NewMCPManager" --type go -B 3 -A 5

Repository: maximhq/bifrost

Length of output: 3504


🏁 Script executed:

# Check if there are any tests or patterns showing typed-nil issues
rg "typed.nil|typed nil" --type go

Repository: maximhq/bifrost

Length of output: 41


Consider a simpler safeguard for SetMCPManager against nil or invalid implementations.

While a nil pointer stored in an interface value can pass != nil checks in Go, a more practical concern is ensuring the manager is properly initialized before use. However, the reflect-based approach is unconventional. A simpler alternative would be to document this expectation or add a basic nil check that converts to nil explicitly:

func (bifrost *Bifrost) SetMCPManager(manager mcp.MCPManagerInterface) {
+	if manager == nil {
+		bifrost.McpManager = nil
+		return
+	}
	bifrost.McpManager = manager
}

This covers the common case without requiring reflection. If stricter typed-nil validation is needed, that's better handled through code review and documentation of what valid implementations must provide.

📝 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
// SetMCPManager sets the MCP manager for this Bifrost instance.
// This is primarily used for testing purposes to inject a custom MCP manager.
// This allows injecting a custom MCP manager implementation (e.g., for enterprise features).
//
// Parameters:
// - manager: The MCP manager to set
func (bifrost *Bifrost) SetMCPManager(manager *mcp.MCPManager) {
bifrost.mcpManager = manager
// - manager: The MCP manager to set (must implement MCPManagerInterface)
func (bifrost *Bifrost) SetMCPManager(manager mcp.MCPManagerInterface) {
bifrost.McpManager = manager
}
// SetMCPManager sets the MCP manager for this Bifrost instance.
// This allows injecting a custom MCP manager implementation (e.g., for enterprise features).
//
// Parameters:
// - manager: The MCP manager to set (must implement MCPManagerInterface)
func (bifrost *Bifrost) SetMCPManager(manager mcp.MCPManagerInterface) {
if manager == nil {
bifrost.McpManager = nil
return
}
bifrost.McpManager = manager
}
🤖 Prompt for AI Agents
In `@core/bifrost.go` around lines 2801 - 2808, The SetMCPManager method currently
assigns whatever is passed into bifrost.McpManager; add a simple nil guard so
that if the incoming manager is nil (or an untyped nil interface) you explicitly
set bifrost.McpManager = nil, otherwise assign the provided manager; locate the
SetMCPManager function and the MCPManagerInterface type to implement this check
and update the assignment accordingly and optionally add a short comment
documenting that callers must pass a properly initialized MCPManagerInterface.

Comment on lines +110 to +135
// Convert selected IDs to Option format
const selectedValues = useMemo((): Option<EntityOptionMeta>[] => {
return value.map((id) => {
// Try to find the option in the provided options
const existingOption = options.find((opt) => opt.id === id)
if (existingOption) {
return {
label: existingOption.label,
value: String(existingOption.id),
meta: {
id: existingOption.id,
description: existingOption.description,
metadata: existingOption.metadata,
},
}
}
// If not found, create a basic option
return {
label: String(id),
value: String(id),
meta: {
id,
},
}
})
}, [value, options])
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

🏁 Script executed:

fd entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 103


🏁 Script executed:

wc -l ui/components/ui/entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 107


🏁 Script executed:

head -50 ui/components/ui/entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 1605


🏁 Script executed:

sed -n '100,150p' ui/components/ui/entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 1474


🏁 Script executed:

rg "entityAssociationSelect|EntityAssociationSelect" --type tsx --type ts -A 3 -B 3

Repository: maximhq/bifrost

Length of output: 86


🏁 Script executed:

rg "entityAssociationSelect|EntityAssociationSelect" --type ts -A 3 -B 3 | head -100

Repository: maximhq/bifrost

Length of output: 1583


🏁 Script executed:

rg "EntityAssociationSelect" -A 5 -B 2 | grep -E "value=|EntityAssociationSelect|<EntityAssociationSelect" | head -50

Repository: maximhq/bifrost

Length of output: 311


🏁 Script executed:

rg "import.*EntityAssociationSelect|from.*entityAssociationSelect" -A 10 -B 2

Repository: maximhq/bifrost

Length of output: 41


🏁 Script executed:

rg "<EntityAssociationSelect" -A 5

Repository: maximhq/bifrost

Length of output: 41


🏁 Script executed:

sed -n '1,100p' ui/components/ui/entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 2915


🏁 Script executed:

sed -n '150,246p' ui/components/ui/entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 3119


🏁 Script executed:

fd "\.tsx$\|\.ts$" | xargs rg "EntityAssociationSelect" | grep -v "entityAssociationSelect.tsx"

Repository: maximhq/bifrost

Length of output: 41


🏁 Script executed:

git log --oneline -n 20 ui/components/ui/entityAssociationSelect.tsx 2>/dev/null || echo "No git history available"

Repository: maximhq/bifrost

Length of output: 83


🏁 Script executed:

git log --oneline -n 50 | head -20

Repository: maximhq/bifrost

Length of output: 83


🏁 Script executed:

rg "id.*string.*number|string.*number.*id" ui/components/ui/entityAssociationSelect.tsx

Repository: maximhq/bifrost

Length of output: 318


🏁 Script executed:

python3 << 'EOF'
# Analyzing the type mismatch issue
# The types allow:
# - value: (string | number)[]
# - options[].id: string | number

# Strict equality comparison behavior:
test_cases = [
  ("string '123'", "number 123", "'123' === 123", False),
  ("string '123'", "string '123'", "'123' === '123'", True),
  ("number 123", "number 123", "123 === 123", True),
  ("number 123", "string '123'", "123 === '123'", False),
]

print("Strict Equality (===) Behavior for ID Matching:")
print("=" * 60)
for type1, type2, expr, result in test_cases:
    print(f"{type1:20} vs {type2:20} | {expr:20} → {result}")

print("\n" + "=" * 60)
print("Type system allows both types, but strict === will fail on mismatches")
print("This means selectedValues could miss label/metadata lookups if types differ")
EOF

Repository: maximhq/bifrost

Length of output: 145


Normalize ID matching to avoid losing labels/metadata.

The component accepts both strings and numbers for ids (value: (string | number)[] and options[].id: string | number). Using strict equality at line 113 will fail when comparing mismatched types—for example, when value contains string "123" while options contains number 123. This causes the lookup to fail and fall back to a basic option without label or metadata.

Suggested fix
-      const existingOption = options.find((opt) => opt.id === id)
+      const existingOption = options.find((opt) => String(opt.id) === String(id))
📝 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
// Convert selected IDs to Option format
const selectedValues = useMemo((): Option<EntityOptionMeta>[] => {
return value.map((id) => {
// Try to find the option in the provided options
const existingOption = options.find((opt) => opt.id === id)
if (existingOption) {
return {
label: existingOption.label,
value: String(existingOption.id),
meta: {
id: existingOption.id,
description: existingOption.description,
metadata: existingOption.metadata,
},
}
}
// If not found, create a basic option
return {
label: String(id),
value: String(id),
meta: {
id,
},
}
})
}, [value, options])
// Convert selected IDs to Option format
const selectedValues = useMemo((): Option<EntityOptionMeta>[] => {
return value.map((id) => {
// Try to find the option in the provided options
const existingOption = options.find((opt) => String(opt.id) === String(id))
if (existingOption) {
return {
label: existingOption.label,
value: String(existingOption.id),
meta: {
id: existingOption.id,
description: existingOption.description,
metadata: existingOption.metadata,
},
}
}
// If not found, create a basic option
return {
label: String(id),
value: String(id),
meta: {
id,
},
}
})
}, [value, options])
🤖 Prompt for AI Agents
In `@ui/components/ui/entityAssociationSelect.tsx` around lines 110 - 135, The
equality check in the selectedValues computation (inside the value.map and
options.find) can miss matches when ids are different types; update the lookup
to normalize types before comparing (e.g., compare String(opt.id) to String(id)
or otherwise cast both sides) so existingOption is found and label/meta from
options are preserved for Option<EntityOptionMeta>; ensure the same
normalization is used consistently in selectedValues and anywhere else you match
ids in this component.

Comment thread ui/components/ui/mcpServerSelector.tsx Outdated
Comment on lines +229 to +232
<a
href={`${registryPath}?server=${server.clientId}`}
target="_blank"
rel="noopener noreferrer"
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

🏁 Script executed:

# First, let's locate and examine the specific file
find . -type f -name "mcpServerSelector.tsx" 2>/dev/null

Repository: maximhq/bifrost

Length of output: 99


🏁 Script executed:

# Check the file size and examine the relevant lines
if [ -f "ui/components/ui/mcpServerSelector.tsx" ]; then
  wc -l "ui/components/ui/mcpServerSelector.tsx"
  echo "---"
  sed -n '220,240p' "ui/components/ui/mcpServerSelector.tsx"
fi

Repository: maximhq/bifrost

Length of output: 835


🏁 Script executed:

# Also search for other instances of similar patterns where clientId might be used in URLs
rg "href=.*\?.*server=" -A 2 -B 2 ui/components/

Repository: maximhq/bifrost

Length of output: 445


🏁 Script executed:

# Search for any encodeURIComponent usage to see if it's used elsewhere
rg "encodeURIComponent" ui/ || echo "No instances found"

Repository: maximhq/bifrost

Length of output: 374


🏁 Script executed:

# Check if there are related files or configurations about URL encoding patterns
rg "registryPath" --type tsx --type ts

Repository: maximhq/bifrost

Length of output: 86


🏁 Script executed:

# Let me look at more context around the line and understand what registryPath is
sed -n '200,240p' "ui/components/ui/mcpServerSelector.tsx"

Repository: maximhq/bifrost

Length of output: 1600


🏁 Script executed:

# Search for the definition of registryPath in the same file
rg "registryPath" "ui/components/ui/mcpServerSelector.tsx" -B 5 -A 2

Repository: maximhq/bifrost

Length of output: 807


🏁 Script executed:

# Check where server.clientId comes from and what its structure might be
rg "clientId" "ui/components/ui/mcpServerSelector.tsx" -B 3 -A 1

Repository: maximhq/bifrost

Length of output: 1321


🏁 Script executed:

# Look at git log to see if there's a PR stack or related commits
git log --oneline -20

Repository: maximhq/bifrost

Length of output: 83


🏁 Script executed:

# Check if there are similar URL parameter patterns elsewhere in the same file
rg "href=.*\$" "ui/components/ui/mcpServerSelector.tsx"

Repository: maximhq/bifrost

Length of output: 129


Encode server IDs in the registry URL to prevent query string breakage.

The server.clientId parameter should be URL-encoded to handle special characters. The codebase already follows this pattern elsewhere (e.g., encodeURIComponent(provider) in governanceApi.ts, encodeURIComponent(token) in useWebSocket.tsx).

✅ Suggested fix
-                    href={`${registryPath}?server=${server.clientId}`}
+                    href={`${registryPath}?server=${encodeURIComponent(server.clientId)}`}
📝 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
<a
href={`${registryPath}?server=${server.clientId}`}
target="_blank"
rel="noopener noreferrer"
<a
href={`${registryPath}?server=${encodeURIComponent(server.clientId)}`}
target="_blank"
rel="noopener noreferrer"
🤖 Prompt for AI Agents
In `@ui/components/ui/mcpServerSelector.tsx` around lines 229 - 232, The href for
the registry link is building a query param with raw server.clientId which can
break the URL for special characters; update the anchor in the mcpServerSelector
component to URL-encode the clientId (use encodeURIComponent on server.clientId)
when composing `${registryPath}?server=...` so it matches the pattern used
elsewhere (e.g., encodeURIComponent(provider)/encodeURIComponent(token)); ensure
target and rel attributes remain unchanged.

Comment thread ui/components/ui/mcpToolSelector.tsx Outdated
Comment on lines +232 to +307
<Collapsible
key={key}
open={isExpanded}
onOpenChange={() => toggleExpanded(key)}
asChild
>
<>
<TableRow className="group">
<TableCell className="p-2">
<CollapsibleTrigger asChild>
<button
type="button"
className="hover:bg-muted flex h-8 w-8 items-center justify-center rounded-md transition-colors"
disabled={!tool.parameters}
>
{tool.parameters ? (
isExpanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)
) : (
<span className="h-4 w-4" />
)}
</button>
</CollapsibleTrigger>
</TableCell>
<TableCell>
<div className="min-w-0">
<div className="text-foreground truncate text-sm font-medium">
{tool.toolName}
</div>
{tool.description && (
<p className="text-muted-foreground mt-0.5 line-clamp-1 text-xs">
{tool.description}
</p>
)}
</div>
</TableCell>
<TableCell className="hidden md:table-cell">
<Badge variant="outline">{tool.mcpClientName}</Badge>
</TableCell>
<TableCell className="p-2">
<Button
type="button"
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={() => handleRemoveTool(tool.mcpClientId, tool.toolName)}
disabled={disabled}
>
<X className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
<CollapsibleContent asChild>
<tr>
<td colSpan={4} className="p-0">
<div className="bg-muted/30 border-t px-4 py-3">
<div className="text-muted-foreground mb-2 text-xs font-medium">
Parameters Schema
</div>
{tool.parameters ? (
<pre className="bg-muted overflow-auto rounded-md p-3 text-xs">
<code>{JSON.stringify(tool.parameters, null, 2)}</code>
</pre>
) : (
<div className="text-muted-foreground text-sm">
No parameters defined
</div>
)}
</div>
</td>
</tr>
</CollapsibleContent>
</>
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

🏁 Script executed:

#!/bin/bash
# Inspect Collapsible and Table component implementations
echo "=== Inspecting collapsible.tsx ===" 
cat ui/components/ui/collapsible.tsx

echo -e "\n=== Inspecting table.tsx ===" 
cat ui/components/ui/table.tsx

Repository: maximhq/bifrost

Length of output: 3168


Remove asChild from Collapsible or remove the fragment wrapper.

The asChild prop on Collapsible (from Radix UI) merges props/refs into its first child via the Slot pattern. A fragment <> cannot receive props or refs, blocking this mechanism. Either remove asChild to let Collapsible render normally, or restructure to pass a single DOM element (not wrapped in a fragment) as the direct child when using asChild.

🤖 Prompt for AI Agents
In `@ui/components/ui/mcpToolSelector.tsx` around lines 232 - 307, The Collapsible
is using asChild but its direct child is a fragment (<>...</>), which prevents
props/refs from being forwarded; either remove the asChild prop on Collapsible
so it renders its internal wrapper normally, or replace the fragment with a
single DOM element (e.g., a <tbody> or <div>) so Collapsible(asChild) can
forward props—update the JSX where Collapsible is declared (the Collapsible
open/onOpenChange block) and keep CollapsibleTrigger and CollapsibleContent
usage unchanged.

@akshaydeo akshaydeo force-pushed the 01-28-mcp_tool_groups branch from 933ba82 to 8a3e7d8 Compare January 28, 2026 07:57
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: 0

Caution

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

⚠️ Outside diff range comments (1)
core/mcp/codemode/starlark/readfile.go (1)

204-252: Avoid “complete file” headers on partial reads.

Because the header is inserted before slicing, any request with startLine/endLine can return only a subset while still saying “this is the complete file.” That can cause callers to stop paginating early. Update the header after clamping so it reflects whether the full range was returned (e.g., “showing lines X–Y of Z”), or only add the “complete file” suffix when the final range is full.

🛠️ Suggested fix (adjust header when returning a subset)
 		if start > end {
 			// If start > end after clamping, just return the start line
 			end = start
 		}
+		if start == 1 && end < totalLines {
+			lines[0] = fmt.Sprintf("# Total lines: %d (showing lines %d-%d)", totalLines, start, end)
+		}
 
 		// Slice lines (convert to 0-based indexing)
 		selectedLines := lines[start-1 : end]
 		fileContent = strings.Join(selectedLines, "\n")
 	}
🧹 Nitpick comments (1)
ui/components/ui/mcpToolSelector.tsx (1)

15-38: Prefer the shared SelectedTool type to avoid drift.

SelectedTool now exists in ui/lib/types/mcp.ts; consider reusing it here instead of redefining it locally to keep types consistent across UI surfaces.

♻️ Possible refactor
+import type { SelectedTool } from "@/lib/types/mcp";
+export type { SelectedTool } from "@/lib/types/mcp";
 
 // Types
-export interface SelectedTool {
-  mcpClientId: string
-  toolName: string
-}

@akshaydeo akshaydeo changed the base branch from 01-27-mcp_flow_refactoring to graphite-base/1464 January 28, 2026 09:34
@akshaydeo akshaydeo closed this Jan 28, 2026
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