Agent Secrets Broker
Reads real credentials from a read-only 1Password vault via a service account, allowing the broker to fetch secrets by reference and use them for upstream API calls without exposing them to agents.
Provides GitHub-like repository tools against a mock Git upstream acting as a stand-in for GitHub, enabling agents to list repositories, retrieve README content, and create issues, with sensitive writes requiring approval.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Agent Secrets Broker
A small service that holds secrets and makes API calls on behalf of AI agents. Agents connect to it as a remote MCP server, sign in with OAuth, and can call only the tools their policy allows. No tool ever returns a secret.
The test it is built to pass: Claude Duet asks the broker to do something that needs an API key, gets the result back, and the key never appears anywhere Duet can see.
Status: proof of concept. Two deployments:
The sandbox holds dummy test secrets (
TEST_SECRET_AtoGstyle values) and calls a mock upstream it hosts itself. It carries the mechanism tests: 40 of 40 pass against the deployed URL (docs/test-log.md).The live broker holds real credentials in 1Password and is waiting on Jack's account setup (docs/live-setup.md).
Phase 2 results, negatives included, are in docs/findings.md.
What the broker can and can't do
It can:
Call APIs with keys agents never see. No tool returns a secret, token or credential, error messages included. A redactor scrubs every secret it has handled from anything it sends out.
Give each agent its own tools. The owner picks the agent when approving a connection. Each agent gets only its policy's tools, and other calls are refused and audited (Duet vs AnimaMesh, tested).
Hold a call until the owner approves it. Sensitive tools return "pending" until Jack approves that exact call on the admin page, and it then runs once. Only the owner's password session can approve, never an agent's token or the admin token.
Contain a fooled agent. Text from upstreams arrives marked untrusted. A call that tries to send out a credential (its own access token, a key) is refused, and writes wait for approval. A key embedded in upstream content is redacted.
Cut a connection off at once. Revoke takes effect on the next call and the next refresh, including tokens minted seconds earlier; that needed a fix, see findings D3.
Rotate keys without a redeploy, and keep an audit trail. The audit log records who called what and how it went, with no arguments, contents or tokens.
Hold a user's own OAuth consent for an upstream (live, Adobe). Jack consents on Adobe's page, and the broker stores the token encrypted and refreshes it. Built and deployed; the first real consent is pending.
It can't, or doesn't yet:
Stop an agent from being fooled. It limits what a fooled agent can do; it can't stop the agent from believing injected text.
Detect a stolen refresh token. The thief and the real client can both refresh until the owner revokes; there's no reuse detection.
Sign in with a username and password where the site forbids agents. Best Buy's terms allow agents only through published methods, and there are none. Blocked, no attempt made (findings B).
Solve CAPTCHAs, get past bot checks, or answer MFA prompts. It doesn't, by design.
Notify the owner of a pending approval. Jack has to open the admin page; email or push needs an account.
Limit an upstream token to less than the user's own rights. Jack's Adobe token can write wherever he can; only the broker enforces the scratch folder.
Sandbox | Live | |
MCP server URL |
|
|
Admin |
|
|
Secrets | KV vault, dummy values A to G | 1Password vault |
Tools |
|
|
Upstream | Mock Git ( | Adobe da.live, a scratch folder Jack names |
Runtime: Cloudflare Workers (three Workers, six KV namespaces, Durable Objects for approvals and revocations).
Related MCP server: AgentGuard MCP
Connect an agent
Claude (claude.ai, desktop, mobile, and Duet if it shares claude.ai connectors)
Get the sandbox owner password on the Mac that holds this repo:
npm run sandbox -- owner-password(it reads the gitignored.secrets/sandbox.json). It is a dummy value, but keep it to yourself: anyone with it can approve a connection.In claude.ai go to Customize > Connectors (
https://claude.ai/customize/connectors; some app builds call it Settings > Connectors) and add a custom connector.Name:
Agent Secrets Broker (sandbox). URL:https://agent-secrets-broker-sandbox.jackzhaojin.workers.dev/mcpOAuth client: either option works. The broker supports both.
Use Claude's published identity (Claude's default, a Client ID Metadata Document). Claude's client ID is
https://claude.ai/oauth/mcp-oauth-client-metadata, or its/confidential_clientvariant, which signs its token requests. The consent page will then say "Published by claude.ai". The scripted test runs this path end to end with the public document (M2), and the confidential one up to the signature only Claude can make (M3).Register automatically (Dynamic Client Registration). The scripted test runs this path too, with the exact claude.ai callback URL (C1 to C7). If the first option fails, remove the connector and add it again with this one: Claude doesn't let you change auth settings after adding.
Leave client ID and secret empty.
Click Connect. A window opens on the broker's consent page. Check that access goes to
claude.ai, keep agent duet selected (it is preselected for clients named Claude), type the owner password, and press Approve.The connector shows as connected, with four tools:
whoami,mockgit_list_my_repos,mockgit_get_readmeandmockgit_create_issue(the last one needs your approval on every call).Ask Duet (or any Claude chat with the connector on) to run acceptance tests 2 to 4:
"Call whoami on the Agent Secrets Broker." Expect agent
duet, your client's name, the four tools, andmockgit_create_issueunder approval_required."List my repos with the broker." Expect the 8 mock repos (
sandbox-alphatosandbox-hotel), each with only name, url and updated."Did any tool result contain an API key, token or password?" Expect no. (You can also check: the key values are in
.secrets/sandbox.json; none should appear in the chat.)
To cut Duet off: open /admin, sign in with the owner password, and press Revoke on its row. Its next call fails with 401, and reconnecting needs your approval again.
Any other MCP client
Point it at the MCP URL. It needs Streamable HTTP and OAuth 2.1 with PKCE (S256), and either Dynamic Client Registration or a Client ID Metadata Document. MCP Inspector and Claude Code work as is. Pick the agent it should be on the consent page; inspector exists for manual testing.
How it works
Agent (Duet / AnimaMesh / Claude Code)
| MCP over HTTPS + OAuth access token (15 min)
v
+---------------------------------------------+
| agent-secrets-broker-sandbox (Worker) |
| - OAuth 2.1 authorization server |
| DCR, CIMD, PKCE S256, refresh rotation |
| - Consent page: owner password + agent |
| - Policy: agent -> allowed tools |
| - Tools fetch the secret at call time |
| - Audit log, redaction backstop |
+---------------------------------------------+
| read secret by reference (cached 5 min in memory)
v
Vault (KV namespace VAULT; 1Password for real secrets)
|
v Service Binding, Authorization: Bearer <secret>
agent-secrets-broker-mockgit (Worker): Mock Git API, a stand-in for GitHubOne Worker is both the OAuth authorization server and the MCP server, using
@cloudflare/workers-oauth-provider1.2 and the MCP TypeScript SDK's web-standard Streamable HTTP transport (stateless, no Durable Objects).Discovery follows the MCP authorization spec and Claude's connector docs: an unauthenticated call gets
401withWWW-Authenticate: Bearer resource_metadata=".../.well-known/oauth-protected-resource/mcp", scope="broker". The protected resource metadata names the exact MCP URL. The authorization server metadata advertises S256, the registration endpoint,client_id_metadata_document_supported: trueand thenoneauth method.Each connection is bound to an agent. The owner picks the agent on the consent page. The grant stores it (encrypted), and every MCP request builds a server with only that agent's tools. A call to any other tool is refused and audited as
denied.Tools are task-shaped. There is no
get_secret.mockgit_list_my_reposreadsmockgit/api_keyfrom the vault, calls the upstream, and returns only name, url and updated. If the upstream rejects the key, the tool drops its cached copy, re-reads the vault and retries once. That is why a rotation works with no redeploy.The upstream is careless on purpose. Its 401 echoes the key it was given, and its listing puts the key in
clone_url_with_token. The broker never passes upstream bodies through and allow-lists the fields it returns. A redactor scrubs any known secret value from tool output as a last line of defense (it has never fired in the tests).Audit: one line per tool call, consent, revoke, policy change and failed sign-in: time, client, agent, tool, outcome, short code. No arguments, tokens or upstream data. Kept 30 days in KV, also written to Workers Logs.
Revoke: the admin page or
npm run admin -- revoke <client_id>revokes every grant of a client. Its access and refresh tokens stop working at once.
Policy
policy.json maps agents to tools:
Agent | Tools | For |
|
| Claude Duet through a claude.ai connector |
|
| AnimaMesh: counts only, nothing Duet has (isolation tests) |
| Duet's tools, with | MCP Inspector and scripted tests |
Tools marked requiresApproval in code (mockgit_create_issue) need the owner's approval on every call; a policy's approval_required can add more per agent. See "Per-call approval" below.
policy.json is bundled with each deploy. To change policy without a deploy, push an override (npm run admin -- policy push edited.json); npm run admin -- policy reset goes back to the file. A malformed override fails closed: no agent gets any tool.
Per-call approval
A tool that needs approval answers its first call with pending_approval, an approval id and a link (/admin/approvals/<id>); nothing runs. The owner opens the link (signing in with the owner password returns to it), checks the arguments and presses Approve once or Deny. The agent then repeats the exact same call, which runs once. An approval is bound to the client, the agent, the tool and the exact arguments, lasts 15 minutes, and is single use; a denial stands for 15 minutes. Only the owner's password session can decide: an agent's OAuth token, the admin API token and the admin API itself cannot (the API lists approvals read-only). Approvals live in a Durable Object, so approve-then-run-once is atomic and takes effect at once. Notifications are the admin page only for now.
Tests
npm run test:e2e (default --target remote) runs the whole flow against the deployed sandbox and writes docs/test-log.md. --target local runs it against npm run dev.
It acts as the agent's MCP client and as the owner:
Discovery: the 401 challenge, both metadata documents, a made-up token refused.
Connect like the Claude app: DCR with
https://claude.ai/api/mcp/auth_callback, the consent page (wrong password refused, right password approved), the PKCE code exchange, code replay refused (which also revokes that code's tokens), then MCP initialize and tools/list with the official SDK client.Acceptance tests 2 to 7 from the spec, against the deployed URL.
CIMD with Anthropic's real documents:
https://claude.ai/oauth/claude-code-client-metadata(Claude Code's published client ID, M1), and the Claude app's ownhttps://claude.ai/oauth/mcp-oauth-client-metadatabound toduet, through token, refresh and both tools (M2). Its/confidential_clientvariant gets the consent page, and its code is refused without Claude's signed assertion (M3). Each CIMD check revokes only the grants it created, because a CIMD client ID is shared by every install of that app, Jack's own connector included.A stock MCP SDK client that discovers auth from the 401 by itself, the way MCP Inspector and Claude Code do.
No-leak sweep: every response body and header (errors included), the audit log, and the Worker's own logs from
wrangler tail, searched for all seven dummy values. A control check first proves the scan finds the values in the mock upstream's own careless responses.
Run and deploy
Node 22 and a wrangler login to the Cloudflare account.
npm install
npm run sandbox -- init # dummy secrets A-G in .secrets/sandbox.json (gitignored), plus .dev.vars
npm run dev # both Workers locally on :8787
npm run sandbox -- seed --local # vault and mock upstream state for local
npm run test:e2e -- --target localDeploy (the KV namespace IDs in the two wrangler.jsonc files belong to Jack's account; create your own with npx wrangler kv namespace create <name> and replace them):
npm run deploy # mock upstream, then the broker
npm run sandbox -- push # OWNER_PASSWORD (D), ADMIN_TOKEN (E), COOKIE_SECRET (F) as Worker secrets
npm run sandbox -- seed --remote # vault mockgit/api_key=A, canary/unused=G; upstream accepts A
npm run test:e2e # writes docs/test-log.md
npm run scan # before every push: dummy values, forbidden files, key shapes, gitleaksLive broker (real secrets; Jack sets OP_SERVICE_ACCOUNT_TOKEN and OWNER_PASSWORD himself, see docs/live-setup.md):
npm run deploy:live # patches the 1Password SDK for workerd, deploys wrangler.live.jsonc
npm run live -- init-keys # ADMIN_TOKEN, COOKIE_SECRET, TOKEN_ENC_KEY: random, never printed
npm run live -- status # which secrets are set (names only)
npm run test:live # live checks + Duet's calls from the audit log; add -- --tail 300 while Duet runs
npm run admin -- --live audit # the live audit logOther sandbox commands: npm run sandbox -- vault B --remote (rotate the vault), npm run sandbox -- upstream B --remote (rotate what the upstream accepts), npm run sandbox -- show (labels and fingerprints). npm run admin -- clients | audit | policy.
Security rules this follows
No tool returns a secret, token or credential. There is no
get_secrettool.Upstream bodies are never passed through. Tool output is allow-listed, then redacted.
No request bodies, tokens or upstream responses in logs or the audit log.
Access tokens last 15 minutes. Refresh tokens rotate on use and are revocable. PKCE S256 is required. Redirect URIs are matched exactly (loopback ports excepted, per RFC 8252).
The consent page shows the client's name, whether it is verified (CIMD) or self-registered (DCR), where access goes, and a warning for localhost. It can't be framed, loads nothing external, and takes a single-use handle bound to the browser.
Owner sign-in locks out an IP after 10 failures in 15 minutes.
HTTPS only (Workers). Sandbox secrets are dummy values that never leave
.secrets/sandbox.jsonand the Worker's secret store.
Findings (spec section 8)
Question | Answer |
What OAuth does a Claude custom connector expect? | Callback |
Can Duet add a connector itself? | Not verified. Connectors are added in the Claude app's settings; whether Duet sees claude.ai connectors needs Jack's check in step 7 above. |
Does the 1Password JS SDK run in Workers? | Not as published (its WASM core is read with |
Can Grok Bot, Muse or Dots use remote MCP? | Not checked (out of the baseline). |
Phase 2
Done this phase:
Per-call approval.
Negative tests (prompt injection, stolen tokens, revoke speed, cross-agent isolation, logs), plus the fixes they drove.
The live deployment on 1Password.
Adobe user-delegated OAuth with the DA tools.
Results and the blocked Best Buy experiment are in docs/findings.md.
Still open:
The real-account runs: Jack's setup, Adobe consent, and Duet calling the live tools.
From the original spec: approval notifications, AnimaMesh as a client-credentials client, a metadata-only
secrets_inventorytool, agent-to-agent grants, and a Grok Bot connector test.
Layout
src/index.ts sandbox entry: KV vault, Mock Git tools, policy.json
src/live.ts live entry: 1Password, DA tools, Adobe connect, daily refresh cron, policy.live.json
src/worker.ts OAuthProvider wiring: routes, scopes, token lifetimes, CIMD, DCR, refresh revocation check
src/deployment.ts what differs between the two deployments
src/app.ts consent page, admin page and admin API, approvals pages, landing, healthz
src/mcp.ts MCP endpoint: policy-filtered tools, approval gate, credential-in-arguments refusal, revocation check, redaction
src/tools/ core.ts (whoami), mockgit.ts (sandbox), da.ts (live, da.live scratch folder), types.ts
src/upstream/adobe.ts Adobe IMS user OAuth: /connect/adobe, token refresh, admin status
src/sealed.ts AES-GCM for upstream tokens at rest
src/approvals.ts per-call approval store (Durable Object)
src/revocations.ts revocation list (Durable Object): revoke takes effect at once
src/secrets/ store.ts (SecretStore, KV vault, cache), onepassword.ts (live)
src/policy.ts, policy.json, policy.live.json, src/audit.ts, src/auth.ts, src/redact.ts, src/html.ts
mock-upstream/ the Mock Git API Worker
scripts/ sandbox secrets, live keys, admin CLI, 1Password patch, secret scan
test/e2e.mjs the sandbox acceptance and negative tests
test/live.mjs checks against the live broker, and Duet's calls from its audit log
docs/ findings, test log, live setup, 1Password notes
spikes/ 1password-workers: the patched SDK running in workerdMIT license.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides a secure MCP gateway for AI agents to access APIs without exposing raw credentials, with scoped access, audit logging, and OAuth support.MIT
- AlicenseNot gradedqualityBmaintenanceAn identity-aware authorization layer for AI agents that enforces OAuth scopes, contextual policies, and human approval workflows before executing sensitive actions. It provides controlled execution and audit trails for MCP tools.23,857 npm1MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to safely call enterprise tools through a governed MCP gateway with permission enforcement, blast-radius controls, input validation, and a full audit trail for every invocation.MIT
- AlicenseNot gradedqualityBmaintenanceProvides a single MCP server endpoint that lets AI agents connect to many real third-party services through OAuth, with user approvals, activity logs, scoped read/write grants, and one-tap access removal.8 npmAGPL 3.0