Private betaZynth Auth is currently in private beta testing.New organizations are created by invitation only, and no plan can be purchased yet.Request early access

Operating Zynth over MCP

Zynth Auth runs a first-party MCP server at POST /api/v1/mcp. An AI agent connects with its own identity — never a human's account — and can inspect and (with approval) operate the platform through registered tools.

Six properties define the surface:

  1. Agents only. Human tokens are refused. The console is the human door; keeping the doors separate is what makes the agent audit trail trustworthy.
  2. An agent sees only the tools it can call. tools/list is filtered to the agent's delegated permissions.
  3. Every write carries exactly one human approval. A state-changing call either parks in the approval queue — bound to that exact action and arguments, usable once — or executes against an approval a human already gave for that exact content (the manifest tools; see §6). Never two asks for one act, never zero.
  4. Every write tool answers a dry run. An agent can ask "what would this do" before it asks a human for anything: no side effects, no approval spent (§5).
  5. The kill switch acts mid-session. An engaged kill switch refuses the agent's very next message — not its next token refresh.
  6. The damage rate is bounded, and the bound stops the agent. Each agent has an hourly write budget from your plan and a refusal allowance. Exhaust either and the kill switch engages automatically — not a slowdown, a stop, releasable by a person in one click.

1. Register an agent and delegate tools

AI Agents → Register agent in the console. Copy the credential (agt_…) — it is shown once, like an API key. Then delegate the permissions the agent should have (for example command:read for the read tools, command:operate for detection acknowledgement).

An agent with no delegations can call only whoami. That is the safe floor, not a bug.

When you're done with an agent, retire it (AI Agents → the agent → Retire). Its credential dies immediately, so it cannot reconnect; its audit trail is kept, so what it did stays on the record. Retirement is permanent — to pause an agent you may want back, Suspend it instead.

2. Exchange the credential for a token

curl -s -X POST https://auth.zynthmedia.com/api/v1/agents/token \
  -H "Content-Type: application/json" \
  -d '{"credential": "agt_..."}'

Agent tokens are short-lived by design; agents re-authenticate with their credential. There is no refresh token — a compromised agent token expires on its own, fast.

Narrow the token to the job (recommended). That token carries everything the agent holds, for its whole lifetime, for any purpose. Add a task block and it carries only what the job needs:

curl -s -X POST https://auth.zynthmedia.com/api/v1/agents/token \
  -H "Content-Type: application/json" \
  -d '{"credential": "agt_...",
       "task": {"task": "Summarise last night'\''s detections",
                "permissions": ["command:read"],
                "budget": 25}}'

The permissions must be a subset of what the agent already holds (anything more is refused, not trimmed), budget is how many authorized requests the token may make, and the task text is shown to whoever approves the agent's actions. A task token for reading detections cannot write an acknowledgment even though the agent can. Mint one per job; details in the API reference.

Or skip the credential entirely. If your agent runs where the runtime can attest — GitHub Actions, Kubernetes, cloud, SPIFFE — exchange that attestation for the same token with no Zynth secret stored anywhere: Federated workload identity. Everything below (MCP connection, tools, approvals, kill switch) is identical from that point on.

Credential staleness. The agt_… credential itself is long-lived, and that standing secret is the weak point. Your platform operator can set a maximum credential age for your organization: past it, an agent authenticating with an older credential raises a Command Center detection naming the agent and its exact age in days, and rotation is one call. The policy never blocks the agent — an unattended agent is surfaced for rotation, never locked out mid-run. No maximum is set by default; ask your operator if you want one.

3. Connect an MCP client

With Claude Code, one command:

claude mcp add --transport http zynth https://auth.zynthmedia.com/api/v1/mcp \
  --header "Authorization: Bearer <agent-access-token>"

Any streamable-HTTP MCP client works the same way: point it at the endpoint with the agent's bearer token. The server is stateless — no session setup beyond initialize.

There is a request budget, and it is generous. Every frame counts — initialize and tools/list as well as tool calls — against a per-agent ceiling, your organization's fleet allowance, and a per-source-host floor. The per-agent ceiling sits roughly an order of magnitude above a busy model-driven tool loop, so ordinary work never meets it; a runaway retry loop will. Over the limit you get 429 with a computed Retry-After and RateLimit-Limit/-Remaining/-Reset describing the budget that bit — honour Retry-After rather than retrying immediately. One agent exhausting its own ceiling does not spend your other agents' allowance. Reuse the same access token for its full expires_in: re-authenticating per request is the one habit that trips the token endpoint's own budget.

4. The tools

Read tools — no approval, no side effects:

