Motivation
MCPClient accepts headers as a static record, which is placed into the transport's
requestInit once, when the connection is opened. A bearer token that has to be refreshed — or
any value belonging to the request in flight — cannot be expressed that way, and a long-lived
connection shared across users would replay whatever was captured when it was opened.
FoundryToolbox already solves this: it passes a fetch hook to the transport and builds the
headers per request, attaching a cached token and the call id of the request in flight. Its class
documentation spells out why this cannot be a static header set — the connection outlives any one
user, so a credential captured at connect time would be sent on behalf of whoever comes later.
That pattern should be the supported one rather than something each integration rebuilds.
Proposal
- Widen
headers to
Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>),
documenting that the provider is called per request, so token refresh works without a custom
transport.
- Move the implementation from
requestInit.headers to the fetch hook, wrapping any
caller-supplied fetch on the outside so a replacement cannot drop the headers.
- Attach injected headers only to requests whose origin (scheme, host, port) matches the
configured URL, so they are not carried to a third-party origin across a redirect. Python
applies exactly this rule to its header provider. Node strips authorization on a cross-origin
redirect on its own, but custom headers are not covered, and the ones this framework injects
today include a call id and W3C trace context.
- Rebase
FoundryToolbox onto the shared implementation.
Acceptance criteria
- A dynamic provider's headers appear on each request, against a mock server.
- An expiring token is refreshed without writing a
transportFactory.
- Injected headers are not attached to a cross-origin redirect target.
- The existing
FoundryToolbox authorization tests still pass against the shared path.
Motivation
MCPClientacceptsheadersas a static record, which is placed into the transport'srequestInitonce, when the connection is opened. A bearer token that has to be refreshed — orany value belonging to the request in flight — cannot be expressed that way, and a long-lived
connection shared across users would replay whatever was captured when it was opened.
FoundryToolboxalready solves this: it passes afetchhook to the transport and builds theheaders per request, attaching a cached token and the call id of the request in flight. Its class
documentation spells out why this cannot be a static header set — the connection outlives any one
user, so a credential captured at connect time would be sent on behalf of whoever comes later.
That pattern should be the supported one rather than something each integration rebuilds.
Proposal
headerstoRecord<string, string> | (() => Record<string, string> | Promise<Record<string, string>>),documenting that the provider is called per request, so token refresh works without a custom
transport.
requestInit.headersto thefetchhook, wrapping anycaller-supplied
fetchon the outside so a replacement cannot drop the headers.configured URL, so they are not carried to a third-party origin across a redirect. Python
applies exactly this rule to its header provider. Node strips
authorizationon a cross-originredirect on its own, but custom headers are not covered, and the ones this framework injects
today include a call id and W3C trace context.
FoundryToolboxonto the shared implementation.Acceptance criteria
transportFactory.FoundryToolboxauthorization tests still pass against the shared path.