Skip to content

feat: support more mcp server types#445

Open
DaniAkash wants to merge 8 commits intomainfrom
feat/update-mcp-support-2
Open

feat: support more mcp server types#445
DaniAkash wants to merge 8 commits intomainfrom
feat/update-mcp-support-2

Conversation

@DaniAkash
Copy link
Contributor

This pull request refactors how MCP server configuration is managed and accessed throughout the application. The main change is the migration from the legacy useMcpServers and related utilities to a new set of hooks and methods (useMcpConfig, useAddMcpServer, useRemoveMcpServer) that centralize and streamline MCP server state management. Additionally, the codebase is simplified by removing now-unnecessary references to enabled MCP/custom servers in chat and workflow contexts. A data migration is also triggered on background startup to ensure existing user data is preserved.

MCP Server Management Refactor:

  • Replaced all usage of useMcpServers with the new useMcpConfig, useAddMcpServer, and useRemoveMcpServer hooks across the app, including in AppSelector.tsx, ConnectMCP.tsx, NewTab.tsx, and ChatFooter.tsx. This change unifies MCP server state management and enables more robust server mutations and cache updates. [1] [2] [3] [4] [5] [6] [7] [8]
  • Updated all server add/remove operations to use async mutations and refresh the MCP config state via mutateMcpConfig, ensuring UI consistency after server changes. [1] [2] [3] [4] [5]

Custom Server Enhancements:

  • Extended custom server creation to support additional fields such as transport, headers, command, args, cwd, and env, and updated the UI to display transport type and command line for stdio transports. [1] [2]

Legacy Code and Data Migration:

  • Removed all logic and references related to enabledMcpServersRef and enabledCustomServersRef from chat, workflow, and session hooks (useChatRefs, useChatSession, useRunWorkflow, CreateGraph.tsx), simplifying the context and reducing unnecessary data passing. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]
  • Added a call to migrateMcpToServer() in the background script to migrate legacy MCP server data to the new storage format on startup, ensuring user data continuity. [1] [2]

These changes modernize and consolidate MCP server management, reduce technical debt, and prepare the codebase for future MCP-related enhancements.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR migrates MCP server configuration from browser extension storage (chrome.storage.local via @wxt-dev/storage) to a server-side mcp.json file, and extends custom server support to include HTTP, SSE, and stdio transports. The refactor introduces new React Query hooks (useMcpConfig, useAddMcpServer, useRemoveMcpServer) that communicate with a new set of Hono REST endpoints, and removes all legacy enabledMcpServersRef/enabledCustomServersRef plumbing from the chat and workflow layers. A one-time migration script runs on background startup to carry existing user data forward.

Key changes:

  • New server-side mcp.json persistence layer (apps/server/src/lib/mcp-config.ts) with atomic writes and Zod validation, accessed via REST routes at /mcp-config
  • useMcpConfig / useAddMcpServer / useRemoveMcpServer React Query hooks replace the deprecated useMcpServers hook across AppSelector, ConnectMCP, NewTab, and ChatFooter
  • AddCustomMCPDialog now supports three transport types (HTTP, SSE, stdio) with dedicated forms per transport
  • mcp-builder.ts consumes getMcpServers() from the server-side file to construct MCP tool clients per request
  • buildMcpServerKey in chat-service.ts is now correctly server-side (reads mcp.json) enabling mid-conversation MCP rebuild detection

Issues found:

  • ai-sdk-agent.ts: connectedApps in the system prompt is now always undefined for regular chat sessions — the browser context no longer carries enabledMcpServers (removed in this PR), so the agent loses its textual hint about which managed apps are connected, even though the tools themselves are correctly registered via buildMcpServerSpecs
  • AddCustomMCPDialog.tsx: stdio args are split on whitespace, breaking any argument that contains a space (e.g. file paths like /Users/John Doe/project)
  • mcp-config.ts (server): addMcpServer uses Date.now().toString() as the entry ID; concurrent requests within the same millisecond would produce duplicate IDs — crypto.randomUUID() is safer

