Roster entries cannot carry request headers, so an authenticated remote MCP has to put its key in the URL #778

Closed
opened 2026-08-14 07:41:17 +00:00 by coilyco-ops · 0 comments
Member

rosterEntry in internal/community/mcp_roster.go carries transport, baseUrl, url, command, args, and env. There is no headers field, and unknown keys are ignored, so writing one parses cleanly and does nothing.

Remote MCP servers already work. eco is a public HTTPS endpoint today and expandRoster resolves ${VAR} from the environment, so a secret reaches an entry without being written into it. What is missing is only the ability to send that secret as a request header.

The consequence is that an authenticated remote MCP has to smuggle its credential into the query string, for example https://mcp.exa.ai/mcp?exaApiKey=${VAR}. That is worse than a header for two reasons:

  1. IdentifierGuard.addEndpoint returns early unless the URL has an explicit port, and even then it guards parsed.Host alone. A portless HTTPS endpoint contributes nothing to the forbidden set, so a key in the query string is not covered by the reply guard.
  2. A query string is the part of a URL most likely to be carried into a log, a span attribute, or an error message intact.

Proposal: add Headers map[string]string to rosterEntry and MCPServerDefinition, expand its values through expandRoster the way Env already is, reject it on stdio the way env is rejected on HTTP, and apply it to the streamable and SSE transports. Feed the values through IdentifierGuard.addOpaque so a credential delivered this way is unsayable in a reply, matching how the Discord token is handled.

This unblocks pointing a lane at any authenticated hosted MCP with one roster entry and one ExternalSecret key, rather than standing up a wrapping service per vendor.

`rosterEntry` in `internal/community/mcp_roster.go` carries `transport`, `baseUrl`, `url`, `command`, `args`, and `env`. There is no `headers` field, and unknown keys are ignored, so writing one parses cleanly and does nothing. Remote MCP servers already work. `eco` is a public HTTPS endpoint today and `expandRoster` resolves `${VAR}` from the environment, so a secret reaches an entry without being written into it. What is missing is only the ability to send that secret as a request header. The consequence is that an authenticated remote MCP has to smuggle its credential into the query string, for example `https://mcp.exa.ai/mcp?exaApiKey=${VAR}`. That is worse than a header for two reasons: 1. `IdentifierGuard.addEndpoint` returns early unless the URL has an explicit port, and even then it guards `parsed.Host` alone. A portless HTTPS endpoint contributes nothing to the forbidden set, so a key in the query string is not covered by the reply guard. 2. A query string is the part of a URL most likely to be carried into a log, a span attribute, or an error message intact. Proposal: add `Headers map[string]string` to `rosterEntry` and `MCPServerDefinition`, expand its values through `expandRoster` the way `Env` already is, reject it on stdio the way `env` is rejected on HTTP, and apply it to the streamable and SSE transports. Feed the values through `IdentifierGuard.addOpaque` so a credential delivered this way is unsayable in a reply, matching how the Discord token is handled. This unblocks pointing a lane at any authenticated hosted MCP with one roster entry and one ExternalSecret key, rather than standing up a wrapping service per vendor.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#778
No description provided.