feat(chat): pick MCP servers with /mcp in the chat composer

Closed
AV
12d ago
+2878-31

Links

  • Linear: SUPER-2629 (phase 1: pick configured servers, install/connect plugins in place)

Summary

  • /mcp in the ACP chat composer opens a searchable list of the MCP servers the chat's agent has in this workspace. A pick becomes a chip; on send, the chips left in the message become a one-line hint that tells the agent which servers to use.
  • Superset plugin servers that aren't installed or have no connected account show Install / Connect, which runs the Plugins page's existing install and connect flow and reopens the list.
  • New host-service query agentTooling.listMcpServers reads Claude Code and Codex config files for a workspace. It reads files only: nothing is started or contacted.

Why / Context

Agents in a chat only see the MCP servers in their own config files, and the composer had no way to see or steer them: no picker, and mcpServers: [] on every session/new. This adds a picker in Superset's own UI, and a way to reach the plugin install and connect flow from where the user notices a server is missing.

How It Works

  • Discovery (host-service): agentTooling.listMcpServers({ workspaceId, agent, refresh? }).
    • Claude Code: user scope in ~/.claude.json (or inside a custom CLAUDE_CONFIG_DIR), local scope in projects[worktree], and project .mcp.json files walked up to the repo root. Claude's per-project disabledMcpServers list applies.
    • Codex: config.toml in CODEX_HOME, plus project .codex/config.toml, read line by line.
    • Plugin servers: entries that point at Superset's plugin proxy (matched by path) or at a catalog plugin (matched by name, then endpoint) are marked with the plugin. Each catalog plugin server the asking agent doesn't have yet is added as not-configured.
    • Cache: 30 s per workspace, agent and config path; refresh: true reads again.
  • Composer (chat-ui): panels gain an optional bare variant with a close() control, so a command can open its own picker above the editor. Existing render() callers are unchanged. Command ranking now ignores the leading / and puts an exact match first; without that, Claude's own /mcp:<server>:<prompt> commands outranked /mcp.
  • Desktop:
    • Picker: McpServerPicker (cmdk), with rows ordered available → install/connect → unavailable.
    • Connection state: joined from the account catalog (plugins.list) the app already loads. Plugin rows only appear for plugins the Plugins page offers.
    • Chips: serialize to @mcp/<name> (scoped only when the same agent has two servers of that name), copy as that text, and come back from stored drafts through AgentComposer's findChips.
    • Install feedback: usePluginMutations takes announceInstall: false, so the picker reports an install with one toast ("… is installed — Its tools reach the agent in your next chat").

Manual QA Checklist

Driven over CDP in the dev app (Claude chat in this worktree, real config on the dev machine):

  • /mcp is first in the slash menu, above Claude's /mcp:notion:… prompts; Enter opens the picker with focus in the search box
  • The list shows this machine's Claude user servers as Available, plugins as Install/Connect, and Codex servers disabled ("Configured for Codex")
  • Search filters (lin → linear rows only); Arrow keys skip disabled rows; Enter picks; Esc and outside click close
  • A picked chip shows the server's icon and copies as @mcp/github …
  • The chip comes back after the composer remounts (switched workspaces and back; editor element confirmed new)
  • Sent message carries MCP context: Use the configured MCP server "github" when relevant to this request. and the agent acted on it
  • Connect on a plugin with no account opens the "Connect " dialog; stopped at the browser hand-off, and closing the dialog reopens the list with fresh data
  • "Manage plugins…" opens the Plugins page
  • Install → success toast: not triggered live (it would install a real plugin); covered by code path only

Testing

  • bun test in packages/host-service src/trpc/router/agent-tooling (discovery: scopes, disabled list, Codex tables, plugin matching, refresh)
  • bun test --isolate on the desktop Composer folder (row model, chip tokens, context line)
  • bun test in packages/chat-ui (command ranking)
  • bun run typecheck in apps/desktop, packages/host-service, packages/chat-ui
  • biome check on every changed file
  • bun run check:i18n (21 new strings, translated in all 16 non-English locales)

Design Decisions

  • No live health check. "Needs authentication" would need claude mcp list, which starts every stdio server and contacts every remote one each time it runs. The picker therefore can't tell an authorized server from an unauthorized one: live, it showed github as Available and the agent then said the server wasn't authorized in that session.
  • Servers are not passed into session/new. It stays mcpServers: []; the agent already loads its own config, and passing them could register a server twice.
  • The context line is plain text in the message. It shows in the sent bubble, as the request was written.

Known Limitations

  • Servers attached to a claude.ai account (e.g. "claude.ai Notion") are not in any config file, so they don't appear.
  • A server added by Connect/Install applies from the next chat session; live pickup by a running agent is unverified, and the UI says "next chat".
  • Seen during QA, not investigated: after the dev app reloaded, the earlier sent message rendered @mcp/github as raw text instead of a chip.

Compatibility

  • listMcpServers is a new host-service procedure, and nothing existing changed. A desktop talking to an older host shows "Could not read MCP servers." in the picker; nothing else is affected.

Summary by cubic

Adds /mcp to the ACP chat composer so users can see the MCP servers their agent has in the workspace and pick which ones to use. Picks become chips that carry into the sent message as a context line for the agent. Also adds agentTooling.listMcpServers, which reads only Claude Code and Codex config files (nothing is started or contacted), and fixes command ranking so /mcp ranks ahead of agent commands like /mcp:notion:....

Key Details

  • Plugin servers that aren't installed or have no connected account show Install / Connect inline, run the existing Plugins page flow, and reopen the picker with fresh data.
  • usePluginMutations now accepts announceInstall: false; the picker reports installs with one toast saying the tools reach the agent in the next chat.
  • Composer panels gain a bare variant with a close control so a command can open its own picker above the editor.
  • Chips serialize as @mcp/<name> (scoped only when the same agent has two servers of that name), copy as that text, and restore from stored drafts.
  • Discovery reads CLAUDE_CONFIG_DIR and CODEX_HOME from the agent's launch env, so config values the agent sees match what the picker shows.
  • Codex project config servers show as untrusted unless the project or its repo is trusted by Codex; only the nearest project definition of a name is kept.
  • The picker counts only what it installed itself; an install on the account no longer counts as one on this machine.
  • Sending replaces an MCP context line a recalled message already carries; /mcp is offered only to Claude and Codex chats.

Summary by CodeRabbit

  • New Features
    • Browse and search MCP servers from the chat composer, with availability, scope, and configuration status displayed.
    • Add servers to messages as chips, connect servers, or install plugins from the picker. Refresh results or open plugin management as needed.
    • MCP server references are included as context when sending a message.
  • Improvements
    • Plugin installation success notifications can be suppressed where appropriate.
    • MCP server controls and related messages are available in additional languages.