@naulon/wayfarer-mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -0
- package/dist/cloud-signer.d.ts +63 -0
- package/dist/cloud-signer.d.ts.map +1 -0
- package/dist/cloud-signer.js +125 -0
- package/dist/cloud-signer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +11 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +11 -0
- package/dist/lib.js.map +1 -0
- package/dist/server.d.ts +159 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +866 -0
- package/dist/server.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# @naulon/wayfarer-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets any
|
|
4
|
+
LLM **discover, quote, pay, and cite** naulon-tolled sources — bring your own
|
|
5
|
+
wallet, local stdio, the [wayfarer](../wayfarer) brain running in-process.
|
|
6
|
+
|
|
7
|
+
Point any MCP-capable client at this server and the model gains tools to find
|
|
8
|
+
tolled articles, get a quote, pay the `402` toll from a wallet you control, and
|
|
9
|
+
cite what it bought — the same budgeted buying loop the CLI agent runs, exposed
|
|
10
|
+
as callable tools and slash commands.
|
|
11
|
+
|
|
12
|
+
Works with **any MCP client**: Claude Code, Claude Desktop, Cursor, Windsurf,
|
|
13
|
+
Cline, VS Code, or your own host. Setup for each is below.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx -y @naulon/wayfarer-mcp # runs the stdio MCP server
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The package is scoped (`@naulon/wayfarer-mcp`); the binary it installs is
|
|
24
|
+
`wayfarer-mcp`. It runs **offline against mock settlement by default** — safe to
|
|
25
|
+
try with zero config, no wallet, no spend. To pay real tolls, see
|
|
26
|
+
[Configuration](#configuration).
|
|
27
|
+
|
|
28
|
+
The canonical registration, which every client below is a variant of:
|
|
29
|
+
|
|
30
|
+
```jsonc
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"naulon": { "command": "npx", "args": ["-y", "@naulon/wayfarer-mcp"] }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Slash commands (prompts)
|
|
41
|
+
|
|
42
|
+
Every prompts-capable client surfaces these as native, argument-taking slash
|
|
43
|
+
commands — no per-user config. In Claude Code / Desktop they appear as
|
|
44
|
+
`/mcp__naulon__<name>` (the `naulon` segment is whatever you named the server):
|
|
45
|
+
|
|
46
|
+
| Prompt | Argument | Does |
|
|
47
|
+
|--------|----------|------|
|
|
48
|
+
| `research` | `topic` | Discover sources, see prices, return a grounded cited answer within budget. |
|
|
49
|
+
| `discover` | `topic` | List candidate sources — **free**, no payment. |
|
|
50
|
+
| `verify` | `claim` | Fact-check a claim against tolled sources, citing what it paid for. |
|
|
51
|
+
| `ask`\* | `question` | Hosted reading agent: pays per citation, returns a grounded answer. |
|
|
52
|
+
|
|
53
|
+
\* `ask` is only present on the **hosted** endpoint (it drives the cloud
|
|
54
|
+
`naulon_ask` tool). The stdio server exposes `research` / `discover` / `verify`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Per-client setup
|
|
59
|
+
|
|
60
|
+
### Claude Code
|
|
61
|
+
|
|
62
|
+
CLI (recommended — `--scope project` writes a shared `.mcp.json`, `user` makes it
|
|
63
|
+
global across your projects):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude mcp add naulon --scope user -- npx -y @naulon/wayfarer-mcp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then `/mcp` inside Claude Code to confirm it connected, and type `/` to see the
|
|
70
|
+
`research` / `discover` / `verify` prompts. Or add it by hand to `.mcp.json`
|
|
71
|
+
(project) using the canonical block above.
|
|
72
|
+
|
|
73
|
+
### Claude Desktop
|
|
74
|
+
|
|
75
|
+
Edit `claude_desktop_config.json`
|
|
76
|
+
(macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`):
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"naulon": { "command": "npx", "args": ["-y", "@naulon/wayfarer-mcp"] }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Restart Claude Desktop. Prompts appear in the `+` / slash-command menu.
|
|
87
|
+
|
|
88
|
+
### Cursor
|
|
89
|
+
|
|
90
|
+
`~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project), key
|
|
91
|
+
`mcpServers` — same canonical block above.
|
|
92
|
+
|
|
93
|
+
### Windsurf
|
|
94
|
+
|
|
95
|
+
`~/.codeium/windsurf/mcp_config.json`, key `mcpServers` — same canonical block.
|
|
96
|
+
|
|
97
|
+
### VS Code (native MCP / Copilot agent)
|
|
98
|
+
|
|
99
|
+
`.vscode/mcp.json` (workspace). VS Code uses the `servers` key (not
|
|
100
|
+
`mcpServers`); the `type` is inferred as stdio from `command`, so it's optional:
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
{
|
|
104
|
+
"servers": {
|
|
105
|
+
"naulon": { "command": "npx", "args": ["-y", "@naulon/wayfarer-mcp"] }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Cline
|
|
111
|
+
|
|
112
|
+
Cline → MCP Servers → Configure (`cline_mcp_settings.json`), key `mcpServers` —
|
|
113
|
+
same canonical block.
|
|
114
|
+
|
|
115
|
+
### Any other MCP host
|
|
116
|
+
|
|
117
|
+
Spawn the stdio binary and speak MCP over its stdio transport:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
command: npx args: ["-y", "@naulon/wayfarer-mcp"]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Hosted endpoint (no local wallet)
|
|
126
|
+
|
|
127
|
+
naulon-cloud exposes the same brain over **Streamable HTTP** at `/_naulon/mcp`,
|
|
128
|
+
authenticated with an agent token — tolls are signed by naulon's custody-free
|
|
129
|
+
session key, so **no private key ever touches your machine**. This endpoint also
|
|
130
|
+
adds the cloud-only `naulon_ask` tool + its `ask` prompt.
|
|
131
|
+
|
|
132
|
+
Clients that support remote/HTTP MCP with headers:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Claude Code
|
|
136
|
+
claude mcp add --transport http naulon \
|
|
137
|
+
https://<your-naulon-host>/_naulon/mcp \
|
|
138
|
+
--header "Authorization: Bearer <AGENT_TOKEN>"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```jsonc
|
|
142
|
+
// Generic HTTP MCP config
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"naulon": {
|
|
146
|
+
"type": "http",
|
|
147
|
+
"url": "https://<your-naulon-host>/_naulon/mcp",
|
|
148
|
+
"headers": { "Authorization": "Bearer <AGENT_TOKEN>" }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Mint the agent token from your naulon buyer wallet / dashboard. Spend is bounded
|
|
155
|
+
by the server budget **and** the token's sub-cap — the model can lower a run's
|
|
156
|
+
budget, never raise it past either.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
Env read by the stdio server (all optional — omit for the offline mock):
|
|
163
|
+
|
|
164
|
+
| Var | Purpose |
|
|
165
|
+
|-----|---------|
|
|
166
|
+
| `PAYMENT_MODE` | `gateway` to pay real tolls over Circle Gateway on Arc Network (default: mock). |
|
|
167
|
+
| `BUYER_PRIVATE_KEY` | The wallet the toll is paid from. BYO-key path; a hosted deploy signs through a cloud signer instead. |
|
|
168
|
+
| `TOLLGATE_URL` | The gate every payment resolves against. Payments only ever flow here — a prompt-injected model cannot redirect them. |
|
|
169
|
+
| `WAYFARER_BUDGET_USDC` | The session spend ceiling. The model can never raise it. |
|
|
170
|
+
| `WAYFARER_ALLOW_DOMAINS` / `WAYFARER_DENY_DOMAINS` | Publisher allow/deny lists for `naulon_research`. |
|
|
171
|
+
| `WAYFARER_PER_DOMAIN_CAP` | Max paid reads per publisher per session. |
|
|
172
|
+
| `WAYFARER_KILL_SWITCH` | Hard stop — refuse all spend. |
|
|
173
|
+
|
|
174
|
+
Budget and wallet are **server config, never tool arguments** — the model plans
|
|
175
|
+
spend within the envelope but can't widen it.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Tools
|
|
180
|
+
|
|
181
|
+
| Tool | Cost | Does |
|
|
182
|
+
|------|------|------|
|
|
183
|
+
| `naulon_discover` | free | Candidate teasers for a topic (slug, title, summary). Start here. |
|
|
184
|
+
| `naulon_appraise` | free | Relevance + rationale for teasers already held. |
|
|
185
|
+
| `naulon_quote` | free | The x402 `402` probe — real price + terms, **no spend**. |
|
|
186
|
+
| `naulon_pay_and_read` | **$** | Pays the toll, returns content + settlement ref + citation license. |
|
|
187
|
+
| `naulon_read_held` | free | Re-read a held live license (PoP-signed if cnf-bound). |
|
|
188
|
+
| `naulon_research` | **$** | One composite that runs the whole discover→quote→pay→ground loop. |
|
|
189
|
+
| `naulon_ask`\* | **$** | Hosted-only reading agent — grounded, numbered-citation answer. |
|
|
190
|
+
|
|
191
|
+
\* hosted endpoint only. All tools carry MCP annotations (`readOnlyHint` on the
|
|
192
|
+
free ones) so clients render safe-vs-spends correctly.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
MIT.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cloud MemoSigner (BUY-2). Wires the wayfarer memo buyer to naulon's hosted, grant-checked
|
|
3
|
+
* signer instead of a local private key: each EIP-3009 leg is POSTed to `/_naulon/buyer-wallet/
|
|
4
|
+
* sign-memo`, which holds the encrypted session key, enforces the grant (cap + TTL), and returns just
|
|
5
|
+
* the signature. So the MCP process never touches a private key — the custody-free hosted path.
|
|
6
|
+
*
|
|
7
|
+
* The endpoint + token are SERVER-CONFIG (env), never LLM tool args — the model cannot point the
|
|
8
|
+
* signer elsewhere or raise its own spend ceiling. The chainId is taken from the request's own domain,
|
|
9
|
+
* so it can never drift from what wayfarer is paying against.
|
|
10
|
+
*/
|
|
11
|
+
import type { AgentWallet, MemoSigner } from "@naulon/wayfarer";
|
|
12
|
+
/** The grant CAP/budget refused the spend — funding the session is the remedy. The message STARTS with the
|
|
13
|
+
* raw code (`grant_exceeded`) so memo.ts's message-prefix classifier maps it to `needs_topup`, not a
|
|
14
|
+
* retryable `origin_error` (mirrors in-process-signer.ts, which already throws code-prefixed messages). */
|
|
15
|
+
export declare class GrantExceededError extends Error {
|
|
16
|
+
readonly remainingMicro?: number | undefined;
|
|
17
|
+
constructor(remainingMicro?: number | undefined);
|
|
18
|
+
}
|
|
19
|
+
/** The grant WINDOW lapsed though funds may be intact — RENEWING the session is the remedy, not a top-up.
|
|
20
|
+
* Distinct class + code-prefixed message so the hosted agent renews rather than funds. */
|
|
21
|
+
export declare class GrantExpiredError extends Error {
|
|
22
|
+
readonly remainingMicro?: number | undefined;
|
|
23
|
+
constructor(remainingMicro?: number | undefined);
|
|
24
|
+
}
|
|
25
|
+
/** Any other non-ok response from the signer BFF (bad_from, no_session, chain_mismatch, 5xx, …). */
|
|
26
|
+
export declare class SignerError extends Error {
|
|
27
|
+
readonly status: number;
|
|
28
|
+
readonly code?: string | undefined;
|
|
29
|
+
constructor(status: number, code?: string | undefined);
|
|
30
|
+
}
|
|
31
|
+
export interface CloudSignerOpts {
|
|
32
|
+
/** Cloud gate base URL, no trailing slash (e.g. https://gate.naulon.app). This is the
|
|
33
|
+
* naulon gate origin that serves the `/_naulon/*` routes — NOT the REST/API edge. */
|
|
34
|
+
endpoint: string;
|
|
35
|
+
/** Per-session bearer token — server-config, never a tool arg. */
|
|
36
|
+
token: string;
|
|
37
|
+
/** The provisioned session EOA address (the `from` every leg signs as). */
|
|
38
|
+
address: `0x${string}`;
|
|
39
|
+
/** Injectable for tests. */
|
|
40
|
+
fetchImpl?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the cloud signer from environment — the hosted path is opt-in via SERVER-CONFIG, never a tool
|
|
44
|
+
* arg. All three of NAULON_CLOUD_ENDPOINT / NAULON_CLOUD_TOKEN / NAULON_BUYER_SESSION_ADDRESS must be
|
|
45
|
+
* present (and the address well-formed) or we return undefined and the caller falls back to the
|
|
46
|
+
* BYO-key path. This keeps the OSS default (local key) exactly as-is when the cloud isn't configured.
|
|
47
|
+
*/
|
|
48
|
+
export declare function cloudSignerFromEnv(env?: Record<string, string | undefined>): MemoSigner | undefined;
|
|
49
|
+
export declare function cloudMemoSigner(opts: CloudSignerOpts): MemoSigner;
|
|
50
|
+
/**
|
|
51
|
+
* The cloud PoP signer (Phase 4 / C2). A held re-read of a cnf-bound license must prove control of
|
|
52
|
+
* the paying wallet by signing an EIP-191 holder-of-key challenge. On the custody-free hosted path the
|
|
53
|
+
* paying identity is the session EOA, whose key lives encrypted in the cloud — so the proof is signed
|
|
54
|
+
* by `/_naulon/buyer-wallet/sign-pop` (the session key) rather than a local key. Returns an
|
|
55
|
+
* `AgentWallet` so `buildPopProof` consumes it exactly like the env wallet.
|
|
56
|
+
*
|
|
57
|
+
* Unlike `/sign-memo` this leg is GRANT-FREE: a PoP is a free re-read, not a spend, so there is no cap
|
|
58
|
+
* to debit. The BFF still authenticates the bearer, checks the `address` matches the session, AND
|
|
59
|
+
* constrains the signed bytes to a canonical `naulon-pop` challenge — the session key can never be
|
|
60
|
+
* coerced into signing an arbitrary message. The endpoint + token are SERVER-CONFIG, never a tool arg.
|
|
61
|
+
*/
|
|
62
|
+
export declare function cloudPopSigner(opts: CloudSignerOpts): AgentWallet;
|
|
63
|
+
//# sourceMappingURL=cloud-signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-signer.d.ts","sourceRoot":"","sources":["../src/cloud-signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEhE;;4GAE4G;AAC5G,qBAAa,kBAAmB,SAAQ,KAAK;aACf,cAAc,CAAC,EAAE,MAAM;gBAAvB,cAAc,CAAC,EAAE,MAAM,YAAA;CAIpD;AAED;2FAC2F;AAC3F,qBAAa,iBAAkB,SAAQ,KAAK;aACd,cAAc,CAAC,EAAE,MAAM;gBAAvB,cAAc,CAAC,EAAE,MAAM,YAAA;CAIpD;AAED,oGAAoG;AACpG,qBAAa,WAAY,SAAQ,KAAK;aAElB,MAAM,EAAE,MAAM;aACd,IAAI,CAAC,EAAE,MAAM;gBADb,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKhC;AAED,MAAM,WAAW,eAAe;IAC9B;0FACsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IACvB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,UAAU,GAAG,SAAS,CAOxB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,UAAU,CAwCjE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAoBjE"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The grant CAP/budget refused the spend — funding the session is the remedy. The message STARTS with the
|
|
2
|
+
* raw code (`grant_exceeded`) so memo.ts's message-prefix classifier maps it to `needs_topup`, not a
|
|
3
|
+
* retryable `origin_error` (mirrors in-process-signer.ts, which already throws code-prefixed messages). */
|
|
4
|
+
export class GrantExceededError extends Error {
|
|
5
|
+
remainingMicro;
|
|
6
|
+
constructor(remainingMicro) {
|
|
7
|
+
super(`grant_exceeded${remainingMicro !== undefined ? ` (remaining ${remainingMicro})` : ""}`);
|
|
8
|
+
this.remainingMicro = remainingMicro;
|
|
9
|
+
this.name = "GrantExceededError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** The grant WINDOW lapsed though funds may be intact — RENEWING the session is the remedy, not a top-up.
|
|
13
|
+
* Distinct class + code-prefixed message so the hosted agent renews rather than funds. */
|
|
14
|
+
export class GrantExpiredError extends Error {
|
|
15
|
+
remainingMicro;
|
|
16
|
+
constructor(remainingMicro) {
|
|
17
|
+
super(`grant_expired${remainingMicro !== undefined ? ` (remaining ${remainingMicro})` : ""}`);
|
|
18
|
+
this.remainingMicro = remainingMicro;
|
|
19
|
+
this.name = "GrantExpiredError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Any other non-ok response from the signer BFF (bad_from, no_session, chain_mismatch, 5xx, …). */
|
|
23
|
+
export class SignerError extends Error {
|
|
24
|
+
status;
|
|
25
|
+
code;
|
|
26
|
+
constructor(status, code) {
|
|
27
|
+
super(`sign-memo failed: ${status}${code ? ` ${code}` : ""}`);
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.name = "SignerError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the cloud signer from environment — the hosted path is opt-in via SERVER-CONFIG, never a tool
|
|
35
|
+
* arg. All three of NAULON_CLOUD_ENDPOINT / NAULON_CLOUD_TOKEN / NAULON_BUYER_SESSION_ADDRESS must be
|
|
36
|
+
* present (and the address well-formed) or we return undefined and the caller falls back to the
|
|
37
|
+
* BYO-key path. This keeps the OSS default (local key) exactly as-is when the cloud isn't configured.
|
|
38
|
+
*/
|
|
39
|
+
export function cloudSignerFromEnv(env = process.env) {
|
|
40
|
+
const endpoint = env.NAULON_CLOUD_ENDPOINT;
|
|
41
|
+
const token = env.NAULON_CLOUD_TOKEN;
|
|
42
|
+
const address = env.NAULON_BUYER_SESSION_ADDRESS;
|
|
43
|
+
if (!endpoint || !token || !address)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
46
|
+
return undefined;
|
|
47
|
+
return cloudMemoSigner({ endpoint, token, address: address });
|
|
48
|
+
}
|
|
49
|
+
export function cloudMemoSigner(opts) {
|
|
50
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
51
|
+
return {
|
|
52
|
+
address: opts.address,
|
|
53
|
+
async signTypedData(args) {
|
|
54
|
+
const m = args.message;
|
|
55
|
+
const res = await doFetch(`${opts.endpoint}/_naulon/buyer-wallet/sign-memo`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { authorization: `Bearer ${opts.token}`, "content-type": "application/json" },
|
|
58
|
+
// BigInt can't be JSON-serialized — send the EIP-3009 fields as decimal strings. The BFF
|
|
59
|
+
// rebuilds the domain + types authoritatively; we send only the leg's message primitives.
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
chainId: Number(args.domain.chainId),
|
|
62
|
+
message: {
|
|
63
|
+
from: m.from,
|
|
64
|
+
to: m.to,
|
|
65
|
+
value: m.value.toString(),
|
|
66
|
+
validAfter: m.validAfter.toString(),
|
|
67
|
+
validBefore: m.validBefore.toString(),
|
|
68
|
+
nonce: m.nonce,
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
if (res.status === 402) {
|
|
73
|
+
// The BFF returns 402 for BOTH grant stops (sign.ts:57/60). Read `error` to pick the remedy:
|
|
74
|
+
// grant_expired ⇒ renew (funds intact); grant_exceeded / anything else ⇒ top up (fund the session).
|
|
75
|
+
const body = (await res.json().catch(() => ({})));
|
|
76
|
+
throw body.error === "grant_expired"
|
|
77
|
+
? new GrantExpiredError(body.remainingMicro)
|
|
78
|
+
: new GrantExceededError(body.remainingMicro);
|
|
79
|
+
}
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
const body = (await res.json().catch(() => ({})));
|
|
82
|
+
throw new SignerError(res.status, body.error);
|
|
83
|
+
}
|
|
84
|
+
const body = (await res.json().catch(() => ({})));
|
|
85
|
+
if (!body.signature)
|
|
86
|
+
throw new SignerError(res.status, "no_signature");
|
|
87
|
+
return body.signature;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The cloud PoP signer (Phase 4 / C2). A held re-read of a cnf-bound license must prove control of
|
|
93
|
+
* the paying wallet by signing an EIP-191 holder-of-key challenge. On the custody-free hosted path the
|
|
94
|
+
* paying identity is the session EOA, whose key lives encrypted in the cloud — so the proof is signed
|
|
95
|
+
* by `/_naulon/buyer-wallet/sign-pop` (the session key) rather than a local key. Returns an
|
|
96
|
+
* `AgentWallet` so `buildPopProof` consumes it exactly like the env wallet.
|
|
97
|
+
*
|
|
98
|
+
* Unlike `/sign-memo` this leg is GRANT-FREE: a PoP is a free re-read, not a spend, so there is no cap
|
|
99
|
+
* to debit. The BFF still authenticates the bearer, checks the `address` matches the session, AND
|
|
100
|
+
* constrains the signed bytes to a canonical `naulon-pop` challenge — the session key can never be
|
|
101
|
+
* coerced into signing an arbitrary message. The endpoint + token are SERVER-CONFIG, never a tool arg.
|
|
102
|
+
*/
|
|
103
|
+
export function cloudPopSigner(opts) {
|
|
104
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
105
|
+
return {
|
|
106
|
+
address: opts.address,
|
|
107
|
+
mock: false,
|
|
108
|
+
signMessage: async (message) => {
|
|
109
|
+
const res = await doFetch(`${opts.endpoint}/_naulon/buyer-wallet/sign-pop`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { authorization: `Bearer ${opts.token}`, "content-type": "application/json" },
|
|
112
|
+
body: JSON.stringify({ address: opts.address, message }),
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
const body = (await res.json().catch(() => ({})));
|
|
116
|
+
throw new SignerError(res.status, body.error);
|
|
117
|
+
}
|
|
118
|
+
const body = (await res.json().catch(() => ({})));
|
|
119
|
+
if (!body.signature)
|
|
120
|
+
throw new SignerError(res.status, "no_signature");
|
|
121
|
+
return body.signature;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=cloud-signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-signer.js","sourceRoot":"","sources":["../src/cloud-signer.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* stdio entry point — an MCP client (Claude Desktop, Cursor, …) spawns this as a
|
|
4
|
+
* child process and speaks JSON-RPC over stdin/stdout.
|
|
5
|
+
*
|
|
6
|
+
* Hard rule for stdio servers: stdout is the protocol channel. Nothing but
|
|
7
|
+
* JSON-RPC may be written there — all diagnostics go to stderr, or they corrupt
|
|
8
|
+
* the stream.
|
|
9
|
+
*/
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { buildServer } from "./server.js";
|
|
12
|
+
async function main() {
|
|
13
|
+
const server = buildServer();
|
|
14
|
+
const transport = new StdioServerTransport();
|
|
15
|
+
await server.connect(transport);
|
|
16
|
+
process.stderr.write("naulon-wayfarer-mcp: listening on stdio\n");
|
|
17
|
+
}
|
|
18
|
+
main().catch((err) => {
|
|
19
|
+
process.stderr.write(`naulon-wayfarer-mcp: failed to start — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;AACpE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrH,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/wayfarer-mcp — public library surface.
|
|
3
|
+
*
|
|
4
|
+
* The side-effect-free barrel the package.json `exports` map points at. `index.ts`
|
|
5
|
+
* (the stdio bin) is a thin consumer of `buildServer`, never the other way round —
|
|
6
|
+
* so the cloud can `buildServer()` and wrap it over its own authenticated transport
|
|
7
|
+
* without dragging in the stdio bootstrap.
|
|
8
|
+
*/
|
|
9
|
+
export { buildServer, SERVER_NAME, SERVER_VERSION, type BuildServerOptions, type DecisionAuditEvent } from "./server.ts";
|
|
10
|
+
export { cloudMemoSigner, cloudPopSigner, cloudSignerFromEnv, GrantExceededError, SignerError, type CloudSignerOpts, } from "./cloud-signer.ts";
|
|
11
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACzH,OAAO,EACL,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC"}
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/wayfarer-mcp — public library surface.
|
|
3
|
+
*
|
|
4
|
+
* The side-effect-free barrel the package.json `exports` map points at. `index.ts`
|
|
5
|
+
* (the stdio bin) is a thin consumer of `buildServer`, never the other way round —
|
|
6
|
+
* so the cloud can `buildServer()` and wrap it over its own authenticated transport
|
|
7
|
+
* without dragging in the stdio bootstrap.
|
|
8
|
+
*/
|
|
9
|
+
export { buildServer, SERVER_NAME, SERVER_VERSION } from "./server.js";
|
|
10
|
+
export { cloudMemoSigner, cloudPopSigner, cloudSignerFromEnv, GrantExceededError, SignerError, } from "./cloud-signer.js";
|
|
11
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAoD,MAAM,aAAa,CAAC;AACzH,OAAO,EACL,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,GAEZ,MAAM,mBAAmB,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/wayfarer-mcp — the MCP server factory.
|
|
3
|
+
*
|
|
4
|
+
* Exposes naulon's pay-per-citation pipeline to any MCP-capable LLM. The wayfarer
|
|
5
|
+
* brain runs IN-PROCESS here (no remote service, BYO wallet never leaves the
|
|
6
|
+
* machine) — the deliberate contrast to a thin client that pays a hosted brain.
|
|
7
|
+
*
|
|
8
|
+
* Tools are deliberately GRANULAR and quote-first: the host model sees prices and
|
|
9
|
+
* plans spend before any payment, rather than calling one black-box "research"
|
|
10
|
+
* verb. The §3.1 surface (BUY-1.2) is:
|
|
11
|
+
*
|
|
12
|
+
* naulon_discover free — candidate teasers for a topic
|
|
13
|
+
* naulon_appraise free — relevance + rationale for teasers the model holds
|
|
14
|
+
* naulon_quote free — the x402 402 probe: real price + terms, NO spend
|
|
15
|
+
* naulon_pay_and_read $ — pays, returns content + settlementRef + license jti
|
|
16
|
+
* naulon_read_held free — re-read a held live license (PoP-signed if cnf-bound)
|
|
17
|
+
* naulon_research $ — one composite that runs the whole loop for lazy clients
|
|
18
|
+
*
|
|
19
|
+
* Two deliberate shapes vs the spec's conceptual `url(...)` signatures:
|
|
20
|
+
* - Tools take a SLUG, never a raw URL. The server resolves it against the
|
|
21
|
+
* server-configured gate (TOLLGATE_URL), so a prompt-injected model can never
|
|
22
|
+
* redirect a payment to an attacker's endpoint — payment only ever flows to
|
|
23
|
+
* the configured gate. (Same "config, not tool args" principle BUY-1.3 applies
|
|
24
|
+
* to the budget + wallet.)
|
|
25
|
+
* - `kind` is pinned to "citation": the MCP's purpose is grounded, citable
|
|
26
|
+
* research, so every quote/pay/re-read asks for a citation license.
|
|
27
|
+
*
|
|
28
|
+
* Budget + wallet are SERVER-CONFIG, never tool args (BUY-1.3). The wallet comes
|
|
29
|
+
* from the env (`BUYER_PRIVATE_KEY` / the dev key) via `getWallet()`; the budget is
|
|
30
|
+
* a single ceiling (`WAYFARER_BUDGET_USDC`) the model cannot raise. Each server
|
|
31
|
+
* instance carries a SESSION SPEND ENVELOPE: every paid read debits a running total,
|
|
32
|
+
* the free tools report "$Y remaining", and a spend that would exceed the ceiling is
|
|
33
|
+
* refused (spending nothing). `naulon_research` accepts an optional `budgetUsdc` the
|
|
34
|
+
* server CLAMPS to what remains — the model can spend less, never more.
|
|
35
|
+
*
|
|
36
|
+
* The explicit toll-moved tolerance + validity-at-pay margin guards are BUY-1.4.
|
|
37
|
+
*/
|
|
38
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
39
|
+
import type { AgentWallet, DecisionPolicy, GatewaySigner, HeldStore, MemoSigner, RailSigners } from "@naulon/wayfarer";
|
|
40
|
+
export declare const SERVER_NAME = "naulon-wayfarer-mcp";
|
|
41
|
+
/** Keep in step with this package's package.json `version` — it is what the MCP
|
|
42
|
+
* handshake reports as `serverInfo.version`. */
|
|
43
|
+
export declare const SERVER_VERSION = "0.2.0";
|
|
44
|
+
/**
|
|
45
|
+
* Per-session options for the HOSTED path (BUY-4). The stdio funnel reads the
|
|
46
|
+
* wallet, budget, and policy from process env (one process = one buyer). The cloud
|
|
47
|
+
* host authenticates MANY buyer sessions over one process, so it injects each
|
|
48
|
+
* session's config here — and each injected value WINS over env, which stays only
|
|
49
|
+
* the default when the option is absent (so the stdio funnel is unchanged). One
|
|
50
|
+
* `buildServer(opts)` per authed session gives per-session budget envelopes for
|
|
51
|
+
* free, since the envelope is a closure over this call.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* One buyer spend DECISION, handed to a `BuildServerOptions.auditSink` (BUY-4.4). The
|
|
55
|
+
* hosted cloud injects a sink that writes each of these to its org-partitioned audit
|
|
56
|
+
* plane — the same immutable log the sell side uses. A PURE structured hook: the package
|
|
57
|
+
* never imports the cloud (the one-way dependency), so the sink is injected, not called.
|
|
58
|
+
*
|
|
59
|
+
* The four actions mirror the wayfarer decision engine exactly (pay · skip · approve ·
|
|
60
|
+
* cache). A kill-switch halt or a policy denial surfaces as a `skip` with the halt reason
|
|
61
|
+
* carried verbatim in `reason` — so nothing is lost to accountability even though it is
|
|
62
|
+
* not its own verb (the engine emits no distinct "kill"/"deny" action; adding one would
|
|
63
|
+
* be a decide.ts change, not an audit concern).
|
|
64
|
+
*/
|
|
65
|
+
export interface DecisionAuditEvent {
|
|
66
|
+
/** The source slug the decision was about. */
|
|
67
|
+
slug: string;
|
|
68
|
+
/** The engine decision: paid · skipped (policy/budget/relevance) · flagged for human approval · re-read a held license free. */
|
|
69
|
+
action: "pay" | "skip" | "approve" | "cache";
|
|
70
|
+
/** The engine's human-readable reason (carries the kill-switch / deny / budget nuance verbatim). */
|
|
71
|
+
reason: string;
|
|
72
|
+
/** The candidate's 0..1 relevance for the topic (research decisions). */
|
|
73
|
+
relevance?: number;
|
|
74
|
+
/** The advertised author-leg price in USDC, when known. */
|
|
75
|
+
priceUsdc?: number;
|
|
76
|
+
/** The author leg actually paid, in USDC (pay decisions only). */
|
|
77
|
+
paidUsdc?: number;
|
|
78
|
+
/** The true total debited across all settlement legs, in USDC (pay_and_read pays only). */
|
|
79
|
+
costUsdc?: number;
|
|
80
|
+
/** The on-chain / settlement reference (pay decisions only). */
|
|
81
|
+
settlementRef?: string;
|
|
82
|
+
/** The Citation License jti (pay decisions only). */
|
|
83
|
+
licenseId?: string;
|
|
84
|
+
/** The policy's agent tag, if configured — audit attribution, not identity. */
|
|
85
|
+
agentId?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface BuildServerOptions {
|
|
88
|
+
/**
|
|
89
|
+
* This session's custody-free cloud signer (else `cloudSignerFromEnv()`). A `MemoSigner`
|
|
90
|
+
* (memo/Arc rail) or a `GatewaySigner` (memo-less Circle rails — Base + every Gateway
|
|
91
|
+
* chain); the cloud injects the one matching the active settlement network's rail, and
|
|
92
|
+
* `buildServer` routes it to the matching buyer (`supportsMemo(activeNetwork())`).
|
|
93
|
+
*/
|
|
94
|
+
signer?: MemoSigner | GatewaySigner;
|
|
95
|
+
/**
|
|
96
|
+
* A mixed-fleet session's BOTH rail signers (RAS-B): the cloud injects a memo AND a gateway
|
|
97
|
+
* signer wrapping the same sealed session key, and `buildServer` builds a `railBuyer` that picks
|
|
98
|
+
* the rail from each tenant's advertised 402 — so one process serves an Arc-default fleet with a
|
|
99
|
+
* Base tenant. Takes precedence over `signer` when present; the single-rail `signer` path stays for
|
|
100
|
+
* the stdio funnel and any host that settles on one network. Both wrap the same key, so the payer
|
|
101
|
+
* identity is either signer's `address` (they are equal).
|
|
102
|
+
*/
|
|
103
|
+
railSigners?: RailSigners;
|
|
104
|
+
/** This session's spend ceiling in USDC (else `WAYFARER_BUDGET_USDC`). */
|
|
105
|
+
budgetUsdc?: number;
|
|
106
|
+
/** This session's decision policy (else the env-derived policy over DEFAULT_POLICY). */
|
|
107
|
+
policy?: DecisionPolicy;
|
|
108
|
+
/**
|
|
109
|
+
* Per-decision audit hook (BUY-4.4). Invoked once per buyer spend decision — a pay, a
|
|
110
|
+
* skip, an approval-gate, a free cache re-read — with a structured `DecisionAuditEvent`.
|
|
111
|
+
* The cloud host injects a sink that writes to its org-partitioned audit plane; the stdio
|
|
112
|
+
* funnel leaves it unset (no sink ⇒ the OSS path is simply unaudited). A pure hook (the
|
|
113
|
+
* package never imports the cloud), and it is fired best-effort — a throwing sink must
|
|
114
|
+
* never break a paid read.
|
|
115
|
+
*/
|
|
116
|
+
auditSink?: (event: DecisionAuditEvent) => void;
|
|
117
|
+
/**
|
|
118
|
+
* This session's gate (base URL) — the fleet tenant it settles into (else the env
|
|
119
|
+
* `TOLLGATE_URL`). The cloud host injects it per authed session so one process can
|
|
120
|
+
* serve many buyers, each accountable to a specific publisher's 402 (BUY-4.2, the
|
|
121
|
+
* moat: own both ends of the receipt). Server-config, never a tool arg — a
|
|
122
|
+
* prompt-injected model can no more redirect the gate than raise the budget.
|
|
123
|
+
*/
|
|
124
|
+
tollgateUrl?: string;
|
|
125
|
+
/**
|
|
126
|
+
* This session's held-license backend. The stdio funnel leaves it unset (the
|
|
127
|
+
* process-global file `fileHeldStore`). The cloud host MUST inject a per-session
|
|
128
|
+
* store (e.g. `memoryHeldStore()`) so one process serving many buyers can never
|
|
129
|
+
* let session B re-read the license session A paid for — the file store is keyed
|
|
130
|
+
* by slug alone and shared, so without this the hosted path leaks licenses across
|
|
131
|
+
* buyers. Server-config, never a tool arg.
|
|
132
|
+
*/
|
|
133
|
+
heldStore?: HeldStore;
|
|
134
|
+
/**
|
|
135
|
+
* The wallet that signs proof-of-possession for a cnf-bound held re-read. On a
|
|
136
|
+
* custody-free hosted deploy the paying identity is the cloud session EOA, but
|
|
137
|
+
* `getWallet()` (the fallback) returns a throwaway dev key when no
|
|
138
|
+
* `BUYER_PRIVATE_KEY` is set — so a PoP signed by it can never satisfy a license
|
|
139
|
+
* cnf-bound to the session address. The cloud injects a signer backed by its
|
|
140
|
+
* `/sign-pop` BFF (the session key). Absent ⇒ `getWallet()` (unchanged OSS path).
|
|
141
|
+
* Server-config, never a tool arg — the model cannot point the signer elsewhere.
|
|
142
|
+
*/
|
|
143
|
+
popWallet?: AgentWallet;
|
|
144
|
+
/**
|
|
145
|
+
* Where the agent (or its operator) tops up / renews the funding session — surfaced on a
|
|
146
|
+
* `needs_topup` / `grant_expired` pay refusal so the refusal is ACTIONABLE, not a dead end. On
|
|
147
|
+
* the hosted path the cloud injects its portal wallet URL; the stdio funnel leaves it at the
|
|
148
|
+
* `/buyer/wallet` default. Server-config, never a tool arg. */
|
|
149
|
+
buyerWalletUrl?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build a fresh, unconnected MCP server with the tool surface registered. The
|
|
153
|
+
* caller connects it to a transport (stdio for the OSS funnel; the cloud wraps it
|
|
154
|
+
* over an authenticated HTTP transport). A factory — not a singleton — so tests
|
|
155
|
+
* can stand up an isolated server per case. `opts` supplies per-session config for
|
|
156
|
+
* the hosted path (BUY-4); absent, every value falls back to process env.
|
|
157
|
+
*/
|
|
158
|
+
export declare function buildServer(opts?: BuildServerOptions): McpServer;
|
|
159
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA2BpE,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAgB,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIrI,eAAO,MAAM,WAAW,wBAAwB,CAAC;AACjD;iDACiD;AACjD,eAAO,MAAM,cAAc,UAAU,CAAC;AAgGtC;;;;;;;;GAQG;AACH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IACjC,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,gIAAgI;IAChI,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7C,oGAAoG;IACpG,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,aAAa,CAAC;IACpC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAChD;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB;;;;mEAI+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,SAAS,CAuyBpE"}
|