Install
$ agentstack add skill-agentproto-ts-auth ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
When to use
Reach for this whenever an agent (or a child MCP it mounts) must present a credential to a remote service and you don't want that secret sprayed into the daemon's global env or hardcoded in a config file. The hallmark: "how do I give this agent access to `` without leaking the key to every other agent?"
- A tool/agent calls a Bearer API (agentpush, Stripe, an internal API).
- A hosted/remote MCP server needs an
Authorizationheader. - You want ONE place to store a key, scoped per service, resolved fresh (with
refresh) at use-time instead of a long-lived env var.
Do NOT use for the daemon's own tunnel login (that's agentproto auth login, a device-code flow already wired) — though it uses the same substrate underneath.
The model — one source of truth, resolved on demand
auth provider (declares WHERE/HOW) ──┐
CredentialStore (holds the secret) ──┤─▶ CredentialBroker.resolveHeaders({ path, audience })
│ → { Authorization: "Bearer …" } (fresh, per call)
- Provider — a manifest declaring a service's
id,apiBase, andauth
flow. Config only, never the secret. Registered once (registerAuthProvider).
- CredentialStore — where the secret lives:
KeychainStore(macOS CLI),
FileStore (AES-256-GCM, headless — key from AGENTPROTO_STORE_KEY), MemoryStore (tests). Swap the backend, same interface.
- Broker —
resolveHeaders({ path, audience?, signal? })turns a provider
path into ready-to-use headers: serves a fresh stored bearer, else runs the flow. audience (api / mcp / tunnel) scopes the credential so it can't be reused cross-plane.
- Flows — pick one per service:
pat(static token you store/paste),
service-auth (auth.md claim ceremony → short oat + durable assertion), device-code (RFC 8628 → durable daemon token + refresh).
Recipe
1. Declare + register the provider
TS literal (builtin/tests):
import { defineAuthProvider, registerAuthProvider } from "@agentproto/auth"
registerAuthProvider(defineAuthProvider({
id: "agentpush",
description: "AgentPush API — paste a personal API key.",
apiBase: "https://api.agentpush.example",
audience: "mcp", // or "api"
auth: { flow: "pat", tokenStore: { keychain: "agentpush", account: "{server}" } },
}))
Or ship a vendor *.auth.md and parseAuthProviderManifest(...) → registerAuthProvider(...) — a host adds providers without editing this package.
2. Store the secret ONCE (out of env, into the store)
import { KeychainStore, resolveStoreRef, getAuthProvider } from "@agentproto/auth"
const p = getAuthProvider("agentpush")!
const store = new KeychainStore() // or FileStore for headless
await store.write(resolveStoreRef(p.auth.tokenStore, p.apiBase, p.audience),
{ value: process.env.AGENTPUSH_API_KEY!, kind: "pat" })
Do this once (onboarding / a secrets set step) — then delete the env var.
3. Resolve at use-time
import { CredentialBroker, getAuthProvider } from "@agentproto/auth"
const broker = new CredentialBroker({ store, getProvider: getAuthProvider })
const headers = await broker.resolveHeaders({ path: "agentpush", audience: "mcp" })
// → { Authorization: "Bearer …" } — attach to your fetch / MCP transport
Fresh every call; for service-auth/device-code it auto-refreshes. No secret touches env or .mcp.json.
Two consumption paths
A. Agent calls the service in its own code/tool. Works TODAY: resolve via the broker (step 3) and attach the header. Nothing else to wire.
B. Service exposed as a child MCP the agent mounts. The clean target is a credentialRef on the child-mcp spec that the daemon resolves via the broker at connect-time (the mcp-header exposure pattern — resolveMcpHeaderExposure turns a credentialPath into headers via a structural resolver the broker satisfies). Gap: agent_start.mcpServers today carries only { name, ref, transport } — no headers/credentialRef — so brokered auth can't yet reach a child MCP at mount. Until that plumbing lands, either use path A, or resolve the header at spawn and pass it through your own mount path.
Worked example — agentpush
agentpush uses a static Bearer key. Clean-auth wiring:
- Register
agentpushas apatprovider (step 1),audience: "mcp". - Move
AGENTPUSH_API_KEYfrom the daemon env into the store (step 2). - In-code callers resolve
broker.resolveHeaders({ path: "agentpush" })(path A) —
works now, key out of the global env, scoped to this agent.
- For agentpush-as-child-MCP (path B), a
credentialRef: "agentpush"on the
child-mcp spec + daemon broker-resolve-at-connect is the north star (reuses everything here; needs the agent_start.mcpServers credentialRef plumbing).
Common mistakes
- Storing the key in the daemon's global env — every spawned agent inherits
it. The whole point of the store is per-service, per-spawn scoping.
- Skipping
audience— set it (api/mcp/tunnel) so a credential minted
for one plane can't be presented on another (defense-in-depth; the normative boundary is server-side).
- Caching
resolveHeadersoutput — resolve per call; the broker owns
freshness/refresh. A cached header defeats service-auth/device-code refresh.
patwhere the service issues short-lived tokens — useservice-auth
(mint + refresh) instead of pasting a token that expires.
- Wrong header shape —
resolveHeadersreturns a header map; a service that
wants X-Api-Key (not Authorization) needs the provider/flow to emit that shape, not a Bearer.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: agentproto
- Source: agentproto/ts
- License: Apache-2.0
- Homepage: https://agentproto.sh
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.