Problem
Every outbound HTTP call uses bare fetch with no timeout, no redirect policy, and no response-size cap on the network read:
apiRequest — packages/mcp-core/src/api-client.ts
exchangeCodeForToken — packages/mcp-core/src/auth/oauth.ts
fetch_docs — packages/mcp/src/tools/docs.ts (caps the stripped output at MAX_BYTES, but await res.text() reads the whole body into memory first, and follows redirects by default)
In fetch_docs the host comes from a fixed enum, but slug is concatenated unvalidated and redirects are followed silently, so a product host that 30x-redirects offsite would be followed without the host allowlist applying to the final URL.
Why it matters
- A hung or slow upstream (
api.ferrlabs.com, a product marketing site, the OAuth exchange) blocks the tool call indefinitely — over stdio that wedges the whole MCP session with no error.
- Following redirects past the vetted host defeats the point of the
fetch_docs enum allowlist (mild SSRF surface).
res.text() with no streamed size guard lets a large/hostile response balloon memory before the post-hoc slice.
Proposed approach
- Add an
AbortController timeout (e.g. 15s, configurable) to all fetch calls; map abort to a clear tool error.
- In
fetch_docs, set redirect: 'manual' (or re-check the final URL host against the allowlist) and reject the slug if it contains ://, .., or a leading //.
- Cap the read for
fetch_docs by reading the stream incrementally and stopping at MAX_BYTES.
Acceptance criteria
- A slow upstream produces a timeout error instead of hanging.
fetch_docs does not follow a redirect to a non-allowlisted host.
- Tests cover timeout and redirect-rejection paths.
Problem
Every outbound HTTP call uses bare
fetchwith no timeout, noredirectpolicy, and no response-size cap on the network read:apiRequest—packages/mcp-core/src/api-client.tsexchangeCodeForToken—packages/mcp-core/src/auth/oauth.tsfetch_docs—packages/mcp/src/tools/docs.ts(caps the stripped output atMAX_BYTES, butawait res.text()reads the whole body into memory first, and follows redirects by default)In
fetch_docsthe host comes from a fixed enum, butslugis concatenated unvalidated and redirects are followed silently, so a product host that 30x-redirects offsite would be followed without the host allowlist applying to the final URL.Why it matters
api.ferrlabs.com, a product marketing site, the OAuth exchange) blocks the tool call indefinitely — over stdio that wedges the whole MCP session with no error.fetch_docsenum allowlist (mild SSRF surface).res.text()with no streamed size guard lets a large/hostile response balloon memory before the post-hoc slice.Proposed approach
AbortControllertimeout (e.g. 15s, configurable) to allfetchcalls; map abort to a clear tool error.fetch_docs, setredirect: 'manual'(or re-check the final URL host against the allowlist) and reject theslugif it contains://,.., or a leading//.fetch_docsby reading the stream incrementally and stopping atMAX_BYTES.Acceptance criteria
fetch_docsdoes not follow a redirect to a non-allowlisted host.