ToolWhat it returnsNeeds
whoamiThe agent's identity, tenant, and governance postureany agent
list_detectionsRecent security detections, newest firstcommand:read
query_auditRecent audit entries (action, outcome, actor, time)command:read
read_entitlementsThe organization's plan, features, capscommand:read
assure_latest_reportThe last continuous-assurance run scored against this deployment: verdict, leg counts, which scenarios failed, how many findings are open. Answers null where no run has been reported to your organizationcommand:read
plan_manifestValidates an IAM manifest and diffs it against live state — also your drift answer for a manifest you already appliedmanifest:plan
request_manifest_approvalParks the apply ask for a human, bound to the manifest's content hash. Classified read: it creates only a pending human decisionmanifest:apply

Write tools — each carries one human approval, and each answers a dry run:

ToolWhat it doesNeeds
acknowledge_detectionAcknowledge/resolve a detection — parks for approvalcommand:operate + a per-call approval
apply_manifestExecutes a human-approved IAM manifest, stepwise and audited. Refuses without a ledger approval for these exact bytesmanifest:apply + an approved manifest hash

Results carry only the documented fields — no session identifiers, IP addresses, or internal correlation data, ever.

Setting up a whole organization? The manifest tools are how an agent configures roles, resource permissions, invitations and an SSO scaffold as one declarative document under one human review — including for a tenant the agent opened itself. The full story, with the manifest schema and its rules, is in Agent-driven onboarding & IAM-as-code.

5. Dry run — "what would this do"

Every write tool accepts "dry_run": true in the tools/call params and answers with the effect it would have: no side effects, and no approval spent. Validate before you ask a human for anything.

{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "apply_manifest", "dry_run": true, "arguments": { "manifest": { "manifest_version": 1 } } }
}

apply_manifest's dry run is the plan — the same verdicted diff plan_manifest returns. Read tools deliberately have no dry run and refuse one: a read is already its own dry run.

dry_run is a call-level flag: it sits in params beside name and arguments, never inside arguments. A dry_run key found inside arguments is refused with a correction rather than silently ignored — a caller who wrote it meant to preview, and a "preview" that executed would be the worst possible answer. More generally, every call's arguments are validated against the tool's advertised inputSchema (the one tools/list returns) before anything runs: unknown keys, wrong types, out-of-range values and missing required keys are refused with the exact field named, and an invalid call never parks an approval.

6. One approval per act

A write tool binds to a human decision in exactly one of two ways — never both, so one human never decides the same act twice.

Per-call park (acknowledge_detection):

  1. The agent calls acknowledge_detection. The result says the action is pending human approval and includes an approval_id.
  2. A human reviews it in the Command Center (or via POST /api/v1/approvals/{id}/decide). The request shows exactly which agent asked for exactly which action.
  3. The agent retries the identical call: the approval is consumed (single-use) and the action executes. A call with different arguments does not match the approval and parks as a new request.
  4. A denial stands: repeats of the denied action keep returning the denial for its validity window. The agent cannot ask its way past a human "no".

Approval by content hash (apply_manifest): the human approves the manifest, once, and the agent applies it — no second ask at call time. The approval is bound to the document's exact bytes, so editing the manifest after approval invalidates it (an edited manifest is a different manifest). Ask with request_manifest_approval, or receive one already decided if the manifest rode an agent-driven onboarding.

If approvals are not enabled on the deployment, write tools are refused outright — including the hash-bound ones.

Troubleshooting

  • 403 on every call — you are using a human token (use the agent's), or the agent's kill switch is engaged. If nobody engaged it, check the agent's record: a reason reading blast-radius breaker: … means the agent exceeded its hourly write budget or was refused too many times and was contained automatically. Fix the cause before releasing it, or it will trip again inside the hour.
  • 429 Too Many Requests — a request budget. RateLimit-Limit names which one bit (per agent, per organization, per source host) and Retry-After says how long to wait. If the response carries no RateLimit-* headers at all, the limit could not be computed and the surface refused deliberately rather than waving requests through — that one is on us; retry shortly and tell support if it persists.
  • A tool is missing from tools/list — the agent lacks the tool's permission; delegate it. An agent with no delegations sees only whoami.
  • denied: not authorized for this tool — same cause as above, or the agent's risk ceiling is below the tool's risk score.
  • pending human approval repeats — the approval was spent, expired, or the retry's arguments differ from what was approved. Check the Command Center queue.
  • this tool has no dry-run (reads ARE their own dry run) — you passed dry_run to a read tool. Just call it.
  • dry_run is a call-level flag, not a tool argument — you put dry_run inside arguments. Move it up into params beside name and arguments; nothing was executed.
  • invalid arguments: … — the call doesn't match the tool's advertised inputSchema. Each problem names the exact field (arguments.limit: must be >= 1); nothing was executed and nothing was parked for approval.
  • apply_manifest says no approval exists for these bytes — the manifest changed after the human decided, or the ask was never made. Re-run plan_manifest, confirm the manifest_hash, and ask again with request_manifest_approval.
  • apply_manifest refuses with blockers before applying anything — live state now conflicts with the approved document (someone created a role it declares, for example). The refusal names each blocker and the fix; your approval is not spent by it.