Skip to content

feat(host-cloudflare): route configured internal hosts to service bindings - #13

Merged
fforres merged 3 commits into
skywardfrom
feat/internal-mcp-hosts
Oct 9, 2026
Merged

fforres merged 3 commits into
skywardfrom
feat/internal-mcp-hosts

Conversation

@fforres

@fforres fforres commented Oct 9, 2026

Copy link
Copy Markdown

What

A private MCP server in another Worker can now be reached over a service binding instead of the internet.

  • SDK: internalHosts on the hosted HTTP client and host config. An exact hostname match goes to its binding, outside the SSRF guard. Any other *.internal host is refused, a redirect from an external server into *.internal is refused, and requests to a binding go out with redirect: "manual" so a 3xx from the bound Worker is never followed. Covers the MCP transport and the plain fetch.
  • host-cloudflare: INTERNAL_MCP_HOSTS (host=BINDING pairs) is validated at config load. A host naming a binding that is absent, or a host not ending in .internal, fails config load, so the Worker answers 503 with a message naming the host and binding instead of silently leaving an unreachable integration. Unset means no internal hosts and no behaviour change.
  • README: internal tools over service bindings, trust boundary, and a note that deployments with internal bindings must be deployed through the repo that owns them.
  • .gitignore for generated wrangler configs; e2e workers ignore a developer's local .dev.vars.

Tests

  • hosted-http-client.test.ts: exact host match (no suffix match), unknown *.internal refused, external redirect into *.internal refused, manual redirect to a binding returned untouched.
  • internal-host.test.ts: MCP discovery against a real MCP server behind a binding.
  • config.test.ts: mapping, trailing dot, unset, missing binding, non-.internal host.
  • Each guard was mutation-checked: removing it fails exactly its test.

…e the SSRF guard

HostedHttpClientOptions and HostConfig gain internalHosts: an exact hostname
match goes to its fetcher; any other *.internal host is refused, and the guard
rejects redirects into one. Applies to the MCP transport and the plain fetch.
…llow a redirect from an internal binding

host-cloudflare config validates INTERNAL_MCP_HOSTS with the same helpers. Requests
to a binding carry redirect: manual so the runtime cannot follow a Location past
the SSRF guard. UPSTREAM.md splits 3ba849c; README documents the trust boundary.
…ice bindings

Validates host=BINDING pairs at config load and fails loudly (503) when a
mapped binding is absent or a host does not end in .internal. Passes the
mapping to the hosted HTTP client, documents the trust boundary and the
deploy path, ignores generated wrangler configs, and makes the e2e worker
ignore a developer's local .dev.vars.
@fforres
fforres added this pull request to stack #15 October 9, 2026 04:37
@fforres
fforres merged commit 65b943c into skyward Oct 9, 2026
4 of 40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant