Agent-side AAuth for MCP. Handles signed HTTP requests, AAuth challenge-response flows, token exchange with auth servers, and 202 deferred/interaction polling.
See the AAuth repo for protocol overview.
npm install @aauth/mcp-agentCreates a protocol-aware fetch that handles the full AAuth flow automatically: signs requests, parses 401 challenges, exchanges tokens with the auth server, caches auth tokens, handles AAuth-Access opaque tokens (two-party mode), and retries.
import { createAAuthFetch } from '@aauth/mcp-agent'
const fetch = createAAuthFetch({
getKeyMaterial: async () => ({
signingKey: privateKeyJwk,
signatureKey: { type: 'jwt', jwt: agentToken }
}),
// Optional: declare protocol capabilities
capabilities: ['interaction', 'clarification'],
// Optional: mission context (sets AAuth-Mission header)
mission: { approver: 'https://ps.example', s256: '...' },
// Optional callbacks
onInteraction: (url, code) => {
console.log(`Visit ${url}?code=${code}`)
},
onClarification: async (question) => {
return prompt(question)
},
// Optional hints for the auth server
justification: 'Read project files',
loginHint: 'user@example.com',
tenant: 'acme.com',
domainHint: 'acme.com',
})
const response = await fetch('https://resource.example/api')When capabilities is set, every signed request includes the AAuth-Capabilities header. When mission is set, every signed request includes the AAuth-Mission header.
The fetch automatically caches and reuses AAuth-Access opaque tokens returned by resources in two-party mode, sending them back via Authorization: Bearer on subsequent requests.
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/mcp-agent'
const signedFetch = createSignedFetch(async () => ({
signingKey: privateKeyJwk,
signatureKey: { type: 'hwk' }
}), {
capabilities: ['interaction'],
mission: { approver: 'https://ps.example', s256: '...' },
})Parses an AAuth-Requirement response header into a structured challenge.
import { parseAAuthHeader } from '@aauth/mcp-agent'
const challenge = parseAAuthHeader(response.headers.get('aauth-requirement'))
// { requirement: 'auth-token', resourceToken: '...' }Returns:
interface AAuthChallenge {
requirement: 'auth-token' | 'approval' | 'interaction' | 'clarification' | 'claims'
resourceToken?: string
url?: string
code?: string
}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/mcp-agent'
const { authToken, expiresIn } = await exchangeToken({
signedFetch,
authServerUrl: 'https://ps.example',
resourceToken: '...',
justification: 'Read project files',
})Polls a 202 Location URL until a terminal response. Handles Retry-After, Prefer: wait, clarification chat, and interaction codes.
import { pollDeferred } from '@aauth/mcp-agent'
const { response, error } = await pollDeferred({
signedFetch,
locationUrl: 'https://auth.example/pending/abc123',
interactionCode: 'ABCD1234',
onInteraction: (code, endpoint) => { /* show to user */ },
maxPollDuration: 300, // seconds, default 300
})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 // Ed25519 private key for HTTP signatures
signatureKey:
| { type: 'jwt', jwt: string } // agent or auth token
| { type: 'hwk' } // bare public key (pseudonym)
}>For local development, use @aauth/local-keys to provide this callback from the OS keychain.
MIT