@aauth/agent
The agent-side AAuth protocol library. Signs HTTP requests, obtains person tokens, handles AAuth challenge-response flows, exchanges resource tokens for auth tokens at the person server, and polls 202 deferred responses.
Renamed from @aauth/mcp-agent: the package contains no MCP and never did. Its only runtime dependencies are @aauth/protocol and @hellocoop/httpsig.
Part of aauth-dev/packages-js. Protocol spec: dickhardt/AAuth.
Install
npm install @aauth/agent
Usage
createAAuthFetch(options): FetchLike
Creates a protocol-aware fetch that handles the full AAuth flow: signs requests, obtains a person token when a resource challenges with requirement=person-token, parses 401 AAuth-Requirement challenges, exchanges resource tokens with the person server, caches auth tokens, handles AAuth-Access session tokens, and retries.
import { createAAuthFetch } from '@aauth/agent'
const fetch = createAAuthFetch({
getKeyMaterial: async () => ({
signingKey: privateKeyJwk,
signatureKey: { type: 'jwt', jwt: agentToken }
}),
authServerUrl: 'https://ps.example',
capabilities: ['interaction', 'clarification'],
missionS256: 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk',
onInteraction: (url, code) => {
console.log(`Visit ${url}?code=${code}`)
},
onClarification: async (question) => {
return prompt(question)
},
justification: 'Read project files',
loginHint: 'user@example.com',
tenant: 'acme.com',
domainHint: 'acme.com',
})
const response = await fetch('https://resource.example/api')
There is no AAuth-Mission header in protocol -11 — it and its IANA registration were removed. A mission reaches a resource only inside a PS-issued token, as the mission_s256 claim.
requestPersonToken(options): Promise<PersonTokenResult>
Requests a person token from the PS's person_token_endpoint. A person token identifies the person the agent acts for to one resource. A resource MUST have verified one before it issues a resource token, and the agent MUST present one on every authorization endpoint request.
import { requestPersonToken } from '@aauth/agent'
const { personToken, expiresIn } = await requestPersonToken({
signedFetch: psSignedFetch,
personServerUrl: 'https://ps.example',
resource: 'https://resource.example',
missionS256: '...',
subagentToken: '...',
onInteraction: (url, code) => { },
})
The request is a signed POST presenting the agent token via Signature-Key: sig=jwt;jwt="…", with body {resource, mission_s256?, subagent_token?}. A 202 with requirement=interaction is polled at its Location like any other deferred response. upstream_token (call chaining) is not implemented.
Present the token in place of the agent token:
Signature-Key: sig=jwt;jwt="<person token>"
createPersonTokenCache(options): PersonTokenCache
Caches person tokens per (resource, mission_s256) — a person token is scoped to one resource and, when it carries mission_s256, to one mission.
import { createPersonTokenCache } from '@aauth/agent'
const personTokens = createPersonTokenCache({
signedFetch: psSignedFetch,
personServerUrl: 'https://ps.example',
})
const token = await personTokens.get('https://resource.example', missionS256)
personTokens.clear()
set(resource, missionS256, token, expiresIn) seeds a token obtained elsewhere, such as the person_tokens map a PS returns with a mission approval.
createSignedFetch(getKeyMaterial, options?): FetchLike
Creates a fetch that signs requests with HTTP Message Signatures but does not handle AAuth challenges. Use this when you only need request signing.
import { createSignedFetch } from '@aauth/agent'
const signedFetch = createSignedFetch(async () => ({
signingKey: privateKeyJwk,
signatureKey: { type: 'hwk' }
}), {
capabilities: ['interaction'],
})
const psSignedFetch = createSignedFetch(getKeyMaterial, { signBody: true })
Set signBody only for PS and AS endpoints. Resources declare what they need through additional_signature_components in their metadata, so a blanket body mandate toward a resource would be wrong.
exchangeToken(options): Promise<TokenExchangeResult>
Exchanges a resource token for an auth token at the person server. Handles metadata discovery (/.well-known/aauth-person.json), 202 deferred responses, and interaction polling.
import { exchangeToken } from '@aauth/agent'
const { authToken, expiresIn } = await exchangeToken({
signedFetch: psSignedFetch,
authServerUrl: 'https://ps.example',
resourceToken: '...',
justification: 'Read project files',
})
The auth token request has no mission parameter — the mission reaches the PS inside the resource token, which copied it from the person token.
fetchAuthServerMetadata(options) / resolveAuthServerMetadata(options)
Fetches and validates /.well-known/aauth-person.json. Both auth_token_endpoint (renamed from token_endpoint in -11) and person_token_endpoint (new in -11) are REQUIRED; a person server publishing neither cannot complete a flow, and the document is rejected. resolveAuthServerMetadata returns a caller-supplied cached copy when there is one.
pollDeferred(options): Promise<DeferredResult>
Polls a 202 Location URL until a terminal response. Handles Retry-After, Prefer: wait, clarification chat, and interaction codes.
import { pollDeferred } from '@aauth/agent'
const { response, error } = await pollDeferred({
signedFetch,
locationUrl: 'https://ps.example/pending/abc123',
interactionCode: 'ABCD1234',
onInteraction: (url, code) => { },
maxPollDuration: 900,
})
Protocol primitives
Header parsing (parseRequirementHeader, buildCapabilitiesHeader, …), access_mode planning, token typ and dwk constants, and JWT decoding live in @aauth/protocol. This package consumes them and defines none of them.
Key Material Callback
All signing functions take a GetKeyMaterial callback. This decouples key management from the protocol — you provide keys however you want:
type GetKeyMaterial = () => Promise<{
signingKey: JsonWebKey
signatureKey:
| { type: 'jwt', jwt: string }
| { type: 'hwk' }
}>
For local development, use @aauth/local-keys to provide this callback from the OS keychain.
License
MIT