RFC: Cloudflare deployment artifact for ToolHive MCP servers#82
Conversation
JAORMX
left a comment
There was a problem hiding this comment.
RFC Review: THV-0082 — Cloudflare Deployment Artifact
Summary
So, this is a genuinely strong RFC. The security discipline is the best I've seen in this repo, the Cloudflare claims check out against the primary docs, and the CloudflareDeployer interface is the right call. I'm recommending revision rather than acceptance for one main reason: it reimplements ToolHive's entire security-critical OAuth core in TypeScript inside the Worker, when that core already exists and is already conformance-tested in Go. That, plus a provider-prefixed manifest API and a few deviations from the original issue that aren't owned by name, are worth fixing before this ships. The good news is the biggest one is fixable by topology, not more tests.
Strengths
- The SEC-01..13 blocking security gate and the dependency-ordered P1-00..14 breakdown are unusually rigorous. Secure-by-default with an explicit
--allow-insecure-public-endpointopt-in matches ToolHive's stance exactly. - The Cloudflare facts are accurate. I verified the egress/HTTPS-interception mechanics, the Containers/Durable Object model, DO encryption-at-rest (app-layer AES-GCM on top is legitimately defense-in-depth, not redundant), the Secrets Store binding shape, and the GHCR exclusion against the primary docs. No contradictions found.
- The
CloudflareDeployerinterface (Inspect/Plan/Apply/Delete/WaitReady, argv-only Wrangler invocation, pinned version) correctly confines the Node dependency tothv-cloudflareand makes a future Go backend a drop-in replacement. - The stateless reconcile (no local state, desired = manifest, actual = Cloudflare) is the right model for CI, and the ownership-marker + desired-digest approach faithfully transplants operator principles to a CLI.
cmd/thv-cloudflarefits the existingthv-<role>multi-binary pattern cleanly.
Critical issues (must fix)
1. The security core should stay in Go, not be rewritten in TypeScript. This is the big one and it's covered in detail in the inline comment on the Worker-side TS modules. Short version: the RFC reimplements DCR, PKCE, the OAuth metadata, JWKS rotation, the token vault, and upstreamInject in TS, when all of that already exists in pkg/auth/* and pkg/vmcp/auth/*. Two implementations of security-critical code in two languages will drift. The fix is the topology the original issue #5854 actually proposed: keep the security core in the existing Go proxy running as the container entrypoint, and shrink the Worker to a dumb, non-security shim that routes paths and serves the static metadata docs. One implementation of the auth core, in Go. If the broker stays Worker-resident instead, then at minimum this needs to be flagged as a risk and backed by a shared conformance harness run against both implementations in CI.
2. The new provider-prefixed manifest API invents parallel vocabulary instead of a portable core. cloudflare.toolhive.stacklok.dev bakes the provider into the API group (Open Question 1 already flags this as unresolved), and the manifest invents runtime.instanceName, permissions.egress.mode: disabled|unrestricted, and the MultiUserPerRequest|SingleIdentity|Unknown status enum with no mapping to the existing PermissionProfile / auth-server vocabulary. The "later conversion layer" is currently one sentence. I'd like a Portable Core subsection with an explicit field mapping, a neutral API group with a provider: cloudflare discriminator, and the allowlist egress enum value reserved now (strict-decoded v1alpha1 makes adding it later a breaking change).
3. Several deviations from issue #5854 aren't owned by name. The issue asked for three run modes (the RFC ships one), pure Go with no Node (the RFC ships Wrangler), CRD reuse (the RFC goes greenfield), and OCI mirroring (deferred, but the success criteria depend on a manually mirrored image with no tooling to produce it). Most are defensible calls. They just need to be stated as deliberate tradeoffs in the Non-Goals so the RFC reads as a faithful response to the issue it came from. Detail in the inline comment on the Non-Goals list.
Suggestions (should consider)
- Secrets Store is in open beta and is a blocking Phase-1 dependency for every secret type. Add an explicit written risk-acceptance or a documented fallback (Workers encrypted vars).
- Give
CloudflareDeployertyped errors (ErrUnownedCollision,ErrVersionDrift,ErrPreflightFailed) and a credential-passage contract, and add a default deadline toApply. - Add a
Drifted: truecondition tostatus(compare the liveTOOLHIVE_DESIRED_DIGESTto the manifest's canonical digest) so the stateless model can detect divergence between CI runs, not just converge on apply. interceptOutboundHttpis named in the issue but never addressed, even to explain the deferral to Container-level egress.- Goal 11 ("track compatibility explicitly") has no test or gate. The matrix is prose, not a verifiable property. Add a unit asserting a few "unsupported combination -> correct error" cases.
- The GitHub App callback URL is manual;
applydoesn't verify it matches before the OAuth redirect, which is the worst time to discover a mismatch.
Security assessment
The security section is the strongest part of the RFC: real threat model, all required subsections present, secure defaults, layered encryption, reference-only secrets, and a blocking gate with named owners. My one concern is structural, not content: the Worker-resident broker is the component that carries most of these guarantees, and it's the component being reimplemented in a second language. Moving that core to the existing Go proxy (or locking the two implementations together with a shared conformance suite) is what makes the otherwise-excellent security story hold up over time. Also worth elevating the Secrets Store open-beta dependency into an explicit risk-acceptance.
Architectural alignment
Mostly aligned. The dedicated binary, the narrow deployer interface, the stateless ownership-marker reconcile, and the secure-by-default posture all fit ToolHive's conventions. The two places it diverges are the two critical issues above: the second-language security core (against the "don't duplicate security-critical logic" spirit) and the provider-coupled manifest vocabulary (against the "platform, not runner" and RunConfig-portability principles). Both are fixable without changing the RFC's Phase-1 scope.
Questions for the author
- Is there a reason the security core has to live in the Worker rather than in the existing Go proxy as the container entrypoint? If it's DO-state access, the
outboundByHostshim (option 2 in the inline comment) or external Redis (option 1) both keep it in Go. - Are you open to a neutral API group +
provider: cloudflarediscriminator, and a documented portable-core field mapping, instead of the provider-prefixed group? - Can the Non-Goals name the dropped run modes (daemon, self-hosted-on-Cloudflare) and the deferred pure-Go / image-mirroring work explicitly?
- On Wrangler distribution (Open Question 2): do you have a lean? The ship checklist can't be fully validated while that's open.
- For the Phase-2 native-Go trigger, is "Cloudflare exposes a public Containers REST API and
cloudflare-goships acontainerspackage" the right condition to write down?
Recommendation
- Ready to accept
- Accept with minor changes
- Needs revision (address critical issues)
- Major rework needed
Happy to talk through the Go-proxy topology in more detail if useful. And thanks @samuv for putting this together, the depth here (especially the security gate and the shipping breakdown) is exactly what a deployment-mode RFC should have.
|
@JAORMX Thanks for the detailed review. I’ve addressed all actionable feedback in the pending RFC revision:
I’ll push the RFC revision after the local commit is reviewed. |
70828a4 to
0074c59
Compare
0074c59 to
b96f7ca
Compare
Summary
Proposes
thv-cloudflare, a dedicated artifact for deploying and reconciling ToolHive-managed MCP servers on Cloudflare Workers and Containers.Related to stacklok/toolhive#5854.
Phase 1 scope
MCPServerDeploymentSecurity and delivery
The RFC includes:
Review focus
Feedback is especially useful on: