Feature request: per-request dynamic headers for Streamable HTTP MCP clients #7046
Replies: 3 comments 1 reply
|
I looked this up in the pinned SDK and in Findings, all verified against 1. The SDK is already request-scoped. The staleness is entirely on the harness side. The transport stores the init object once and re-reads it on every outbound request:
So both POSTs and the stream re-read the live object every time — exactly the "all HTTP requests, including initialization and reconnects" requirement. An object that is mutated between requests is picked up by the next one, with no SDK change. What freezes it in dsh is simply that no such object is ever retained:
Net effect: MCP headers are load/generation-scoped, not request-scoped. (Your workaround works for the same reason in reverse: 2. There is no Cordis hook, and the transport is deliberately unreachable.
So a third-party plugin cannot supply 3. Your merge/overwrite question has a concrete answer, and it matters for
if (token) headers['Authorization'] = `Bearer ${token}`
if (this._protocolVersion) headers['mcp-protocol-version'] = this._protocolVersion
const extraHeaders = normalizeHeaders(this._requestInit?.headers)
return new Headers({ ...headers, ...extraHeaders }) // user headers win…but
So today: Second consequence: because the headers are read from the stored object, a provider that throws must fail that request rather than leave the previous value in place — otherwise you get exactly the silent stale-header reuse you want to eliminate. Minimal change I would propose (all inside
I would rather not prepare a patch until the config-provider shape is settled, since |
|
Thank you for checking the pinned SDK and harness sources. Your explanation also confirms why our current reconnect workaround works. I omitted an important part of our deployment model in the original post. DSH is not embedded in our web process; we run it as a server-side agent runtime behind our own gateway: The browser headers terminate at our gateway. The next hop is ACP over stdio, so there is no HTTP request header for DSH to inherit. We need a supported way to carry an allowlisted, invocation-scoped context from the ACP caller into DSH, and then apply it to outbound MCP requests. We do not want to forward all browser headers implicitly. Our current workaround is:
This preserves the trace ID but makes the header connection-generation scoped and forces lifecycle churn. For our server-side case, the complete upstream capability therefore seems to need two pieces:
{
"ai.deepseek.dsh/trace-context": {
"traceparent": "00-...-...-01",
"tracestate": "..."
}
}DSH could bind this data to the prompt/tool execution context, and the in-process request-header provider could read it immediately before each outbound MCP request. A function-valued config alone would help in-process plugins, but an external ACP client cannot serialize or install that function, and it still needs a supported source for the current invocation context. I agree that no SDK fork is necessary. One concurrency concern with mutating a retained shared For precedence, my preferred default would be:
The Java gateway can provide the parent trace context for the turn. If DSH wants a distinct span for each tool call, that child span must be created inside DSH because the tool is selected and executed there. Is there already an ACP invocation metadata extension we should use for the first hop? If not, would a namespaced per-prompt |
|
Two answers: the protocol has the slot, dsh has no reader — and the second half of your model is better supported than you assumed, because the injection point you propose already exists in the SDK dsh pins. First hop — ACP invocation metadata
_meta?: { [key: string]: unknown } | null;with the spec's own comment: "The _meta property is reserved by ACP to allow clients and agents to attach additional metadata to their interactions. Implementations MUST NOT make assumptions about values at these keys." So a namespaced But dsh never reads it:
There is also no extension-method escape hatch to reach for instead: the bridge registers nine handlers ( So: the protocol offers exactly the carrier you want, and today nothing end-to-end can put a value in it or read one out. That makes "namespaced per-prompt Second hop — the outbound providerYour read of the SDK is correct, and slightly pessimistic in a useful direction: return new StreamableHTTPClientTransport(
new URL(config.url),
{ requestInit: { headers: config.headers } },
)and /** Custom fetch implementation used for all network requests. */
fetch?: FetchLike; // (url: string | URL, init?: RequestInit) => Promise<Response>So "a DSH-owned fetch wrapper could await the provider and construct a fresh
Precedence — I agree with your four defaults, and #1 has a hard mechanical reasonThe transport does not only set
What cannot be done from a plugin (I checked, since it would be the cheapest contribution)I looked for a plugin-mountable path and there is none, on either hop — mechanically:
⇒ both halves are core work. One adjacent piece is plugin-reachable: the per-tool-call child span. Finally, a note on why your workaround has to be connection-scoped: |
Uh oh!
There was an error while loading. Please reload this page.
Problem
DeepSeek Harness supports configuring headers for HTTP MCP servers, including through ACP session configuration. This works well for static values, but some headers must be generated from the active invocation context immediately before each outbound MCP HTTP request.
Examples include:
traceparentandtracestateA header captured when an MCP transport or session is created can become stale. With reused transports or concurrent sessions, reusing a request-scoped header may also attach a call to the wrong trace or tenant.
Current workaround
We currently pass a fresh
traceparentthroughmcpServers[].headersonsession/neworsession/resume, and close the turn-scoped runtime afterward. This uses existing DSH APIs and preserves end-to-end trace IDs, but it requires lifecycle churn and cannot select the exact active span for every individual MCP HTTP request.Proposed capability
Could DSH expose an async per-request header provider or hook for Streamable HTTP MCP clients, for example:
Useful context would include:
The returned headers would be merged case-insensitively immediately before the final HTTP request is sent. Static
mcpServers[].headersshould continue to work, with documented precedence and protection for protocol-managed headers.Requirements
Authorization, MCP protocol headers, and user headersRelated ecosystem patterns
This pattern exists in other agent/MCP implementations:
mcp_request_headershook for per-request W3Ctraceparentpropagation.headerProvider.Would the maintainers be open to a request-level hook/provider in the official MCP client? If there is already a preferred Cordis hook or transport extension point for this, guidance would be appreciated.
All reactions