@aauth/mcp-agent
Agent-side AAuth for MCP. Handles signed HTTP requests, AAuth challenge-response flows, token exchange with auth servers, and 202 deferred/interaction polling.
Part of aauth-dev/packages-js. Protocol spec: dickhardt/AAuth.
Install
npm install @aauth/mcp-agent
Usage
createAAuthFetch(options): FetchLike
Creates 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 }
}),
capabilities: ['interaction', 'clarification'],
mission: { approver: 'https://ps.example', s256: '...' },
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')
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.
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/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'))
Returns:
interface AAuthChallenge {
requirement: 'auth-token' | 'approval' | 'interaction' | 'clarification' | 'claims'
resourceToken?: string
url?: string
code?: string
}
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/mcp-agent'
const { authToken, expiresIn } = await exchangeToken({
signedFetch,
authServerUrl: 'https://ps.example',
resourceToken: '...',
justification: 'Read project files',
})
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/mcp-agent'
const { response, error } = await pollDeferred({
signedFetch,
locationUrl: 'https://auth.example/pending/abc123',
interactionCode: 'ABCD1234',
onInteraction: (code, endpoint) => { },
maxPollDuration: 300,
})
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