Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a67aea3
@aauth/mcp-stdio 3.0.0 — @aauth/agent, person-token hop
Aug 11, 2026
bc2b946
local-keys 2.0.0 — AAuth -11 PS metadata shape + fully-specified JWK …
Aug 11, 2026
d5b03f8
mcp-openclaw 3.0.0 — @aauth/agent, person tokens, protocol-driven req…
Aug 11, 2026
3259701
protocol: new @aauth/protocol 1.0.0 — AAuth -11 wire format
Aug 11, 2026
a3ab64f
@aauth/agent 3.0.0 — rename from @aauth/mcp-agent, add person tokens
Aug 11, 2026
d01e038
bootstrap 2.0.0 — AAuth -11 PS metadata, person tokens, fully-specifi…
Aug 11, 2026
1d66bd2
fetch 3.0.0 — AAuth -11 access modes, R3 -02 annotations, person-toke…
Aug 11, 2026
575c8fa
@aauth/resource 2.0.0 — resource-side reference implementation
Aug 11, 2026
5dddc64
Merge branch 'aauth-11/wp2-local-keys' into aauth-11/wp20-integration
Aug 11, 2026
6dfd96d
Merge branch 'aauth-11/wp3-agent' into aauth-11/wp20-integration
Aug 11, 2026
7130480
Merge branch 'aauth-11/wp4-resource' into aauth-11/wp20-integration
Aug 11, 2026
0061ce1
Merge branch 'aauth-11/wp5-bootstrap' into aauth-11/wp20-integration
Aug 11, 2026
538f125
Merge branch 'aauth-11/wp6-fetch' into aauth-11/wp20-integration
Aug 11, 2026
7ba2df8
Merge branch 'aauth-11/wp7-mcp-stdio' into aauth-11/wp20-integration
Aug 11, 2026
73c152d
Merge branch 'aauth-11/wp8-mcp-openclaw' into aauth-11/wp20-integration
Aug 11, 2026
a680506
Integrate WP-1..WP-8: shared files, directory moves, name reconciliat…
Aug 11, 2026
bbb34d5
e2e: rewrite the cross-package suite against mockin
Aug 12, 2026
0b92c0e
release.yml + README: shared files the directory moves broke
Aug 12, 2026
e5d8b5d
@aauth/agent fixes + R3 cross-package coverage
Aug 12, 2026
83d75dd
Name the PS metadata helpers, export isProposal, record two more gaps
Aug 12, 2026
ead768c
Resolve PS token endpoints from metadata; align endpoint fixtures
Aug 12, 2026
220f8a7
protocol: use httpsig's RFC 8941 parser, delete the hand-rolled one
Aug 12, 2026
5587260
Lock @hellocoop/httpsig at 2.1.0
Aug 12, 2026
c8619ad
e2e: resolve mockin from node_modules, not a sibling worktree
Aug 12, 2026
78e4028
Lock @hellocoop/mockin at 2.0.0
Aug 12, 2026
0edfda6
Bump jose to ^6.0.0 in local-keys and resource
Aug 13, 2026
f92ecea
Bump @hellocoop/httpsig to ^2.2.0 in agent and protocol
Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
- run: npm ci
- name: Build packages (dependency order)
run: |
for pkg in interaction-code local-keys mcp-agent mcp-server bootstrap fetch mcp-openclaw mcp-stdio; do
for pkg in protocol interaction-code local-keys agent resource bootstrap fetch mcp-openclaw mcp-stdio; do
echo "Building $pkg..."
(cd "$pkg" && npm run build)
done
Expand All @@ -88,7 +88,7 @@ jobs:
# all-must-match gate.
- name: Publish changed packages with provenance
run: |
for pkg in interaction-code local-keys mcp-agent mcp-server bootstrap fetch mcp-openclaw mcp-stdio; do
for pkg in protocol interaction-code local-keys agent resource bootstrap fetch mcp-openclaw mcp-stdio; do
version=$(node -p "require('./$pkg/package.json').version")
published=$(npm view "@aauth/$pkg" version 2>/dev/null || echo "0.0.0")
if [ "$version" = "$published" ]; then
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ AAuth is an agent-aware authentication protocol that lets AI agents prove their
|---------|-------------|
| [`@aauth/bootstrap`](./bootstrap) | CLI for setting up AAuth agent keys, person server registration, and hosting |
| [`@aauth/fetch`](./fetch) | CLI for making AAuth-authenticated HTTP requests |
| [`@aauth/mcp-agent`](./mcp-agent) | Agent-side AAuth: signed fetch, challenge-response, token exchange |
| [`@aauth/mcp-server`](./mcp-server) | Server-side AAuth: token verification, challenge building, resource tokens |
| [`@aauth/protocol`](./protocol) | Wire format: AAuth-Requirement, AAuth-Capabilities, access_mode planning, typ/dwk constants |
| [`@aauth/agent`](./agent) | Agent-side AAuth: signed fetch, person tokens, challenge-response, token exchange |
| [`@aauth/resource`](./resource) | Resource-side AAuth: token verification, challenge building, resource tokens, R3 |
| [`@aauth/local-keys`](./local-keys) | Library for managing AAuth agent signing keys across hardware and software backends |
| [`@aauth/hardware-keys`](./hardware-keys) | Native bindings for YubiKey PIV and macOS Secure Enclave |
| [`@aauth/mcp-stdio`](./mcp-stdio) | stdio-to-HTTP proxy with AAuth signatures |
Expand Down
181 changes: 181 additions & 0 deletions agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# @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`](../protocol) and `@hellocoop/httpsig`.

