Skip to content

Support remote/HTTP MCP servers in the integrations gateway #229

Description

@germanescobar

Summary

The integrations gateway (server/lib/mcp-client.ts) currently supports MCP servers over stdio only. Cloudflare (and many other vendors) ship MCP servers over Streamable HTTP at a URL, and our stdio-only client rejects them with:

Remote MCP servers aren't supported yet — configure a command.

This issue adds HTTP/remote support so Controller can register any URL-based MCP server. Cloudflare (Code Mode) will be the first consumer (tracking issue will follow).

Why

  • Code Mode at https://mcp.cloudflare.com/mcp exposes a curated, small toolset over Streamable HTTP — far better suited for an agent than Cloudflare's 3,204-endpoint OpenAPI spec.
  • The same code path unblocks GitHub's MCP (api.githubcopilot.com/mcp/), Linear (mcp.linear.app/mcp), etc. — a real platform improvement, not a one-off.
  • Bearer-token auth model is identical to our existing OpenAPI integrations, so it fits the existing secret-injection machinery.

Proposed Approach

  1. Extend server/lib/mcp-client.ts to handle transport.mode === "mcp" with a url config in addition to the existing command config. Reject connections that have neither.
  2. Streamable HTTP transport per the MCP spec: POST JSON-RPC to the URL, accept both application/json and text/event-stream responses, parse SSE data: frames the same way the stdio path parses newline-delimited JSON. Reuse the existing initializenotifications/initializedtools/list / tools/call flow.
  3. Auth injection at the HTTP boundary: resolve connection.auth.schemes via the same resolveAuth + getConnectionSecrets path used by integration-execute.ts and attach headers (e.g. Authorization: Bearer <token>).
  4. Connection-shaped config:
    {
      "name": "Cloudflare",
      "transport": { "mode": "mcp", "config": { "url": "https://mcp.cloudflare.com/mcp" } },
      "auth": {
        "schemes": [
          {
            "acquisition": "static",
            "attachment": { "kind": "header", "name": "Authorization", "prefix": "Bearer " },
            "hasSecret": true
          }
        ]
      }
    }
  5. Status must distinguish "remote server reachable" from "auth works" — call tools/list and report tool count like the stdio path.
  6. Tests: add unit tests for the HTTP transport (success, 401, non-2xx, SSE framing, auth header injection). The stdio path is currently untested — leave that alone, but at least cover the new path.
  7. No gateway plumbing changes requiredintegration-gateway.ts already routes mcp mode through mcpListTools / mcpCallTool / mcpStatus. The summary string in summaryOf is already correct.

Out of Scope

  • Resumability / event IDs (MCP Streamable HTTP optional). Not needed for tools/list and tools/call round-trips.
  • OAuth flows for MCP servers that require authorization_code. We start with static-bearer (API tokens) and add OAuth as a follow-up if a real need appears.
  • Per-server session caching / re-use. The current model spawns a session per gateway op; for HTTP we'll reuse a single session per integration per process, since spawning a TCP connection per call would be needlessly slow.

Acceptance Criteria

  • mcpListTools / mcpCallTool / mcpStatus work when transport.config.url is set.
  • Authorization header is injected from connection.auth.schemes exactly as it is for OpenAPI/REST.
  • SSE responses are parsed correctly; the JSON-only application/json form is also accepted.
  • Existing stdio MCP path is unchanged (new tests prove no regression).
  • Cloudflare (Code Mode) can be registered as a follow-up integration in integrations.json and tools / call / status work end-to-end against mcp.cloudflare.com.

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