Confidence Score: 2/5

  • Not safe to merge as-is — the agent system prompt silently loses connected-app context for all regular chat sessions.
  • The architecture of the migration is sound and the new hooks/routes are well-structured, but there is a silent regression in ai-sdk-agent.ts: connectedApps is now always undefined for the main chat flow because enabledMcpServers was removed from the browser context. This means the agent's system prompt no longer includes the names of connected managed apps, degrading its ability to proactively surface and use those tools. The args-splitting bug in AddCustomMCPDialog also affects a key new feature (stdio support). Both issues should be resolved before shipping.
  • apps/server/src/agent/ai-sdk-agent.ts (connectedApps regression) and apps/agent/entrypoints/app/connect-mcp/AddCustomMCPDialog.tsx (stdio args parsing).

Important Files Changed

Filename Overview
apps/server/src/agent/ai-sdk-agent.ts Passes connectedApps from browserContext?.enabledMcpServers, which is now always undefined for regular chat sessions after this PR removes client-side MCP population — the system prompt will silently lose connected-app context.
apps/agent/entrypoints/app/connect-mcp/AddCustomMCPDialog.tsx New dialog supporting HTTP/SSE/stdio transport types; stdio args are split on whitespace which breaks for arguments containing spaces (e.g. file paths).
apps/server/src/lib/mcp-config.ts Solid server-side persistence layer with atomic writes; uses Date.now().toString() for IDs which can collide under concurrent requests — crypto.randomUUID() would be safer.
apps/agent/lib/mcp/useMcpConfig.ts Clean React Query hooks replacing the deprecated useMcpServers; mutations correctly invalidate the query cache on success.
apps/agent/lib/mcp/migrateMcpToServer.ts One-time migration from browser extension storage to server-side mcp.json; correctly idempotent (flag-gated) and tolerates server-not-yet-running with silent retry on next startup.
apps/agent/entrypoints/app/connect-mcp/ConnectMCP.tsx Fully migrated to new hooks; add/remove operations now async with proper try/catch and error toasts for all paths.
apps/server/src/api/routes/mcp-config.ts Clean Hono routes for CRUD + migration with Zod validation; all endpoints return appropriate HTTP status codes.
apps/server/src/agent/mcp-builder.ts Reads server-side mcp.json to build MCP specs for managed (Klavis Strata) and custom (HTTP/SSE/stdio) servers; concurrently connects clients with timeout protection.
apps/agent/entrypoints/background/index.ts Correctly fires migrateMcpToServer() on background startup with a silent catch (intentional — retries on next startup if the agent server isn't running yet).
apps/agent/lib/mcp/mcpServerStorage.ts Correctly deprecated with @deprecated JSDoc; retained solely for the one-time migration path.

Sequence Diagram

sequenceDiagram
    participant Ext as Browser Extension
    participant BG as Background Script
    participant UI as React UI (useMcpConfig)
    participant SRV as Agent Server (/mcp-config)
    participant FS as mcp.json (disk)
    participant Agent as AiSdkAgent

    Note over BG,FS: One-time migration on startup
    BG->>FS: migrateMcpToServer() → POST /mcp-config/migrate
    FS-->>BG: migrated count

    Note over UI,FS: CRUD via REST hooks
    UI->>SRV: GET /mcp-config
    SRV->>FS: readMcpConfig()
    FS-->>SRV: { version, servers[] }
    SRV-->>UI: { servers[] }

    UI->>SRV: POST /mcp-config (add server)
    SRV->>FS: addMcpServer() → atomic write
    FS-->>SRV: McpServerEntry (with id)
    SRV-->>UI: { server } 201
    UI->>UI: invalidateQueries([mcp-config])

    Note over Agent,FS: Per-session MCP tool building
    Agent->>FS: buildMcpServerSpecs() → getMcpServers()
    FS-->>Agent: McpServerEntry[]
    Agent->>Agent: createMcpClients(specs)
    Agent-->>Agent: { tools } merged toolset
Loading

Comments Outside Diff (1)

  1. apps/server/src/agent/ai-sdk-agent.ts, line 149 (link)

    connectedApps always undefined for regular chat sessions

    After this PR, the main chat flow no longer populates enabledMcpServers in the browser context — it was removed from useChatSession.ts and useChatRefs.ts. As a result, config.browserContext?.enabledMcpServers is always undefined for every regular chat request, and the system prompt's connectedApps section (which tells the agent which managed apps like Gmail, Calendar, etc. are connected) will always be empty.

    The MCP tools themselves are correctly registered via buildMcpServerSpecs (which reads from the server-side mcp.json), so the agent has the tools — but it loses the textual context hint about which apps are connected. This is a regression for the chat path, even though the SDK path (packages/agent-sdk) still forwards enabledMcpServers from its context.

    The fix is to derive connectedApps directly from the server-side config alongside buildMcpServerSpecs:

    const specs = await buildMcpServerSpecs({
      klavisClient: config.klavisClient,
      browserosId: config.browserosId,
    })
    const { clients, tools: externalMcpTools } = await createMcpClients(specs)
    
    // Derive connected app names from server-side mcp.json for the system prompt
    const mcpServers = await getMcpServers()
    const connectedApps = mcpServers
      .filter((s) => s.type === 'managed' && s.managedServerName)
      .map((s) => s.managedServerName!)
    
    // ...
    
    const instructions = buildSystemPrompt({
      // ...
      connectedApps:
        config.browserContext?.enabledMcpServers ??
        (connectedApps.length > 0 ? connectedApps : undefined),
      // ...
    })
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/server/src/agent/ai-sdk-agent.ts
    Line: 149
    
    Comment:
    **`connectedApps` always `undefined` for regular chat sessions**
    
    After this PR, the main chat flow no longer populates `enabledMcpServers` in the browser context — it was removed from `useChatSession.ts` and `useChatRefs.ts`. As a result, `config.browserContext?.enabledMcpServers` is always `undefined` for every regular chat request, and the system prompt's `connectedApps` section (which tells the agent which managed apps like Gmail, Calendar, etc. are connected) will always be empty.
    
    The MCP tools themselves **are** correctly registered via `buildMcpServerSpecs` (which reads from the server-side `mcp.json`), so the agent has the tools — but it loses the textual context hint about which apps are connected. This is a regression for the chat path, even though the SDK path (`packages/agent-sdk`) still forwards `enabledMcpServers` from its context.
    
    The fix is to derive `connectedApps` directly from the server-side config alongside `buildMcpServerSpecs`:
    
    ```ts
    const specs = await buildMcpServerSpecs({
      klavisClient: config.klavisClient,
      browserosId: config.browserosId,
    })
    const { clients, tools: externalMcpTools } = await createMcpClients(specs)
    
    // Derive connected app names from server-side mcp.json for the system prompt
    const mcpServers = await getMcpServers()
    const connectedApps = mcpServers
      .filter((s) => s.type === 'managed' && s.managedServerName)
      .map((s) => s.managedServerName!)
    
    // ...
    
    const instructions = buildSystemPrompt({
      // ...
      connectedApps:
        config.browserContext?.enabledMcpServers ??
        (connectedApps.length > 0 ? connectedApps : undefined),
      // ...
    })
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/server/src/agent/ai-sdk-agent.ts
Line: 149

Comment:
**`connectedApps` always `undefined` for regular chat sessions**

After this PR, the main chat flow no longer populates `enabledMcpServers` in the browser context — it was removed from `useChatSession.ts` and `useChatRefs.ts`. As a result, `config.browserContext?.enabledMcpServers` is always `undefined` for every regular chat request, and the system prompt's `connectedApps` section (which tells the agent which managed apps like Gmail, Calendar, etc. are connected) will always be empty.

The MCP tools themselves **are** correctly registered via `buildMcpServerSpecs` (which reads from the server-side `mcp.json`), so the agent has the tools — but it loses the textual context hint about which apps are connected. This is a regression for the chat path, even though the SDK path (`packages/agent-sdk`) still forwards `enabledMcpServers` from its context.

The fix is to derive `connectedApps` directly from the server-side config alongside `buildMcpServerSpecs`:

```ts
const specs = await buildMcpServerSpecs({
  klavisClient: config.klavisClient,
  browserosId: config.browserosId,
})
const { clients, tools: externalMcpTools } = await createMcpClients(specs)

// Derive connected app names from server-side mcp.json for the system prompt
const mcpServers = await getMcpServers()
const connectedApps = mcpServers
  .filter((s) => s.type === 'managed' && s.managedServerName)
  .map((s) => s.managedServerName!)

// ...

const instructions = buildSystemPrompt({
  // ...
  connectedApps:
    config.browserContext?.enabledMcpServers ??
    (connectedApps.length > 0 ? connectedApps : undefined),
  // ...
})
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/agent/entrypoints/app/connect-mcp/AddCustomMCPDialog.tsx
Line: 159-163

Comment:
**Args with spaces cannot be represented**

Splitting the args string on whitespace (`split(/\s+/)`) breaks for any argument containing a space — the most common case being file paths (e.g. `/Users/John Doe/project` or `C:\Program Files\my-server`). Such a path would be silently split into multiple incorrect arguments, causing the stdio MCP server to fail at start-up with no clear error in the UI.

Consider replacing the single args string with a dynamic list of individual argument fields (similar to the env-var key/value pairs already used below), or at minimum support simple shell quoting so that `"arg with spaces"` is kept as one token.

```ts
// Current — breaks on spaces:
const args = values.args?.trim()
  ? values.args.trim().split(/\s+/)
  : undefined

// Safer alternative using shell-quote or a per-argument list UI:
// e.g. import { parse } from 'shell-quote'
// const args = values.args?.trim() ? parse(values.args.trim()) as string[] : undefined
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/server/src/lib/mcp-config.ts
Line: 85-92

Comment:
**`Date.now()` IDs can collide under concurrent adds**

`Date.now().toString()` is used as the server ID. If two `POST /mcp-config` requests arrive within the same millisecond (e.g. during a migration batch or a rapid double-click), both entries will receive the same ID. The `migrateMcpServers` deduplication logic keys on `id`, so one of the migrated servers would be silently dropped.

Consider `crypto.randomUUID()` which guarantees uniqueness:

```suggestion
export async function addMcpServer(
  server: Omit<McpServerEntry, 'id'>,
): Promise<McpServerEntry> {
  const config = await readMcpConfig()
  const entry: McpServerEntry = { ...server, id: crypto.randomUUID() }
  config.servers.push(entry)
  await writeMcpConfig(config)
  return entry
}
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 95ccf20

@DaniAkash
Copy link
Contributor Author

@claude @greptileai

@claude
Copy link

claude bot commented Mar 12, 2026

Claude finished @DaniAkash's task in 2m 4s —— View job


Code Review

  • Gather context and understand request
  • Review ConnectMCP.tsx (error handling for addCustomServer & removal)
  • Review AddCustomMCPDialog.tsx (async/dialog close issue)
  • Review useMcpConfig.ts (new React Query hooks)
  • Review migrateMcpToServer.ts (stale data cleanup)
  • Review mcp-config.ts (server-side CRUD)
  • Review mcp-builder.ts (stdio transport support)
  • Post final review summary

The architecture is solid — migrating MCP config to a server-side mcp.json is a clean design, and the new React Query hooks, Hono routes, and mcp-builder.ts refactor are well-structured. However, three issues from the previous Greptile review remain unresolved, plus two new findings.

Issues Remaining from Previous Review

1. addCustomServer still has no error handlingConnectMCP.tsx:177-195

const addCustomServer = async (config: CustomServerConfig) => {
  await addServerMutation({  // throws on non-2xx response
    ...
  })
  track(CUSTOM_MCP_ADDED_EVENT, ...)
}

All sibling handlers (addManagedServer, deleteManagedServer, handleSubmitApiKey) wrap mutations in try/catch and call failedToAddMcp on failure. This one doesn't — network errors are silently swallowed. The failedToAddMcp helper is already imported and ready to use. Fix this →

2. Custom server removal silently drops errorsConnectMCP.tsx:338

} else {
  removeServerMutation(server.id)  // promise dropped, no .catch()
}

