A custom agent loop in React that plugs any model into any remote MCP server. One Cloudflare Worker serves the app and does everything that needs a secret.
Browser (React) Worker Outside
agent loop, approvals ──────► login, Vault (encrypted) ─────► model APIs
sees text + results only model adapters, MCP client ────► MCP servers
The only shipped dependencies are react and react-dom. The Worker has none.
-
Accounts: Google sign-in through Firebase. The Worker verifies the ID token, then uses its own signed HttpOnly session cookie. No Firebase token stays in the browser.
-
Isolation: each user has their own
VaultDurable Object and their own AES-256 key, derived fromENCRYPTION_KEYand their user id. -
Secrets: model keys and MCP tokens are write-only and never reach the browser.
-
Rate limits (
src/worker/limits.ts): per IP and per user, in theRateLimiterDurable Object.Scope Limit Any API call, per IP 240 / minute Login, per IP 10 / minute, 60 / hour Model calls, per user 30 / minute, 500 / day Tool calls, per user 60 / minute, 2000 / day Settings changes, per user 30 / minute, 300 / day MCP sign-ins, per user 10 / minute, 50 / day -
Other limits: 10 models and 10 servers per user, 2 MB per chat request, 256 KB per tool call.
-
Outbound URLs: public https only, no redirects with secrets, and changing a model's URL drops its key. Production also sets
global_fetch_strictly_public. -
Browser: strict CSP and security headers in
public/_headers. Other sites can't send state-changing requests. -
Tools: anything not marked read-only needs approval in the chat.
-
Deletion: Settings → Delete my data wipes the user's vault.
Add a URL, key and model id. The format is detected from the URL, or pick one:
| Format | Example URL | Also covers |
|---|---|---|
| OpenAI-compatible | https://api.openai.com/v1 |
OpenRouter, Groq, Mistral, DeepSeek, vLLM… |
| Claude | https://api.anthropic.com |
|
| Gemini | https://generativelanguage.googleapis.com/v1beta |
You need Node 22+, a Cloudflare account and a Firebase project.
-
In the Firebase console, create a project, then turn on Google under Authentication → Sign-in method.
-
Copy the project id and the Web API key from Project settings. The Web API key identifies the project in browsers; it isn't a password.
-
Configure the app:
cp wrangler.example.toml wrangler.toml # set your Firebase project id and auth domain cp .dev.vars.example .dev.vars # set ENCRYPTION_KEY and FIREBASE_API_KEY npm installwrangler.tomland.dev.varsare gitignored. GenerateENCRYPTION_KEYwithopenssl rand -base64 48.
npm run dev # http://localhost:8787
Use localhost, not 127.0.0.1. The dev env may connect to MCP servers running on localhost.
npx wrangler login
npm run "deploy prod"
npx wrangler secret put ENCRYPTION_KEY --env production # use a new key, not your dev one
npx wrangler secret put FIREBASE_API_KEY --env production
This creates the mcp-harness-production Worker, which serves both the app and the API, with its Durable Objects. No KV or D1 setup is needed.
Then add the Worker's hostname under Firebase Authentication → Settings → Authorized domains, or Google sign-in will fail. A deployed harness can only reach public MCP servers.
Back up ENCRYPTION_KEY somewhere safe. If it's lost or changed, saved keys become unreadable and users must enter them again.
- Chats live in the page's memory and are gone on refresh.
- The agent loop runs in the page, so closing the tab stops it.
- Remote HTTP MCP servers only, using tools. No stdio, resources, prompts or elicitation.
MIT