Skip to content

feat(mcp): MCP Hub — stdio bridge + curated server catalog for zero-config MCP #310

@tmustier

Description

@tmustier

Summary

The current MCP integration is a minimal HTTP-only gateway: users manually add an MCP server URL + optional bearer token, and the agent calls it via JSON-RPC over HTTP. This works for the rare remote/hosted MCP server, but most real-world MCP servers are stdio-based (Gmail, Slack, Granola, Brave Search, filesystem, etc.) and unreachable from the Excel WebView.

For non-technical AI power users — the target audience — the current UX is a dead end. They can't use the MCPs they know from Claude Desktop or Claude Code.

Goal: make MCP a "just works" experience where enabling a popular MCP server requires minimal setup (ideally just an API key or OAuth), no extra processes beyond the CORS proxy, and no restart.

Current architecture

Excel WebView (browser)
    │
    │  POST JSON-RPC to server URL
    │
    ├── direct ──────────────→  Remote HTTP MCP server  (rare)
    │
    └── via CORS proxy ──────→  localhost:3003 ──→ Remote HTTP MCP server
        (proxy.enabled=true)     cors-proxy-server.mjs

Limitations:

  1. HTTP-only transport — can't reach stdio MCP servers (the vast majority)
  2. CORS proxy host allowlistDEFAULT_ALLOWED_TARGET_HOSTS only includes LLM provider domains; arbitrary MCP server hosts are blocked
  3. No hot-reload — adding a server mid-session requires config + proxy restart
  4. Manual-only setup — user must know the exact server URL and token format

Proposed architecture: MCP Hub in the CORS proxy

Extend cors-proxy-server.mjs into an MCP hub that manages stdio server lifecycles:

Excel WebView
    │
    │  POST /mcp/{server-id}/rpc      (tool calls)
    │  GET  /mcp/servers               (discovery)
    │
    ▼
┌────────────────────────────────────────┐
│  localhost:3003  (existing proxy)      │
│                                        │
│  Existing:  /?url=...  (CORS proxy)   │
│                                        │
│  New:  MCP Hub layer                   │
│    ┌──────────┐  ┌──────────┐         │
│    │ gmail    │  │ slack    │  ...    │
│    │ (stdio)  │  │ (stdio)  │         │
│    └──────────┘  └──────────┘         │
│                                        │
│  Config:  mcp-servers.json             │
│  (file-watched for hot-reload)         │
└────────────────────────────────────────┘

Config format (mcp-servers.json)

{
  "gmail": {
    "command": "npx",
    "args": ["-y", "@anthropic/gmail-mcp"],
    "env": { "GMAIL_OAUTH_CREDENTIALS": "~/.config/gmail-oauth.json" }
  },
  "slack": {
    "command": "npx",
    "args": ["-y", "@anthropic/slack-mcp"],
    "env": { "SLACK_BOT_TOKEN": "xoxb-..." }
  },
  "brave-search": {
    "command": "npx",
    "args": ["-y", "@anthropic/brave-search-mcp"],
    "env": { "BRAVE_API_KEY": "..." }
  }
}

Key design decisions

Lazy spawn: Don't start all servers on boot. Spawn a child process on first connect or tool call. This keeps startup fast and avoids wasted processes.

File-watch hot-reload: fs.watch("mcp-servers.json") detects changes → diffs config → spawns new servers, kills removed ones. No proxy restart needed to add/remove servers mid-session.

Stdio ↔ HTTP translation: The hub receives HTTP JSON-RPC from the WebView, writes it as newline-delimited JSON to the child's stdin, reads the response from stdout, and returns it as HTTP.

CORS is a non-issue: All /mcp/ routes are loopback through the proxy the user already trusts. No host allowlist changes needed.

Management API (stretch): POST /mcp/servers / DELETE /mcp/servers/{id} so the Excel UI can add/remove servers without the user touching JSON.

Ship a curated mcp-servers.example.json

Pre-configure ~8 popular servers (commented out / disabled by default) so users just uncomment and add credentials:

Category Server Package Auth
Email Gmail @anthropic/gmail-mcp OAuth
Files Google Drive @anthropic/gdrive-mcp OAuth
Calendar Google Calendar @anthropic/gcalendar-mcp OAuth
Chat Slack @anthropic/slack-mcp Bot token
Search Brave Search @anthropic/brave-search-mcp API key
Notes Notion @anthropic/notion-mcp Integration token
Files Filesystem @anthropic/filesystem-mcp None
Data SQLite @anthropic/sqlite-mcp None

(Exact package names TBD — verify against the MCP server registry before implementation.)

Phases

Phase 1 — Stdio bridge in the proxy

  • /mcp/{server-id}/rpc route in cors-proxy-server.mjs
  • Child process spawn/lifecycle management (lazy start, graceful shutdown)
  • mcp-servers.json config loading
  • fs.watch hot-reload
  • GET /mcp/servers discovery endpoint
  • Ship mcp-servers.example.json with curated popular servers

Phase 2 — Wire Excel client

  • mcp tool auto-discovers servers from GET /mcp/servers (bridge-managed servers appear alongside manually-added HTTP servers)
  • /integrations UI shows bridge-managed servers with status (running/stopped/error)
  • "Test connection" triggers lazy spawn + MCP handshake

Phase 3 — Management API + zero-JSON UX

  • POST /mcp/servers / DELETE /mcp/servers/{id} for CRUD from Excel UI
  • Browse/search a curated server catalog from the /integrations overlay
  • One-click enable with inline credential prompts
  • Auto-install npm packages on first use

Out of scope (for now)

  • OAuth broker in the proxy (users handle OAuth setup per-server for now)
  • SSE / Streamable HTTP transport for remote servers (existing HTTP path covers this)
  • Sandboxing / capability restrictions on stdio servers

Context

  • Current MCP tool: src/tools/mcp.ts (HTTP JSON-RPC only)
  • Config store: src/tools/mcp-config.ts (mcp.servers.v1 settings key)
  • CORS proxy: scripts/cors-proxy-server.mjs
  • Proxy target policy: scripts/proxy-target-policy.mjs
  • Integration catalog: src/integrations/catalog.ts
  • UI overlay: src/commands/builtins/integrations-overlay.ts + integrations-overlay-elements.ts
  • MCP probe (test connection): src/commands/builtins/integrations-overlay-mcp-probe.ts
  • Agent Skill: skills/mcp-gateway/SKILL.md
  • Docs: docs/integrations-external-tools.md, docs/agent-skills-interop.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions