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!
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 aurl+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:
command${PLUGIN_ROOT}.argsenvcwdThere 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:
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
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"withurl,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):
command${PLUGIN_ROOT}.argsenvcwdRemote (proposed):
url${PLUGIN_ROOT}where applicable.type"http"/"sse"(or"remote"). Hosts MAY infer fromurl.headersAuthorization). Values support env interpolation.oauthoauthobject (proposed):clientIdclientSecretscopes/.well-known/oauth-authorization-server.A server entry is local if it has
command, remote if it hasurl. 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
url(used by Codex, Cursor, and OpenCode). AcceptserverUrlas an alias for Windsurf compatibility, but documenturlas canonical.command(local) vsurl(remote), with an optionaltypehint for explicitness.clientId,clientSecret,scopescover the common cases across Codex/Cursor/OpenCode, while leaving room for host-specific extensions.${env:VAR}(and optionally${file:...}) since hosts already converge on similar syntax.Open questions
typebe required for remote servers, or always inferable?Offer
I'm happy to submit a PR implementing this spec change if you are open to the direction!