@polaris-auth/agent
The agent SDK for Polaris for PHP: discover a Polaris server
(polaris/agents), register or enrol an agent, request capability grants and wait for their approval,
sign request JWTs, execute capabilities, mint OAuth tokens (client credentials, token exchange) and
verify the signed receipt chain. Node 20+ and the edge runtimes: fetch and WebCrypto only, no runtime
dependency. The protocol is
docs/better/agent-auth-compat.md.
npm install @polaris-auth/agentimport { createAgent, generateAgentKey, exportPrivateJwk } from "@polaris-auth/agent";
const key = await generateAgentKey("ES256"); // or "EdDSA"; persist exportPrivateJwk(key) for the next runconst sdk = await createAgent("https://app.example.com/auth", { key }); // reads <mount>/.well-known/agent-configuration
const { agent, grants } = await sdk.enroll(process.env.POLARIS_ENROLLMENT_TOKEN!, { name: "Claude Code", mode: "delegated" });const asked = await agent.requestGrants(["deploy.staging", { name: "billing.refund", constraints: { amount: { max: 50 } } }]);if (asked.approval) { // what the policy did not approve waits for a person console.log(`approve at ${asked.approval.verification_uri_complete}`); await agent.waitForApproval(asked.approval, { signal: AbortSignal.timeout(600_000) });}const { data, receipt } = await agent.execute("deploy.staging", { build: "1.4.2" });Every call of the agent is signed with its key: a request JWT (typ: agent+jwt) for the exact URL
called (aud, htu), the method (htm) and the exact body bytes (body_hash), 60 seconds long, with
a random jti; the server verifies it with the public JWK sent at registration. The private key never
leaves WebCrypto; exportPrivateJwk is for the agent’s own storage.
The API
Section titled “The API”discover(url, { fetch? }) | The discovery document under the mount (or at its own URL). |
createAgent(url, options) | discover() then new PolarisAgent({ configuration, ...options }). |
generateAgentKey(alg?, kid?), importAgentKey(privateJwk), exportPrivateJwk(key) | ES256 (P-256) or EdDSA (Ed25519); kid is the RFC 7638 thumbprint unless given. |
agent.register(ownerToken, { name, mode, organization_id?, capabilities? }) | By the owner; auth is private_key_jwt with a key, else client_secret (the pcs_ secret once). |
agent.enroll(enrollmentToken, { name, mode }) | Through a host (peh_...), under the host’s owner with its default capabilities. |
agent.requestGrants(capabilities, { reason?, preferred_method?, binding_message? }) | AAP’s { agent_id, agent_capability_grants, approval? }. |
agent.waitForApproval(approval, { signal?, maxAttempts? }) | Polls GET /agents/session every interval seconds (+5 s on a 429 or slow_down) until the grants are active; ApprovalError on denied, expired or aborted. |
agent.execute(capability, arguments, { capabilities? }) | { data, receipt: { id, seq, hash } }; capabilities restricts the grants the JWT may use. |
agent.session(), agent.capabilities() | The agent, owner, host and grants with their budgets; the capabilities with grant_status. |
agent.token({ scope?, resource?, subjectToken? }) | Client credentials (a private_key_jwt assertion, or the clientSecret as HTTP Basic); with subjectToken, the RFC 8693 exchange of a user’s token for a delegated one. resource: <mount>/mcp for the MCP server. |
agent.withToken(accessToken) | The same agent sending that token instead of request JWTs. |
agent.verifyReceipt(receipt, previous?) | Recomputes the hash as the server canonicalises it, verifies the receipt+jwt signature with /auth/.well-known/jwks.json (RS256 or EdDSA), checks the link to previous. |
register and enroll answer the record plus agent, a new PolarisAgent bound to the id; the
instance is immutable. A refusal is a PolarisAgentError with status, code (the problem’s error:
invalid_jwt, capability_not_granted, constraint_violated, budget_exhausted, …) and problem,
the RFC 9457 document (with violations when constraints failed).
Generated from the manifest
Section titled “Generated from the manifest”src/routes.ts (the route table: method, path, path parameters, body fields) and src/schema.d.ts
(the request and response types of those routes) are generated by npm run generate from
packages/client-ts/openapi.json, the client’s rendering of the manifest, filtered to the ten routes
the SDK calls; they are checked in and drift-checked in CI. Hand-written are only the cryptography (keys,
compact JWS, request JWTs and assertions, receipt verification) and the loops (the approval poller).
Development
Section titled “Development”npm cinpm run generate # src/routes.ts and src/schema.d.ts from ../client-ts/openapi.jsonnpm run typechecknpm test # the unit tests against a fake fetch: request shapes, JWT claims, the poller, receiptsnpm run e2e # the enrol-to-receipt walkthrough against examples/slim (installed and set up; POLARIS_URL for a running one)License
Section titled “License”MIT. Polaris for PHP is created and maintained by 2am.tech.