Skip to content

Support remote MCP servers in the spec #7

Description

@bradgarropy

Over at Cloudflare, we are building a plugin registry based on the Open Plugins spec to support our OpenCode instances. As we continue, we are noticing some gaps in the spec that we could flesh out together.

Summary

The MCP Servers component spec documents only a local server schema (command, args, env, cwd). It does not define a remote (Streamable HTTP or SSE) with a url + headers (and optional OAuth).

The Model Context Protocol itself defines remote transports, and every major agent harness that consumes plugins/MCP — including OpenAI Codex, Cursor, and Windsurf — supports remote MCP servers today. As written, a plugin author cannot portably declare a remote MCP server in .mcp.json, even though their target host supports it.

Current spec (local-only)

Per the component page, the documented server schema is:

Field Required Type Description
command Yes string The executable to run. Supports ${PLUGIN_ROOT}.
args No string[] Command-line arguments.
env No object Environment variables.
cwd No string Working directory.

There is no url, type, headers, or OAuth field — so only local command-launched (stdio) servers can be expressed.

Why this matters

MCP defines transports beyond stdio (Streamable HTTP, SSE). Remote servers are increasingly the norm for hosted/multi-user tools (Figma, Linear, Sentry, etc.). Restricting the plugin spec to stdio:

  • Prevents portable packaging of remote MCP servers.
  • Forces each host to invent its own non-standard remote field, defeating the purpose of a shared format.
  • Diverges from what conformant hosts already implement.

Prior art: harnesses that support remote MCP

OpenAI Codex — supports Streamable HTTP servers with bearer-token and OAuth auth.
Docs: https://developers.openai.com/codex/mcp and https://developers.openai.com/codex/config-reference

[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
bearer_token_env_var = "FIGMA_OAUTH_TOKEN"
http_headers = { "X-Figma-Region" = "us-east-1" }

Cursor — supports three transports (stdio, SSE, Streamable HTTP); remote uses url + headers, with OAuth (including static OAuth client credentials).
Docs: https://cursor.com/docs/mcp

{
  "mcpServers": {
    "server-name": {
      "url": "http://localhost:3000/mcp",
      "headers": { "API_KEY": "value" }
    }
  }
}

Static OAuth form for remote servers:

{
  "mcpServers": {
    "oauth-server": {
      "url": "https://api.example.com/mcp",
      "auth": {
        "CLIENT_ID": "your-oauth-client-id",
        "CLIENT_SECRET": "your-client-secret",
        "scopes": ["read", "write"]
      }
    }
  }
}

Windsurf (Cascade) — supports stdio, Streamable HTTP, and SSE, with OAuth per transport; remote uses serverUrl/url + headers.
Docs: https://docs.devin.ai/desktop/cascade/mcp

{
  "mcpServers": {
    "remote-http-mcp": {
      "serverUrl": "<your-server-url>/mcp",
      "headers": { "API_KEY": "value" }
    }
  }
}

OpenCode — supports type: "remote" with url, headers, and OAuth.
Docs: https://opencode.ai/docs/mcp-servers and schema https://opencode.ai/config.json

{
  "mcp": {
    "my-remote-mcp": {
      "type": "remote",
      "url": "https://my-mcp-server.com",
      "headers": { "Authorization": "Bearer MY_API_KEY" },
      "oauth": {}
    }
  }
}

Proposal

Extend the MCP Servers component to define a remote server form alongside the existing local form, aligned with the MCP transport spec.

Local (existing):

Field Required Type Description
command Yes string Executable to run. Supports ${PLUGIN_ROOT}.
args No string[] Command-line arguments.
env No object Environment variables.
cwd No string Working directory.

Remote (proposed):

Field Required Type Description
url Yes string Endpoint for a Streamable HTTP or SSE MCP server. Supports ${PLUGIN_ROOT} where applicable.
type No string Transport hint: "http" / "sse" (or "remote"). Hosts MAY infer from url.
headers No object Static headers (e.g. Authorization). Values support env interpolation.
oauth No object OAuth configuration for servers that require it (see below).

oauth object (proposed):

Field Required Type Description
clientId No string OAuth 2.0 Client ID. If omitted, hosts MAY attempt Dynamic Client Registration (RFC 7591).
clientSecret No string OAuth 2.0 Client Secret (confidential clients only).
scopes No string[] Scopes to request. If omitted, hosts SHOULD discover via /.well-known/oauth-authorization-server.

A server entry is local if it has command, remote if it has url. Hosts MUST ignore transports they do not support (consistent with the existing conformance language: "Tools MUST ignore component types they do not support").

Example combining both:

{
  "mcpServers": {
    "plugin-db": {
      "command": "${PLUGIN_ROOT}/servers/db-server",
      "args": ["--config", "${PLUGIN_ROOT}/config.json"]
    },
    "hosted-api": {
      "url": "https://api.example.com/mcp",
      "headers": { "Authorization": "Bearer ${env:API_TOKEN}" },
      "oauth": {
        "clientId": "${env:MCP_CLIENT_ID}",
        "scopes": ["read", "write"]
      }
    }
  }
}

Recommendations

  • Canonical field naming: standardize on url (used by Codex, Cursor, and OpenCode). Accept serverUrl as an alias for Windsurf compatibility, but document url as canonical.
  • Transport discriminator: prefer inference from the presence of command (local) vs url (remote), with an optional type hint for explicitness.
  • OAuth: keep the OAuth object minimal and host-extensible — clientId, clientSecret, scopes cover the common cases across Codex/Cursor/OpenCode, while leaving room for host-specific extensions.
  • Interpolation: recommend standardizing ${env:VAR} (and optionally ${file:...}) since hosts already converge on similar syntax.

Open questions

  • Should type be required for remote servers, or always inferable?
  • How prescriptive should the OAuth object be vs. deferring to host-specific extensions?
  • Should the spec mandate a standard interpolation syntax, or leave it host-defined?

Offer

I'm happy to submit a PR implementing this spec change if you are open to the direction!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions