Skip to content

@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.

Terminal window
npm install @polaris-auth/agent
import { createAgent, generateAgentKey, exportPrivateJwk } from "@polaris-auth/agent";
const key = await generateAgentKey("ES256"); // or "EdDSA"; persist exportPrivateJwk(key) for the next run
const 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.

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).

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).

Terminal window
npm ci
npm run generate # src/routes.ts and src/schema.d.ts from ../client-ts/openapi.json
npm run typecheck
npm test # the unit tests against a fake fetch: request shapes, JWT claims, the poller, receipts
npm run e2e # the enrol-to-receipt walkthrough against examples/slim (installed and set up; POLARIS_URL for a running one)

MIT. Polaris for PHP is created and maintained by 2am.tech.