Part of [aauth-dev/packages-js](https://github.com/aauth-dev/packages-js). Protocol spec: [dickhardt/AAuth](https://github.com/dickhardt/AAuth).

## Install

```bash
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.

```ts
import { createAAuthFetch } from '@aauth/agent'

const fetch = createAAuthFetch({
getKeyMaterial: async () => ({
signingKey: privateKeyJwk,
signatureKey: { type: 'jwt', jwt: agentToken }
}),
// Person server — the `ps` claim of the agent token.
authServerUrl: 'https://ps.example',
// Optional: declare protocol capabilities
capabilities: ['interaction', 'clarification'],
// Optional: the mission the agent is operating under, as the base64url
// SHA-256 of the approved mission blob. Forwarded when a person token is
// requested; it then flows person token → resource token → auth token.
missionS256: 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk',
// Optional callbacks
onInteraction: (url, code) => {
console.log(`Visit ${url}?code=${code}`)
},
onClarification: async (question) => {
return prompt(question)
},
// Optional hints for the person server
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.

```ts
import { requestPersonToken } from '@aauth/agent'

const { personToken, expiresIn } = await requestPersonToken({
signedFetch: psSignedFetch, // createSignedFetch(..., { signBody: true })
personServerUrl: 'https://ps.example',
resource: 'https://resource.example',
missionS256: '...', // optional
subagentToken: '...', // optional — parent requesting for a sub-agent
onInteraction: (url, code) => { /* the PS may ask the person first */ },
})
```

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:

```http
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.

```ts
import { createPersonTokenCache } from '@aauth/agent'

const personTokens = createPersonTokenCache({
signedFetch: psSignedFetch,
personServerUrl: 'https://ps.example',
})

const token = await personTokens.get('https://resource.example', missionS256)

// One rotation of the agent's signing key invalidates every cached token at
// once — they all bind that key through `cnf`. Flush and re-request lazily.
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.

```ts
import { createSignedFetch } from '@aauth/agent'

const signedFetch = createSignedFetch(async () => ({
signingKey: privateKeyJwk,
signatureKey: { type: 'hwk' }
}), {
capabilities: ['interaction'],
})

// For PS and AS endpoints: a request carrying a body additionally signs
// `content-digest` and `content-type`.
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.

```ts
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.

```ts
import { pollDeferred } from '@aauth/agent'

const { response, error } = await pollDeferred({
signedFetch,
locationUrl: 'https://ps.example/pending/abc123',
interactionCode: 'ABCD1234',
onInteraction: (url, code) => { /* show to user */ },
maxPollDuration: 900, // seconds, default 900
})
```

## Protocol primitives

Header parsing (`parseRequirementHeader`, `buildCapabilitiesHeader`, …), `access_mode` planning, token `typ` and `dwk` constants, and JWT decoding live in [`@aauth/protocol`](../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:

```ts
type GetKeyMaterial = () => Promise<{
signingKey: JsonWebKey // Ed25519 private key for HTTP signatures
signatureKey:
| { type: 'jwt', jwt: string } // agent, person, or auth token
| { type: 'hwk' } // bare public key (pseudonym)
}>
```

For local development, use [`@aauth/local-keys`](../local-keys) to provide this callback from the OS keychain.

## License

MIT
13 changes: 7 additions & 6 deletions mcp-agent/package.json → agent/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@aauth/mcp-agent",
"version": "2.0.0",
"description": "Authenticated MCP transport with HTTP Signatures for AAuth agents",
"name": "@aauth/agent",
"version": "3.0.0",
"description": "Agent-side AAuth protocol library — HTTP Signatures, person tokens, token exchange, deferred polling",
"type": "module",
"exports": {
".": {
Expand All @@ -18,7 +18,7 @@
},
"keywords": [
"aauth",
"mcp",
"agent",
"http-signatures"
],
"author": "Dick Hardt <dick.hardt@hello.coop>",
Expand All @@ -29,10 +29,11 @@
"repository": {
"type": "git",
"url": "https://github.com/aauth-dev/packages-js",
"directory": "mcp-agent"
"directory": "agent"
},
"dependencies": {
"@hellocoop/httpsig": "^2.0.0"
"@aauth/protocol": "^1.0.0",
"@hellocoop/httpsig": "^2.2.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
Expand Down
Loading