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
- 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.
- 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 initialize → notifications/initialized → tools/list / tools/call flow.
- 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>).
- 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
}
]
}
}
- Status must distinguish "remote server reachable" from "auth works" — call
tools/list and report tool count like the stdio path.
- 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.
- No gateway plumbing changes required —
integration-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
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: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
https://mcp.cloudflare.com/mcpexposes a curated, small toolset over Streamable HTTP — far better suited for an agent than Cloudflare's 3,204-endpoint OpenAPI spec.api.githubcopilot.com/mcp/), Linear (mcp.linear.app/mcp), etc. — a real platform improvement, not a one-off.Proposed Approach
server/lib/mcp-client.tsto handletransport.mode === "mcp"with aurlconfig in addition to the existingcommandconfig. Reject connections that have neither.POSTJSON-RPC to the URL, accept bothapplication/jsonandtext/event-streamresponses, parse SSEdata:frames the same way the stdio path parses newline-delimited JSON. Reuse the existinginitialize→notifications/initialized→tools/list/tools/callflow.connection.auth.schemesvia the sameresolveAuth+getConnectionSecretspath used byintegration-execute.tsand attach headers (e.g.Authorization: Bearer <token>).{ "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 } ] } }tools/listand report tool count like the stdio path.integration-gateway.tsalready routesmcpmode throughmcpListTools/mcpCallTool/mcpStatus. The summary string insummaryOfis already correct.Out of Scope
tools/listandtools/callround-trips.authorization_code. We start with static-bearer (API tokens) and add OAuth as a follow-up if a real need appears.Acceptance Criteria
mcpListTools/mcpCallTool/mcpStatuswork whentransport.config.urlis set.Authorizationheader is injected fromconnection.auth.schemesexactly as it is for OpenAPI/REST.application/jsonform is also accepted.integrations.jsonandtools/call/statuswork end-to-end againstmcp.cloudflare.com.