You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(mcp): stream pinned transport under Bun (providers + self-hosted-private MCP) (#5901)
* fix(mcp): stream pinned transport via undici.request + redirect interceptor
Extends the Bun undici-streaming fix to createPinnedFetchWithDispatcher (providers,
A2A, self-hosted-private MCP over SSE). It now routes through undiciRequestAsResponse
like the guarded builder, so streaming bodies deliver under Bun. Unlike the guarded
path it has no followRedirectsGuarded wrapper (it's handed straight to provider SDKs),
so redirects are followed via undici's redirect interceptor composed onto the pinned
Agent — every hop still dispatches through the pinned connect.lookup (resolvedIP), so a
redirect can't escape to another address, matching the old fetch guarantee. secureFetchWithPinnedIP (raw Node http, tools path) is untouched.
* fix(mcp): honor redirect mode + drop cross-origin credentials on pinned fetch
Replaces the always-on redirect interceptor with redirect-mode-aware handling:
- redirect:'manual' returns the 3xx without following (detectMcpAuthType inspects it)
- redirect:'error' throws on a 3xx
- default 'follow' uses followRedirectsGuarded, which drops ALL headers on a
cross-origin hop (so a redirect can't disclose a provider api-key to another
origin — Greptile P1) and stamps the final response.url + redirected flag.
Extracts the shared Request-lift helper used by both guarded and pinned builders.
* fix(mcp): don't block private IP-literal URLs on the pinned fetch path
Routing the pinned fetch through followRedirectsGuarded added an initial
assertGuardedRedirectTarget check the old undici.fetch path never ran, which
would block a self-hosted MCP configured with a private IP-literal URL
(e.g. http://10.0.0.5:3000/mcp) — its own transport. The pinned path's callers
already validate the target and the private carve-out intentionally pins to a
private IP, so skip the initial-target check (validateInitialTarget: false) while
still validating every redirect hop. Adds a regression test.
* fix(mcp): carry redirect mode from a Request input in liftFetchArgs
liftFetchArgs copied method/headers/body/signal from a Request but omitted
redirect, so a Request({ redirect: 'manual' }) on the pinned path defaulted to
'follow' and was transparently followed. Copy input.redirect (explicit init still
wins). Adds a Request-input redirect-mode test.
* fix(mcp): permit the pinned IP as a redirect target (initial + hops), block other private IPs
Consolidates the pinned-path redirect policy into one mechanism. followRedirectsGuarded
took validateInitialTarget to skip the initial private-IP check, but per-hop checks still
blocked a self-hosted MCP redirecting to its own pinned private IP (e.g. a trailing-slash
301 to http://10.0.0.5/mcp/). Replace it with allowRedirectToIp: the pinned fetch permits
exactly its own validated IP as a target — initial URL and any hop that stays on it — while
every OTHER private target (e.g. the 169.254.169.254 metadata IP) stays blocked. Tests cover
the same-IP hop (followed) and the metadata-IP escape (still refused).
// double-cast-allowed: DOM RequestInfo/URL and undici fetch input types differ but are structurally compatible at runtime (Node's global fetch IS undici)
0 commit comments