Skip to content

Commit 408c68e

Browse files
committed
resolve comments + sync docs
1 parent bad37a3 commit 408c68e

File tree

7 files changed

+54
-8
lines changed

7 files changed

+54
-8
lines changed

docs/api-reference/aixyz-server.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,15 @@ await server.initialize();
9191

9292
```typescript title="app/server.ts"
9393
import { AixyzApp } from "aixyz/app";
94+
import { SessionPlugin } from "aixyz/app/plugins/session";
9495
import { IndexPagePlugin } from "aixyz/app/plugins/index-page";
9596
import { A2APlugin } from "aixyz/app/plugins/a2a";
9697
import * as agent from "./agent";
9798

9899
const server = new AixyzApp();
99100

101+
// SessionPlugin must be registered first so its middleware runs before other plugins
102+
await server.withPlugin(new SessionPlugin());
100103
await server.withPlugin(new IndexPagePlugin());
101104
await server.withPlugin(new A2APlugin([{ exports: agent }]));
102105

docs/api-reference/session-plugin.mdx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,24 @@ Implement the `SessionStore` interface for Redis, a database, or any KV store:
117117

118118
```typescript title="app/session.ts"
119119
import { defineSessionStore } from "aixyz/app/plugins/session";
120-
import type { SessionStore } from "aixyz/app/plugins/session";
120+
import type { SessionStore, ListOptions, SetOptions } from "aixyz/app/plugins/session";
121121

122122
class RedisSessionStore implements SessionStore {
123123
async get(payer: string, key: string) {
124124
return (await redis.get(`${payer}:${key}`)) ?? undefined;
125125
}
126-
async set(payer: string, key: string, value: string) {
127-
await redis.set(`${payer}:${key}`, value);
126+
async set(payer: string, key: string, value: string, options?: SetOptions) {
127+
if (options?.ttlMs) {
128+
await redis.set(`${payer}:${key}`, value, "PX", options.ttlMs);
129+
} else {
130+
await redis.set(`${payer}:${key}`, value);
131+
}
128132
}
129133
async delete(payer: string, key: string) {
130134
return (await redis.del(`${payer}:${key}`)) > 0;
131135
}
132-
async list(payer: string) {
133-
// scan for keys matching payer prefix
136+
async list(payer: string, options?: ListOptions) {
137+
// scan for keys matching payer prefix, apply options.prefix/limit/cursor
134138
return {
135139
entries: {
136140
/* ... */

docs/getting-started/payments.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,19 @@ export const facilitator = new HTTPFacilitatorClient({
137137
```
138138

139139
See the [BYO Facilitator template](/templates/advanced/with-custom-facilitator) for a working example.
140+
141+
## Payer Identity and Sessions
142+
143+
Every verified x402 payment identifies the payer by their wallet address. `SessionPlugin` (auto-registered by the build pipeline) gives each payer isolated key-value storage accessible via `getSession()`:
144+
145+
```typescript
146+
import { getSession } from "aixyz/app/plugins/session";
147+
148+
const session = getSession();
149+
if (session) {
150+
await session.set("preference", "dark-mode");
151+
const payer = session.payer; // "0x1234..."
152+
}
153+
```
154+
155+
This works in both A2A agent handlers and MCP tool handlers. See [SessionPlugin](/api-reference/session-plugin) for the full API and custom store configuration.

docs/getting-started/project-structure.mdx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ An aixyz agent is defined by a small set of files in a standard layout. Run `aix
3535
description: "Custom x402 facilitator",
3636
badge: { text: "optional override", color: "purple" },
3737
},
38+
{
39+
name: "session.ts",
40+
description: "Custom session store",
41+
badge: { text: "optional override", color: "purple" },
42+
},
3843
{
3944
name: "erc-8004.ts",
4045
description: "ERC-8004 identity registration",
@@ -126,6 +131,7 @@ An aixyz agent is defined by a small set of files in a standard layout. Run `aix
126131
| `app/tools/*.ts` | No | Tool implementations, auto-discovered by the build |
127132
| `app/server.ts` | No | [Custom server](/api-reference/aixyz-server) — overrides auto-generation entirely |
128133
| `app/accepts.ts` | No | [Custom x402 facilitator](/api-reference/accepts) for payment verification |
134+
| `app/session.ts` | No | [Custom session store](/api-reference/session-plugin) override for SessionPlugin |
129135
| `app/erc-8004.ts` | No | ERC-8004 identity registration and trust config |
130136
| `app/agent.test.ts` | No | Agent tests using `bun:test` |
131137
| `app/icon.svg` | No | Agent icon served as a static asset |
@@ -141,7 +147,8 @@ The build pipeline scans the `app/` directory to auto-generate a server:
141147
2. Imports `app/agent.ts` for the main agent definition (if present)
142148
3. Discovers all `.ts` files in `app/agents/` for sub-agents (each gets its own A2A endpoint)
143149
4. Discovers all `.ts` files in `app/tools/` (excluding `_` prefixed files)
144-
5. Wires up A2A (when agents exist), MCP (when tools exist), and x402 endpoints automatically
150+
5. Registers `SessionPlugin` (with custom store from `app/session.ts` if present)
151+
6. Wires up A2A (when agents exist), MCP (when tools exist), and x402 endpoints automatically
145152

146153
The result is a server exposing:
147154

docs/protocols/mcp.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ await server.withPlugin(
5858
);
5959
```
6060

61+
## Session Integration
62+
63+
Paid MCP tools automatically get session context. When a tool is invoked with x402 payment, `getSession()` returns the payer-scoped session inside the tool handler — no additional configuration needed.
64+
65+
```typescript
66+
import { getSession } from "aixyz/app/plugins/session";
67+
68+
// Inside an MCP tool handler:
69+
const session = getSession();
70+
await session?.set("key", "value"); // stored per-payer
71+
```
72+
73+
See [SessionPlugin](/api-reference/session-plugin) for the full API.
74+
6175
## Payment-Gated Tools
6276

6377
Tools with `accepts.scheme === "exact"` require [x402 payment](/protocols/x402) via `@x402/mcp`. The payment wrapper is applied automatically when you provide an `accepts` configuration during registration.
@@ -81,6 +95,9 @@ await server.withPlugin(
8195
<Card title="Tools" icon="folder-tree" href="/api-reference/tools">
8296
How tools are defined in the app/ directory.
8397
</Card>
98+
<Card title="SessionPlugin" icon="database" href="/api-reference/session-plugin">
99+
Payer-scoped storage for MCP tools.
100+
</Card>
84101
<Card title="A2A Protocol" icon="diagram-project" href="/protocols/a2a">
85102
Agent discovery and communication via A2A.
86103
</Card>

packages/aixyz/app/plugin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { HttpMethod, RouteHandler, RouteEntry, RouteOptions, Middleware } from "./types";
2-
import type { AcceptsX402 } from "../accepts";
32
import type {
43
PaymentGateway,
54
BeforeVerifyHook,

packages/aixyz/app/plugins/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export class MCPPlugin extends BasePlugin {
111111
}
112112

113113
async initialize(ctx: InitializeContext): Promise<void> {
114-
this.sessionPlugin = ctx.getPlugin<SessionPlugin>("session") as SessionPlugin | undefined;
114+
this.sessionPlugin = ctx.getPlugin<SessionPlugin>("session");
115115

116116
if (!ctx.payment) return;
117117

0 commit comments

Comments
 (0)