Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Summary
Add a plugin hook that lets plugins inject HTTP headers into outbound MCP tool calls, with the current sessionID (and callID, server, tool) available in scope. This enables per-session/per-request
header forwarding (e.g. X-Session-Id, X-User-Id) to remote MCP servers — something the existing static headers config and OAuth cannot express, because headers today are bound at transport construction
time.
Current behavior
- Remote MCP
headers(packages/opencode/src/config/mcp.ts:41) are a staticRecord<string,string>resolved once at config load via{env:...}/{file:...}substitution. - Those headers are passed to
StreamableHTTPClientTransport/SSEClientTransportviarequestInit.headersat construction time (packages/opencode/src/mcp/index.ts:335,342). After construction the
transport is shared across all sessions, so headers cannot vary per call. - The tool
execute(args)closure atpackages/opencode/src/mcp/index.ts:168does not carry any session context, so even a per-call template like{session:id}would have nothing to resolve against.
There is therefore no way today — via config, plugin, or otherwise — to attach the current sessionID (or any other per-call value) as a header on outbound MCP HTTP requests.
Proposed shape
A new plugin hook modeled on the existing chat.headers hook (packages/plugin/src/index.ts:256), but fired before each outbound MCP request:
"mcp.call.before"?: (
input: { server: string; tool: string; sessionID: string; callID: string },
output: { headers: Record<string, string> },
) => Promise<void>Plugin usage:
export default {
"mcp.call.before": async (input, output) => {
output.headers["X-Session-Id"] = input.sessionID
output.headers["X-Call-Id"] = input.callID
if (input.server === "my-server") {
output.headers["X-User-Id"] = process.env.USER_ID ?? ""
}
},
}Proposed behavior
- Hook fires once per client.callTool (and ideally per listTools / getPrompt / readResource too, for consistency — happy to scope down to callTool only in v1).
- output.headers is pre-populated with the static headers from config; the hook may add, override, or remove keys.
- Resulting headers are merged into the outbound HTTP request via a custom fetch passed to the SDK transports (requestInit.fetch), since the SDK does not expose per-call header injection. The transport remains
shared across sessions; only the fetch wrapper is per-call-aware. - Per-call context (sessionID, callID, server, tool) is propagated from the tool execute boundary in packages/opencode/src/session/prompt.ts via AsyncLocalStorage (or Effect Context), read inside the
wrapper-fetch. - If multiple plugins register the hook, they run in order and each sees the previous plugin's mutations to output.headers (matching existing hook semantics).
- Static config headers continues to work unchanged. {env:...}/{file:...} substitution still resolves at load time.
Why this would help
- Lets users correlate MCP server logs/observability with opencode sessions (one of the most common asks for any tool-running agent).
- Lets multi-tenant or per-user deployments forward identity to MCP servers without spinning up a separate opencode process per user.
- Matches the precedent already set by chat.headers for LLM provider HTTP calls — same surface, applied to MCP HTTP calls.
- Does not introduce a new config schema, new substitution token, or new dependency. Pure plugin addition + one fetch wrapper.
Out of scope (suggested follow-ups)
- Forwarding inbound REST request headers (from POST /session/:id/message) into MCP calls. That's a separate concern (header allowlist + request-scoped context on the REST side) and can be layered on top of
this hook later by a plugin once the hook exists. - Dynamic header generation via external helper script — already covered by [FEATURE]: add dynamic MCP headersHelper support for remote authentication #27161.
Notes
Happy to open a focused PR with the hook, the AsyncLocalStorage plumbing in session/prompt.ts, the fetch wrapper in mcp/index.ts, tests, docs, and generated SDK/types — if the core team confirms the hook name
and shape are acceptable.
Activity
github-actions commentedon May 18, 2026
This issue might be a duplicate of existing issues. Please check:
github-actions commentedon Jul 18, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.