Two issues: (a) errors are silently lost with no user feedback, and (b) deletingServerId is never set for custom server deletion, so there's no loading spinner — clicking the button twice can fire duplicate mutations. deleteManagedServer does both correctly. Fix this →

3. Dialog closes synchronously before async mutation settlesAddCustomMCPDialog.tsx:77

interface AddCustomMCPDialogProps {
  onAddServer: (config: CustomServerConfig) => void  // typed as void
}

// In onUrlSubmit:
onAddServer({...})   // fire-and-forget
resetForms()         // dialog closes immediately
onOpenChange(false)

The prop should be (config: CustomServerConfig) => Promise<void> so the submit handlers can await onAddServer(config) before closing. This would enable: showing a loading state on the "Add Server" button, keeping the dialog open until success, and surfacing errors back to the user. Fix this →

Remaining Issue from Greptile (Lower Priority)

4. Stale mcpServerStorage data never clearedmigrateMcpToServer.ts

After a successful migration, MCP_MIGRATED_KEY is set to true but the original data in local:mcpServers is never removed. It becomes orphaned dead storage. Consider adding await mcpServerStorage.setValue([]) after await storage.setItem(MCP_MIGRATED_KEY, true).

New Findings

5. No loading state on custom server deletion (also part of issue #2 above)

deletingServerId is only set in the managed server path. The Button at line 330 checks deletingServerId === server.id, but for custom server deletion this is never set, so the button shows no spinner while the mutation is in-flight.

6. Experimental_StdioMCPTransport usagemcp-builder.ts:2

import { Experimental_StdioMCPTransport as StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio'

The Experimental_ prefix indicates an unstable API. Worth leaving a comment noting this and pinning the @ai-sdk/mcp version so an upstream breaking change doesn't silently break stdio transport.

What Looks Good

  • mcp-config.ts: Atomic writes via temp-file-then-rename are correct. Zod validation on both read and write is solid. migrateMcpServers correctly deduplicates by ID.
  • mcp-builder.ts: Per-client timeout protection via Promise.race is a good pattern. Concurrent client creation with Promise.all and graceful per-client skip on failure is clean.
  • useMcpConfig.ts: React Query integration is correct. Null-key guard (enabled: !!baseUrl) prevents spurious requests. Cache invalidation on mutation onSuccess removes the need for manual mutateMcpConfig() calls.
  • mcp-config routes: Zod validation on all inputs, consistent 404 handling, clean Hono structure.
  • migrateMcpToServer.ts: One-shot migration with retry-on-next-startup semantics is appropriate.

@DaniAkash
Copy link
Contributor Author

@claude @greptileai

@claude
Copy link

claude bot commented Mar 12, 2026

Claude finished @DaniAkash's task in 2m 6s —— View job


Code Review

  • Gather context and understand request
  • Read changed files (ConnectMCP.tsx, chat-service.ts, browser-context.ts, migrateMcpToServer.ts, AddCustomMCPDialog.tsx, useMcpConfig.ts)
  • Check if previous review issues were resolved
  • Post updated review findings

Good progress — the critical issues from the previous round are resolved. Here's the updated status:

Previously Raised Issues — Status

✅ Fixed: buildMcpServerKey broken mid-conversation rebuild (chat-service.ts:255–261)

The method now reads directly from getMcpServers() (the server-side mcp.json), building a stable key like custom:abc123,managed:xyz789. The mid-conversation change detection is working correctly again. This was the most important fix.

✅ Fixed: addCustomServer missing error handling (ConnectMCP.tsx:177–199)

Now wrapped in try/catch with failedToAddMcp(config.name, e) on failure. Matches the pattern used by addManagedServer.

✅ Fixed: Custom server removal dropped promise (ConnectMCP.tsx:342–344)

Now uses .catch((e) => failedToRemoveMcp(server.displayName, e)). Errors are surfaced to the user.

Note: The redundant mutateMcpConfig() calls were correctly removed — useAddMcpServer and useRemoveMcpServer already call queryClient.invalidateQueries in their onSuccess handlers, so manual cache invalidation was unnecessary.


Remaining Issues (2 low-priority items from previous review)

1. Stale schema fields still presentpackages/shared/src/schemas/browser-context.ts:72–73

enabledMcpServers and customMcpServers remain in BrowserContextSchema but are never populated by any client code after this PR. These are dead schema fields:

// These lines should be removed:
enabledMcpServers: z.array(z.string()).optional(),
customMcpServers: z.array(CustomMcpServerSchema).optional(),

CustomMcpServerSchema is exported and now used by mcp-config.ts server-side, so the schema itself stays — but the two BrowserContextSchema fields can go. Fix this →

2. Stale chrome.storage.local data after migrationapps/agent/lib/mcp/migrateMcpToServer.ts:25–27

After a successful migration, the original local:mcpServers data is never cleared — it becomes orphaned dead storage. Consider clearing it on success:

if (response.ok) {
  await mcpServerStorage.setValue([])  // clear legacy data
  await storage.setItem(MCP_MIGRATED_KEY, true)
}

Fix this →


Minor Note (no action needed)

Dialog closes before async mutation resolvesAddCustomMCPDialog.tsx:142–183

onAddServer is typed as void, so onUrlSubmit/onStdioSubmit close the dialog before the mutation completes. Since addCustomServer now has a try/catch that shows a toast on failure, errors are no longer silent. This is acceptable for now — the toast provides sufficient feedback even after the dialog has closed.

Args whitespace splittingAddCustomMCPDialog.tsx:162

split(/\s+/) still breaks for arguments containing spaces (e.g., file paths). The separate command/args fields are a UX improvement over the previous single-field approach, but the limitation remains. The placeholder example (-y @modelcontextprotocol/server-filesystem /tmp) happens to work fine, but paths like /home/user/my project would silently produce wrong tokens. Worth documenting in the placeholder or help text.

Comment on lines +159 to +163
const onStdioSubmit = (values: StdioFormValues) => {
// Parse space-separated args string into array
const args = values.args?.trim()
? values.args.trim().split(/\s+/)
: undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

Args with spaces cannot be represented

Splitting the args string on whitespace (split(/\s+/)) breaks for any argument containing a space — the most common case being file paths (e.g. /Users/John Doe/project or C:\Program Files\my-server). Such a path would be silently split into multiple incorrect arguments, causing the stdio MCP server to fail at start-up with no clear error in the UI.

Consider replacing the single args string with a dynamic list of individual argument fields (similar to the env-var key/value pairs already used below), or at minimum support simple shell quoting so that "arg with spaces" is kept as one token.

// Current — breaks on spaces:
const args = values.args?.trim()
  ? values.args.trim().split(/\s+/)
  : undefined

// Safer alternative using shell-quote or a per-argument list UI:
// e.g. import { parse } from 'shell-quote'
// const args = values.args?.trim() ? parse(values.args.trim()) as string[] : undefined
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/agent/entrypoints/app/connect-mcp/AddCustomMCPDialog.tsx
Line: 159-163

Comment:
**Args with spaces cannot be represented**

Splitting the args string on whitespace (`split(/\s+/)`) breaks for any argument containing a space — the most common case being file paths (e.g. `/Users/John Doe/project` or `C:\Program Files\my-server`). Such a path would be silently split into multiple incorrect arguments, causing the stdio MCP server to fail at start-up with no clear error in the UI.

Consider replacing the single args string with a dynamic list of individual argument fields (similar to the env-var key/value pairs already used below), or at minimum support simple shell quoting so that `"arg with spaces"` is kept as one token.

```ts
// Current — breaks on spaces:
const args = values.args?.trim()
  ? values.args.trim().split(/\s+/)
  : undefined

// Safer alternative using shell-quote or a per-argument list UI:
// e.g. import { parse } from 'shell-quote'
// const args = values.args?.trim() ? parse(values.args.trim()) as string[] : undefined
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant