Credential Broker
FreeNot checkedA local-first MCP server that lets AI agents use gated APIs without holding keys, enforcing declarative policies, injecting secrets server-side, and auditing ac
About
A local-first MCP server that lets AI agents use gated APIs without holding keys, enforcing declarative policies, injecting secrets server-side, and auditing access without content.
README
A local-first MCP server that lets AI agents use gated APIs without ever holding the keys.
The agent asks the broker to make a request. The broker canonicalises the URL, checks a declarative per-operation policy, verifies any required approval at execution time, injects the secret at the last moment, calls the upstream, redacts and filters the response, and writes a content-free audit record. The secret lives with the broker process and is never relayed to the agent in any representation the broker creates: raw, Base64, base64url, percent-encoded, form-encoded, or Unicode-normalised. An upstream that transforms a secret with a one-way function of its own defeats string matching; scoped operations and metadata-only responses are the defence there, and SECURITY.md states this boundary honestly.
Pre-release. Nothing is published to npm yet. Treat
mainas a work in progress until a taggedv0.1.0exists.
Why
Giving an agent an API key gives it everything the key can do, for as long as the key lives, invisibly. Most agent tasks need a fraction of that: read these repos, query this endpoint, GET but never DELETE. This broker turns "here is my key" into "here is a scoped, observable capability":
- Possession vs use. Agents exercise credentials; they never possess them.
- Deny-by-default. Authority is an enumerated list of named operations per credential. An absent grant is a denial, not an oversight. Deny rules always beat grants.
- Nothing widens sideways. A method, path, query parameter, body rule, or approval attached to one credential never widens any other credential.
- One decision function. The dry-run explainer and the live request
path call the same
evaluate(). What the broker reports and what it enforces cannot drift apart. - Content-free audit. Who reached what, when, with what outcome, and through which lifecycle state. Never what they saw.
Tools
| Tool | What it does |
|---|---|
broker_whoami |
Show the authenticated agent and its per-credential operation grants. |
broker_list_credentials |
Granted credentials: id, kind, base URL, provisioned or not. Never values. |
broker_explain_request |
Dry run: would this request be permitted, which rule decides it, and is an approval required or satisfied? No upstream call, no secret touched. |
broker_read |
GET or HEAD through a granted credential. Honestly annotated read-only. |
broker_mutate |
POST, PUT, PATCH, or DELETE. Annotated destructive and non-idempotent, because its strongest capability is. May require an approval and an idempotency key. |
Policy (version 2)
Policy objects reject unknown fields at every level. Correct misspelt keys
and remove unsupported metadata before upgrading; ignored fields could
otherwise remove a restriction, such as a misspelt enabled or deny.
One YAML file declares credentials and per-credential operation grants:
version: 2
credentials:
- id: github-readonly
kind: bearer # bearer | header | basic | query
env_var: GITHUB_READONLY_TOKEN
base_url: https://api.github.com
agents:
- id: researcher
grants:
- credential: github-readonly
operations:
- id: read-repo
method: GET
path: /repos/*/* # literal segments, '*', or trailing '**'
response_body: true # default is metadata only
deny:
- /repos/*/*/keys
A request must clear every gate: identity provisioned and enabled,
credential granted, URL canonical and provably unambiguous, origin exactly
the credential's origin, path inside the credential's base path, no deny
match, and an operation whose method, path template, query parameter
allowlist, and body rule all match. broker_explain_request reports which
gate decided any outcome.
Semantics worth knowing:
- Operation paths are relative to the credential's base path. With
base_url: https://api.example.com/v2, the template/weatherauthorises/v2/weather. - Path matching is case-sensitive for operations and case-insensitive for deny rules, because many upstreams treat paths case-insensitively.
- Query parameters not named in
query_paramsare denied. - Operations without a
bodyrule reject any request body. Body rules name allowed content types and a byte cap. Mutations must declarerequires_approvalexplicitly, true or false. - Responses default to metadata only.
response_body: truerelays the body, and only for content types inresponse_content_types(defaultapplication/json). - Credentials of kind
querymust sethigh_risk: true: URLs are commonly retained by upstream and proxy telemetry, so a query-string secret has a wider blast radius than a header.
Approvals
An operation with requires_approval: true executes only when the request
carries an approval_id that exists in the approval store, is bound to
exactly this principal, credential, and operation, is unexpired, and is
not revoked, checked at execution time, not config-load time. On stdio the
store is a JSON file (config/approvals.json by default) that is re-read
on every check, so editing it revokes access mid-flight:
[
{
"id": "apr-2026-08-13-01",
"agent_id": "researcher",
"credential_id": "github-readonly",
"operation_id": "create-issue",
"expires_at": "2026-08-14T00:00:00Z",
"revoked": false
}
]
Migrating from version 1
Version 1 policies shared one method list and path set across every credential an agent held, so authority needed for one credential widened all of them. The broker refuses v1 files. Convert with:
npm run migrate -- config/broker.yaml > config/broker-v2.yaml
The output is deliberately conservative (mutations require approval, query parameters must be enumerated) and flagged for review: it makes the old blast radius visible rather than silently preserving it.
Safety properties
- Every URL is canonicalised once; policy and the outbound request consume the same canonical value, and the outbound bytes are round-trip checked. Encoded separators, double encoding, control characters, backslashes, userinfo, non-ASCII path segments, repeated or trailing slashes, and trailing-dot segments are rejected, not guessed at.
- Secrets are injected into exactly one header or query parameter. One redactor covers response bodies, relayed headers, error chains, and audit records, across raw, Base64, base64url, percent-encoded, form-encoded, and NFC/NFD representations, including truncation boundaries.
- Raw upstream error text is never relayed to agents; they receive a stable error category and a trace id, and the redacted detail goes to the audit log.
- Redirects are refused: a redirect could point anywhere, and
fetchwould re-send the injected credential there. - A credential can only be exercised against its exact origin and base path; lookalike hosts and string-prefix tricks are refused.
- Plain-http upstreams are rejected at policy load, except on loopback.
- Response headers are allowlisted;
Set-Cookieand friends are dropped. - Responses are size-capped per operation, metadata-only by default.
- Mutations are a separate, honestly-annotated tool; approval state is
checked at execution time; a dispatch failure on a mutation is recorded
as
indeterminate, never silently retried. - Every decision is audited through its full lifecycle without request or response content, and audit records pass through the redactor too.
Identity boundary
On stdio, BROKER_AGENT_ID is a trusted-launcher assertion: the
operator starting the process vouches for which agent is connected. It is
only a real boundary when the broker runs under the secure-launch profile
in SECURITY.md: dedicated OS identity, owner-only
policy/approvals/audit files, a fixed identity wrapper the agent cannot
edit, and no agent-readable secret environment. The broker loads only its
principal's secrets and scrubs every policy credential variable from its
environment after loading. One broker process serves one principal; never
share a process between agents.
Managing keys (Windows)
npm install
npm run keys
This opens a local page in your browser where you:
- Add an API: name, base URL, how the key is sent, paste the key, then choose individual agents or group shortcuts. The key goes into Windows Credential Manager; the policy file gets persistent group assignments and separate read-only grants for individually selected agents (GET on any path under the base URL, only the query parameters you list).
- Save, replace, test, or remove a key for any API already in the policy. Test makes one GET with the key and reports the status code only. Keys are write-only: the page never shows one back.
- Import keys you already have. "Find my keys" looks in Windows user
environment variables, MCP client configs (Claude Code, Claude Desktop,
Cursor), and
.envfiles underDocumentsandC:\Dev(override withBROKER_IMPORT_ROOTS, semicolon-separated). Known providers (OpenRouter, Gemini, ElevenLabs, DigitalOcean, Nansen, TypeSafe, Clerk, CoinGecko, PixelLab, OpenAI, Anthropic, GitHub) can be added as APIs in one click and are tested straight away; anything else is stored only. Values are read by the key manager process and never sent to the page. Wallet keys are flagged and never offered as APIs. Original copies are not touched. - Copy the
claude mcp addcommand that registers the broker for an agent with absolute paths filled in.
Write access (POST, PUT, PATCH, DELETE) is never granted from the page; add those operations to the policy by hand. Restart the MCP client after changing a key, because the broker loads keys once at start-up.
Optional top-level groups: in broker.yaml maps a name to registered agent
IDs. all: ['*'] means every registered agent. The example policy shows
hermes, grokbot and all; replace their sample members with your own IDs.
Groups cannot be empty, nested or refer to unknown agents. Selecting a group
in the key manager writes a group_grants: entry for that credential. The
broker resolves it for the authenticated agent on every request. Editing
groups: changes access after the broker restarts and reloads its policy.
Membership alone grants nothing; a matching group_grants: entry is required.
Group and direct denials are combined, and group mutations must require
approval. The key cards show groups actually assigned to each credential.
Keys live in Control Panel > Credential Manager > Windows Credentials as
credential-broker/<credential-id>. An env_var value, if set, overrides
the stored key. The page listens on 127.0.0.1 only, needs a per-run token
from the printed link, and closes after 30 minutes idle. On other
platforms, set each credential's env_var instead.
Running
npm install
cp config/broker.example.yaml config/broker.yaml # then edit, or use npm run keys
BROKER_AGENT_ID=researcher npm start
Environment:
| Variable | Meaning |
|---|---|
BROKER_AGENT_ID |
Identity this stdio session runs as. Required. |
BROKER_POLICY_FILE |
Policy path. Default config/broker.yaml. |
BROKER_AUDIT_FILE |
Audit log path. Default data/audit.jsonl. |
BROKER_APPROVALS_FILE |
Approval store path. Default config/approvals.json. |
BROKER_KEYCHAIN |
off stops the broker and key manager reading Credential Manager. |
BROKER_KEYS_PORT |
Fixed port for npm run keys. Default: a free port. |
| (per credential) | Each credential's env_var carries its secret, overriding a stored key. |
Claude Code registration (once published; see the pre-release note above):
{
"mcpServers": {
"credential-broker": {
"command": "npx",
"args": ["-y", "@sarutobi-sasuke/credential-broker-mcp"],
"env": { "BROKER_AGENT_ID": "researcher" }
}
}
}
Verification
Run npm run check for types, lint and unit tests, npm run verify:pack
for the package allowlist, and npm run test:e2e for the installed-server
proof. CI runs these on Windows and Linux with Node 22 and 24.
The end-to-end test packs the project, installs the tarball into an empty directory and starts its declared stdio executable. Synthetic credentials and a loopback HTTP API exercise tool discovery, principal-scoped grants, denials before upstream calls, response redaction, metadata-only responses, approval revocation and content-free audits. No production credential, OS keychain entry or external API is used. This does not verify the key manager UI, secure-launch deployment, independent operator workflows or the eventual npm-hosted artifact.
Idempotency keys are required and recorded when the policy says so. They are audit identifiers, not upstream deduplication: the broker neither forwards them as headers nor suppresses repeated mutations.
The manual release workflow uses Node 24 for the npm trusted-publishing
toolchain and runs the installed proof through prepublishOnly before
publication. See npm's trusted-publisher requirements.
The package's npm trust configuration and the release tag still need
operator verification before a first publish.
What this is not
- Not an OAuth authorization server. It brokers static secrets you already hold. OAuth token acquisition and refresh are on the roadmap.
- Not a cloud service. It runs where your agents run, and your secrets stay on your machine. If you want hosted multi-tenant auth for agents, use a platform built for that.
- Not a bypass. It delegates through existing gates with scoped-down authority; it does not defeat anything.
Roadmap
- OAuth 2.1 device and refresh flows for credentials that expire
- HTTP transport with mutually authenticated, audience-bound, expiring principal tokens through the same engine, matching agent-handoff-mcp
- Per-credential rate limits and spend counters
- Key manager page: provision, replace, test, and remove keys (Windows)
- Keychain backends for macOS and Linux
- Operator CLI: approve, rotate
Licence
Apache 2.0. See LICENSE.
Installing Credential Broker
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/SarutobiSasuke8/credential-broker-mcpFAQ
Is Credential Broker MCP free?
Yes, Credential Broker MCP is free — one-click install via Unyly at no cost.
Does Credential Broker need an API key?
No, Credential Broker runs without API keys or environment variables.
Is Credential Broker hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Credential Broker in Claude Desktop, Claude Code or Cursor?
Open Credential Broker on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Opencode Omniroute Plugin
OpenCode plugin for the OmniRoute AI Gateway. Drives dynamic model discovery, /connect auth flow, and multi-instance OmniRoute providers via the official @openc
by GitHub ActionsRoblox Studio
Enables AI coding tools to control Roblox Studio for workspace exploration, instance manipulation, and script management. It provides tools for playtesting, sce
by paralovAntigravity Drawio
MCP server that enables AI assistants to create, parse, render, and validate Draw.io diagrams programmatically.
by S-SUJAN-SFetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
by modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
by xuzexin-hzMCP-Agent
A simple, composable framework to build agents using Model Context Protocol by [LastMile AI](https://www.lastmileai.dev)
by lastmile-aiSpring AI MCP Client
Provides auto-configuration for MCP client functionality in Spring Boot applications.
mcp.natoma.ai
A Hosted MCP Platform to discover, install, manage and deploy MCP servers by [Natoma Labs](https://www.natoma.ai)
Compare Credential Broker with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All ai MCPs
