twzrd-mcp-server 0.2.4 → 0.2.6
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 +69 -21
- package/dist/index.js +78 -9
- package/examples/agent-drop-in.mjs +219 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# twzrd-mcp-server - auto-pay MCP for the TWZRD Trust API
|
|
2
2
|
|
|
3
|
+
<!-- mcp-name: xyz.twzrd/twzrd-mcp -->
|
|
4
|
+
|
|
3
5
|
> Payment mechanism is mainnet-verified via the official x402 SDK (Python path,
|
|
4
|
-
> $0.001 moved 2026-06-26 - see Status).
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
> settle remains before npm publish.
|
|
6
|
+
> $0.001 moved 2026-06-26 - see Status). The Node package is published on npm as
|
|
7
|
+
> `twzrd-mcp-server`; v0.2.5 source adds the single-shot payment guard required
|
|
8
|
+
> before the next live paid proof.
|
|
8
9
|
|
|
9
10
|
Auto-pay MCP server for TWZRD's Trust API, matching the competitor GTM shape
|
|
10
11
|
(anchor-x402, Br0ski777, BitBooth all ship one). An agent adds one `mcpServers`
|
|
@@ -22,7 +23,8 @@ non-Solana challenge instead of mis-signing.
|
|
|
22
23
|
- Cumulative session cap `TWZRD_MAX_USDC_TOTAL` (default 1.00)
|
|
23
24
|
- Free discovery tools never enter the payment path
|
|
24
25
|
- No cross-chain fallback — a non-`exact`/non-`solana:` challenge is rejected
|
|
25
|
-
- Paid calls
|
|
26
|
+
- Paid trust calls buy intel on the target wallet by design; use the free
|
|
27
|
+
`preflight` tool separately to vet a seller before paying it elsewhere.
|
|
26
28
|
|
|
27
29
|
## Status — payment path VERIFIED on mainnet 2026-06-26
|
|
28
30
|
|
|
@@ -38,8 +40,8 @@ Two authorized settles from dev wallet `2pHjZLqs…`:
|
|
|
38
40
|
a no-data pubkey returned `422 charged:false` — the server's no-charge-on-empty
|
|
39
41
|
guard works.
|
|
40
42
|
|
|
41
|
-
**Conclusion: auto-pay works
|
|
42
|
-
|
|
43
|
+
**Conclusion: auto-pay works through the official x402 SDK path.** Proven client
|
|
44
|
+
wiring (Python):
|
|
43
45
|
|
|
44
46
|
```python
|
|
45
47
|
from x402.client import x402ClientSync
|
|
@@ -52,16 +54,22 @@ session = x402_requests(client)
|
|
|
52
54
|
session.get("https://intel.twzrd.xyz/v1/intel/quick/<wallet>") # auto-pays $0.001
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
### TypeScript path — integrated (v0.2.
|
|
56
|
-
The
|
|
57
|
-
|
|
58
|
-
`
|
|
59
|
-
the
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
### TypeScript path — integrated (v0.2.5)
|
|
58
|
+
The TypeScript path uses the official x402 JS SDK (`@x402/core` client +
|
|
59
|
+
`@x402/svm` ExactSvmScheme). `@x402/svm` reads the challenge
|
|
60
|
+
`extra.feePayer` and builds the partially-signed sponsored transfer, and
|
|
61
|
+
`x402HTTPClient` encodes the `X-PAYMENT` header the server validates. Spend caps +
|
|
62
|
+
free/paid split are preserved — caps are enforced in the payment selector before
|
|
63
|
+
any signature.
|
|
64
|
+
|
|
65
|
+
Important: v0.2.5 uses a **single-shot** paid retry. The first SDK-backed E2E found
|
|
66
|
+
that the generic `@x402/fetch` wrapper can re-pay after a transient-looking settle
|
|
67
|
+
response, moving `$0.003` across two `$0.001` calls. This package now performs at
|
|
68
|
+
most one signed retry per logical tool call; any second 402 is surfaced instead of
|
|
69
|
+
silently paying again.
|
|
70
|
+
|
|
71
|
+
**Next verification step:** one operator-authorized `$0.001` `quick_trust` settle
|
|
72
|
+
through v0.2.5, followed by offline receipt verification.
|
|
65
73
|
|
|
66
74
|
## Install & Config
|
|
67
75
|
|
|
@@ -86,7 +94,11 @@ The **free** tools (`preflight`, `wallet_lookup`) need no wallet and no flags
|
|
|
86
94
|
`TWZRD_MCP_PAYMENTS_ENABLED` unset and they work read-only. Only the paid tools need
|
|
87
95
|
the keypair + `TWZRD_MCP_PAYMENTS_ENABLED=1`.
|
|
88
96
|
|
|
89
|
-
### Node (`npx twzrd-mcp-server`) —
|
|
97
|
+
### Node (`npx twzrd-mcp-server`) — x402 JS SDK
|
|
98
|
+
```bash
|
|
99
|
+
npx -y twzrd-mcp-server --help
|
|
100
|
+
```
|
|
101
|
+
|
|
90
102
|
```json
|
|
91
103
|
{ "mcpServers": { "twzrd": {
|
|
92
104
|
"command": "npx", "args": ["-y", "twzrd-mcp-server"],
|
|
@@ -100,9 +112,45 @@ the keypair + `TWZRD_MCP_PAYMENTS_ENABLED=1`.
|
|
|
100
112
|
```
|
|
101
113
|
Auto-pay is enabled whenever `TWZRD_WALLET_SECRET_KEY` is present (set
|
|
102
114
|
`TWZRD_MCP_PAYMENTS_ENABLED=0` to force paid tools off). Free tools need no wallet.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
The package is published on npm; v0.2.5 is the next source release that includes
|
|
116
|
+
the single-shot double-settle guard.
|
|
117
|
+
|
|
118
|
+
## One-command Agent Demo
|
|
119
|
+
|
|
120
|
+
The packaged demo starts the MCP server over stdio, lists the tools, and runs a
|
|
121
|
+
live **free** preflight. It is no-spend by default, even if your shell has a
|
|
122
|
+
wallet secret:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm run build
|
|
126
|
+
npm run demo
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Turn the same demo into the operator-authorized `$0.001` settle proof by changing
|
|
130
|
+
one env var and setting tight caps:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
TWZRD_DEMO_PAID=quick \
|
|
134
|
+
TWZRD_WALLET_SECRET_KEY="<base58 Solana secret>" \
|
|
135
|
+
TWZRD_RPC_URL="<mainnet RPC>" \
|
|
136
|
+
TWZRD_MAX_USDC_PER_CALL=0.001 \
|
|
137
|
+
TWZRD_MAX_USDC_TOTAL=0.001 \
|
|
138
|
+
node examples/agent-drop-in.mjs
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
For the full signed-receipt path, use `TWZRD_DEMO_PAID=full` and set both caps to
|
|
142
|
+
`0.05`; the demo verifies any returned receipt through the MCP `verify_receipt`
|
|
143
|
+
tool. To additionally pipe that receipt into the standalone verifier package:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
TWZRD_DEMO_PAID=full \
|
|
147
|
+
TWZRD_DEMO_RUN_VERIFIER_SELF_TEST=1 \
|
|
148
|
+
TWZRD_WALLET_SECRET_KEY="<base58 Solana secret>" \
|
|
149
|
+
TWZRD_RPC_URL="<mainnet RPC>" \
|
|
150
|
+
TWZRD_MAX_USDC_PER_CALL=0.05 \
|
|
151
|
+
TWZRD_MAX_USDC_TOTAL=0.05 \
|
|
152
|
+
node examples/agent-drop-in.mjs
|
|
153
|
+
```
|
|
106
154
|
|
|
107
155
|
## Tools
|
|
108
156
|
- `preflight` (free) — allow/warn/block + trust_score before you pay a **seller** you're about to transact with
|
package/dist/index.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* wallet (the challenge `extra.feePayer`) co-signs + pays the SOL fee server-side.
|
|
13
13
|
* `@x402/svm`'s ExactSvmScheme reads `extra.feePayer` and builds exactly this
|
|
14
14
|
* partially-signed transaction; the SDK also encodes the X-PAYMENT header the
|
|
15
|
-
* server validates.
|
|
16
|
-
*
|
|
15
|
+
* server validates. This keeps the client payload byte-compatible with the
|
|
16
|
+
* server-side x402 validator instead of inventing a parallel header format.
|
|
17
17
|
*
|
|
18
18
|
* SAFETY GUARDRAILS (all enforced in the payment-requirements selector, BEFORE
|
|
19
19
|
* any signature):
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
* - Free discovery tools are NEVER routed through the payment path
|
|
23
23
|
* - No cross-chain fallback: only a Solana "exact" requirement is selectable;
|
|
24
24
|
* anything else throws (refuse to pay) rather than mis-signing
|
|
25
|
-
* - Paid tools
|
|
25
|
+
* - Paid tools buy intel on the target wallet by design; use the free
|
|
26
|
+
* `preflight` tool separately to vet a seller before paying it elsewhere.
|
|
26
27
|
*
|
|
27
28
|
* STATUS: payment mechanism is SDK-backed and the payload construction is
|
|
28
29
|
* verified against the live Solana "exact" challenge. The first real on-chain
|
|
@@ -40,11 +41,53 @@
|
|
|
40
41
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
41
42
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
42
43
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
43
|
-
import { x402Client } from "@x402/core/client";
|
|
44
|
-
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
44
|
+
import { x402Client, x402HTTPClient } from "@x402/core/client";
|
|
45
45
|
import { ExactSvmScheme, SOLANA_MAINNET_CAIP2 } from "@x402/svm";
|
|
46
46
|
import { createKeyPairSignerFromBytes } from "@solana/kit";
|
|
47
47
|
import bs58 from "bs58";
|
|
48
|
+
const VERSION = "0.2.5";
|
|
49
|
+
function printHelp() {
|
|
50
|
+
console.log(`twzrd-mcp-server v${VERSION}
|
|
51
|
+
|
|
52
|
+
TWZRD Trust API MCP server with Solana x402 auto-pay.
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
twzrd-mcp-server Start stdio MCP server
|
|
56
|
+
twzrd-mcp-server --help Show this help
|
|
57
|
+
twzrd-mcp-server --version Print version
|
|
58
|
+
|
|
59
|
+
Free tools work with no wallet:
|
|
60
|
+
preflight, wallet_lookup, verify_receipt
|
|
61
|
+
|
|
62
|
+
Paid tools require:
|
|
63
|
+
TWZRD_WALLET_SECRET_KEY=<base58 Solana secret key>
|
|
64
|
+
TWZRD_RPC_URL=<mainnet Solana RPC>
|
|
65
|
+
TWZRD_MAX_USDC_PER_CALL=0.05
|
|
66
|
+
TWZRD_MAX_USDC_TOTAL=1.00
|
|
67
|
+
|
|
68
|
+
MCP config:
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"twzrd": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "twzrd-mcp-server"],
|
|
74
|
+
"env": {
|
|
75
|
+
"TWZRD_RPC_URL": "<mainnet rpc>",
|
|
76
|
+
"TWZRD_WALLET_SECRET_KEY": "<base58 secret>"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`);
|
|
82
|
+
}
|
|
83
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
84
|
+
printHelp();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
88
|
+
console.log(VERSION);
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
48
91
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
49
92
|
const API_BASE = process.env.TWZRD_API_URL || "https://intel.twzrd.xyz";
|
|
50
93
|
const RPC_URL = process.env.TWZRD_RPC_URL || "https://api.mainnet-beta.solana.com";
|
|
@@ -75,8 +118,11 @@ function selectSolanaExact(_x402Version, accepts) {
|
|
|
75
118
|
spentUsdc += amountUsdc;
|
|
76
119
|
return req;
|
|
77
120
|
}
|
|
78
|
-
// Build
|
|
79
|
-
// force-disabled.
|
|
121
|
+
// Build a SINGLE-SHOT payment fetch once, if a key is present and payments
|
|
122
|
+
// aren't force-disabled. We intentionally do not use @x402/fetch's
|
|
123
|
+
// wrapFetchWithPayment here: its recovery branch can create a fresh payload and
|
|
124
|
+
// re-pay after a transient-looking settle response. This wrapper makes exactly
|
|
125
|
+
// one paid retry per logical tool call; any second 402 is surfaced to the caller.
|
|
80
126
|
let paidFetch = null;
|
|
81
127
|
let paymentInitError = "";
|
|
82
128
|
if (SECRET && !PAYMENTS_DISABLED) {
|
|
@@ -86,7 +132,30 @@ if (SECRET && !PAYMENTS_DISABLED) {
|
|
|
86
132
|
const scheme = new ExactSvmScheme(signer, { rpcUrl: RPC_URL });
|
|
87
133
|
const client = new x402Client(selectSolanaExact);
|
|
88
134
|
client.register(SOLANA_MAINNET_CAIP2, scheme);
|
|
89
|
-
|
|
135
|
+
const httpClient = new x402HTTPClient(client);
|
|
136
|
+
paidFetch = (async (input, init) => {
|
|
137
|
+
const first = await fetch(input, init);
|
|
138
|
+
if (first.status !== 402)
|
|
139
|
+
return first;
|
|
140
|
+
let challengeBody;
|
|
141
|
+
try {
|
|
142
|
+
const text = await first.text();
|
|
143
|
+
if (text)
|
|
144
|
+
challengeBody = JSON.parse(text);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Header-only challenges are valid; the SDK can read those from headers.
|
|
148
|
+
}
|
|
149
|
+
const paymentRequired = httpClient.getPaymentRequiredResponse((name) => first.headers.get(name), challengeBody);
|
|
150
|
+
const payload = await client.createPaymentPayload(paymentRequired);
|
|
151
|
+
const headers = httpClient.encodePaymentSignatureHeader(payload);
|
|
152
|
+
const retry = new Request(input, init);
|
|
153
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
154
|
+
retry.headers.set(key, value);
|
|
155
|
+
}
|
|
156
|
+
retry.headers.set("Access-Control-Expose-Headers", "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE");
|
|
157
|
+
return fetch(retry);
|
|
158
|
+
});
|
|
90
159
|
console.error(`TWZRD MCP: auto-pay armed (payer ${signer.address}) caps $${MAX_PER_CALL}/call $${MAX_TOTAL}/session`);
|
|
91
160
|
}
|
|
92
161
|
catch (e) {
|
|
@@ -122,7 +191,7 @@ const TOOLS = [
|
|
|
122
191
|
{ name: "quick_trust", description: "PAID $0.001 (auto-pay, Solana x402): quick tier+score for a Solana wallet.", inputSchema: { type: "object", properties: { wallet: { type: "string" } }, required: ["wallet"] } },
|
|
123
192
|
{ name: "full_trust", description: "PAID $0.05 (auto-pay, Solana x402): full trust intel + signed V6 receipt.", inputSchema: { type: "object", properties: { wallet: { type: "string" }, seller_wallet: { type: "string" } }, required: ["wallet"] } },
|
|
124
193
|
];
|
|
125
|
-
const server = new Server({ name: "twzrd-mcp-server", version:
|
|
194
|
+
const server = new Server({ name: "twzrd-mcp-server", version: VERSION }, { capabilities: { tools: {} } });
|
|
126
195
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
127
196
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
128
197
|
const { name, arguments: args } = request.params;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TWZRD MCP drop-in demo.
|
|
4
|
+
*
|
|
5
|
+
* Default path is free and read-only:
|
|
6
|
+
* npm run build
|
|
7
|
+
* npm run demo
|
|
8
|
+
*
|
|
9
|
+
* Operator-authorized paid proof (spends exactly one capped x402 call):
|
|
10
|
+
* TWZRD_DEMO_PAID=quick \
|
|
11
|
+
* TWZRD_WALLET_SECRET_KEY=<base58-solana-secret> \
|
|
12
|
+
* TWZRD_RPC_URL=<mainnet-rpc> \
|
|
13
|
+
* TWZRD_MAX_USDC_PER_CALL=0.001 \
|
|
14
|
+
* TWZRD_MAX_USDC_TOTAL=0.001 \
|
|
15
|
+
* node examples/agent-drop-in.mjs
|
|
16
|
+
*
|
|
17
|
+
* Full receipt path (costs 0.05 USDC, verifies receipt if one is returned):
|
|
18
|
+
* TWZRD_DEMO_PAID=full TWZRD_MAX_USDC_PER_CALL=0.05 TWZRD_MAX_USDC_TOTAL=0.05 ...
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const serverPath = process.env.TWZRD_MCP_SERVER_PATH || path.resolve(__dirname, "../dist/index.js");
|
|
27
|
+
const demoWallet =
|
|
28
|
+
process.env.TWZRD_DEMO_WALLET || "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
|
29
|
+
const paidMode = (process.env.TWZRD_DEMO_PAID || "off").toLowerCase();
|
|
30
|
+
|
|
31
|
+
if (!["off", "quick", "full"].includes(paidMode)) {
|
|
32
|
+
throw new Error("TWZRD_DEMO_PAID must be one of: off, quick, full");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const childEnv = { ...process.env };
|
|
36
|
+
if (paidMode === "off" && childEnv.TWZRD_MCP_PAYMENTS_ENABLED === undefined) {
|
|
37
|
+
// Free demo should stay free even if the shell happens to have a wallet secret.
|
|
38
|
+
childEnv.TWZRD_MCP_PAYMENTS_ENABLED = "0";
|
|
39
|
+
}
|
|
40
|
+
if (paidMode !== "off") {
|
|
41
|
+
if (!childEnv.TWZRD_WALLET_SECRET_KEY) {
|
|
42
|
+
throw new Error("Paid demo requires TWZRD_WALLET_SECRET_KEY (base58 Solana secret)");
|
|
43
|
+
}
|
|
44
|
+
childEnv.TWZRD_MAX_USDC_PER_CALL ||= paidMode === "quick" ? "0.001" : "0.05";
|
|
45
|
+
childEnv.TWZRD_MAX_USDC_TOTAL ||= childEnv.TWZRD_MAX_USDC_PER_CALL;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
49
|
+
env: childEnv,
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let nextId = 1;
|
|
54
|
+
let stdoutBuffer = "";
|
|
55
|
+
const pending = new Map();
|
|
56
|
+
|
|
57
|
+
child.stderr.on("data", (chunk) => {
|
|
58
|
+
process.stderr.write(`[twzrd-mcp] ${chunk}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
child.stdout.on("data", (chunk) => {
|
|
62
|
+
stdoutBuffer += chunk.toString("utf8");
|
|
63
|
+
parseFrames();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
child.on("exit", (code, signal) => {
|
|
67
|
+
const err = new Error(`MCP server exited code=${code} signal=${signal}`);
|
|
68
|
+
for (const { reject, timer } of pending.values()) {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
reject(err);
|
|
71
|
+
}
|
|
72
|
+
pending.clear();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function parseFrames() {
|
|
76
|
+
for (;;) {
|
|
77
|
+
const lineEnd = stdoutBuffer.indexOf("\n");
|
|
78
|
+
if (lineEnd < 0) return;
|
|
79
|
+
|
|
80
|
+
const line = stdoutBuffer.slice(0, lineEnd).replace(/\r$/, "");
|
|
81
|
+
stdoutBuffer = stdoutBuffer.slice(lineEnd + 1);
|
|
82
|
+
if (!line.trim()) continue;
|
|
83
|
+
|
|
84
|
+
const message = JSON.parse(line);
|
|
85
|
+
if (message.id !== undefined && pending.has(message.id)) {
|
|
86
|
+
const { resolve, reject, timer } = pending.get(message.id);
|
|
87
|
+
pending.delete(message.id);
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
if (message.error) reject(new Error(JSON.stringify(message.error)));
|
|
90
|
+
else resolve(message.result);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function send(message) {
|
|
96
|
+
const body = JSON.stringify(message);
|
|
97
|
+
child.stdin.write(`${body}\n`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function request(method, params) {
|
|
101
|
+
const id = nextId++;
|
|
102
|
+
send({ jsonrpc: "2.0", id, method, params });
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
pending.delete(id);
|
|
106
|
+
reject(new Error(`Timed out waiting for ${method}`));
|
|
107
|
+
}, 15000);
|
|
108
|
+
pending.set(id, { resolve, reject, timer });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function notify(method, params = {}) {
|
|
113
|
+
send({ jsonrpc: "2.0", method, params });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function textFromTool(result) {
|
|
117
|
+
return result?.content?.find((part) => part.type === "text")?.text || "";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseJsonText(text) {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(text);
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function printSection(title, value) {
|
|
129
|
+
console.log(`\n== ${title} ==`);
|
|
130
|
+
if (typeof value === "string") console.log(value);
|
|
131
|
+
else console.log(JSON.stringify(value, null, 2));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function main() {
|
|
135
|
+
const init = await request("initialize", {
|
|
136
|
+
protocolVersion: "2024-11-05",
|
|
137
|
+
capabilities: {},
|
|
138
|
+
clientInfo: { name: "twzrd-agent-drop-in", version: "0.1.0" },
|
|
139
|
+
});
|
|
140
|
+
notify("notifications/initialized");
|
|
141
|
+
printSection("server", init.serverInfo);
|
|
142
|
+
|
|
143
|
+
const tools = await request("tools/list", {});
|
|
144
|
+
printSection(
|
|
145
|
+
"tools",
|
|
146
|
+
tools.tools.map((tool) => `${tool.name}: ${tool.description}`),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const preflight = await request("tools/call", {
|
|
150
|
+
name: "preflight",
|
|
151
|
+
arguments: {
|
|
152
|
+
seller_wallet: demoWallet,
|
|
153
|
+
resource_name: "agent-drop-in-demo",
|
|
154
|
+
price_usdc: paidMode === "quick" ? 0.001 : 0.05,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
printSection("free preflight", parseJsonText(textFromTool(preflight)) || textFromTool(preflight));
|
|
158
|
+
|
|
159
|
+
if (paidMode === "quick") {
|
|
160
|
+
const quick = await request("tools/call", {
|
|
161
|
+
name: "quick_trust",
|
|
162
|
+
arguments: { wallet: demoWallet },
|
|
163
|
+
});
|
|
164
|
+
printSection("paid quick_trust", parseJsonText(textFromTool(quick)) || textFromTool(quick));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (paidMode === "full") {
|
|
168
|
+
const full = await request("tools/call", {
|
|
169
|
+
name: "full_trust",
|
|
170
|
+
arguments: { wallet: demoWallet },
|
|
171
|
+
});
|
|
172
|
+
const body = parseJsonText(textFromTool(full)) || {};
|
|
173
|
+
printSection("paid full_trust", body || textFromTool(full));
|
|
174
|
+
|
|
175
|
+
const receipt = body.twzrd_receipt || body.receipt;
|
|
176
|
+
if (receipt?.leaf) {
|
|
177
|
+
printSection("receipt", {
|
|
178
|
+
leaf: receipt.leaf,
|
|
179
|
+
signature: receipt.signature,
|
|
180
|
+
signing_pubkey: receipt.signing_pubkey,
|
|
181
|
+
settlement_tx: receipt.preimage?.settlement_tx || body.settlement_tx,
|
|
182
|
+
});
|
|
183
|
+
const verified = await request("tools/call", {
|
|
184
|
+
name: "verify_receipt",
|
|
185
|
+
arguments: {
|
|
186
|
+
leaf: receipt.leaf,
|
|
187
|
+
signature: receipt.signature,
|
|
188
|
+
signing_pubkey: receipt.signing_pubkey,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
printSection("receipt verification", parseJsonText(textFromTool(verified)) || textFromTool(verified));
|
|
192
|
+
|
|
193
|
+
if (process.env.TWZRD_DEMO_RUN_VERIFIER_SELF_TEST === "1") {
|
|
194
|
+
console.log("\n== receipt-verifier self-test ==");
|
|
195
|
+
const verifier = spawnSync(
|
|
196
|
+
"npx",
|
|
197
|
+
["-y", "twzrd-receipt-verifier@1.2.0", "-", "--self-test"],
|
|
198
|
+
{ input: JSON.stringify(receipt), stdio: ["pipe", "inherit", "inherit"] },
|
|
199
|
+
);
|
|
200
|
+
if (verifier.status !== 0) {
|
|
201
|
+
throw new Error(`twzrd-receipt-verifier self-test exited ${verifier.status}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
console.log("\nNo receipt object returned, so receipt verification was skipped.");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(`\nDemo complete. paid_mode=${paidMode}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
main()
|
|
213
|
+
.catch((err) => {
|
|
214
|
+
console.error(`\nDemo failed: ${err.message}`);
|
|
215
|
+
process.exitCode = 1;
|
|
216
|
+
})
|
|
217
|
+
.finally(() => {
|
|
218
|
+
child.kill();
|
|
219
|
+
});
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twzrd-mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "TWZRD Trust API MCP server with Solana x402 auto-pay (official @x402 SDK, spend-capped, preflight-gated).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"twzrd-mcp-server": "
|
|
7
|
+
"twzrd-mcp-server": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"files": [
|
|
11
|
-
"dist",
|
|
11
|
+
"dist/index.js",
|
|
12
|
+
"examples/agent-drop-in.mjs",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"engines": {
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsc",
|
|
35
|
+
"demo": "node examples/agent-drop-in.mjs",
|
|
34
36
|
"typecheck": "tsc --noEmit",
|
|
35
37
|
"prepublishOnly": "npm run build"
|
|
36
38
|
},
|
|
@@ -38,7 +40,6 @@
|
|
|
38
40
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
41
|
"@x402/core": "^2.17.0",
|
|
40
42
|
"@x402/svm": "^2.17.0",
|
|
41
|
-
"@x402/fetch": "^2.17.0",
|
|
42
43
|
"@solana/kit": "^5.1.0",
|
|
43
44
|
"bs58": "^6.0.0"
|
|
44
45
|
},
|