@hpp-io/x402-mcp-bridge 0.0.1 → 0.0.2
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 +54 -4
- package/bin/hpp-x402-channel.js +5 -0
- package/bin/hpp-x402-keychain.js +5 -0
- package/bin/hpp-x402-policy.js +5 -0
- package/bin/x402-mcp-bridge.js +0 -0
- package/dist/a2a.d.ts +70 -0
- package/dist/a2a.js +210 -0
- package/dist/a2a.js.map +1 -0
- package/dist/autoSettlePickup.d.ts +69 -0
- package/dist/autoSettlePickup.js +150 -0
- package/dist/autoSettlePickup.js.map +1 -0
- package/dist/cli/channel.d.ts +1 -0
- package/dist/cli/channel.js +330 -0
- package/dist/cli/channel.js.map +1 -0
- package/dist/cli/install-claude.d.ts +2 -20
- package/dist/cli/install-claude.js +12 -63
- package/dist/cli/install-claude.js.map +1 -1
- package/dist/cli/install-mcp-host.d.ts +35 -0
- package/dist/cli/install-mcp-host.js +82 -0
- package/dist/cli/install-mcp-host.js.map +1 -0
- package/dist/cli/install-openclaw.d.ts +7 -0
- package/dist/cli/install-openclaw.js +28 -0
- package/dist/cli/install-openclaw.js.map +1 -0
- package/dist/cli/keychain.d.ts +1 -0
- package/dist/cli/keychain.js +147 -0
- package/dist/cli/keychain.js.map +1 -0
- package/dist/cli/policy.d.ts +1 -0
- package/dist/cli/policy.js +219 -0
- package/dist/cli/policy.js.map +1 -0
- package/dist/cli/safe.d.ts +4111 -9519
- package/dist/cli/setup.js +23 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/client.d.ts +19 -8
- package/dist/client.js +118 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.js +11 -5
- package/dist/config.js.map +1 -1
- package/dist/httpX402.d.ts +39 -0
- package/dist/httpX402.js +208 -0
- package/dist/httpX402.js.map +1 -0
- package/dist/index.js +43 -14
- package/dist/index.js.map +1 -1
- package/dist/keychain.d.ts +23 -0
- package/dist/keychain.js +111 -0
- package/dist/keychain.js.map +1 -0
- package/dist/policy.d.ts +73 -0
- package/dist/policy.js +249 -0
- package/dist/policy.js.map +1 -0
- package/dist/server.d.ts +15 -1
- package/dist/server.js +154 -10
- package/dist/server.js.map +1 -1
- package/docs/policy.md +101 -0
- package/package.json +12 -5
package/dist/server.js
CHANGED
|
@@ -14,25 +14,126 @@
|
|
|
14
14
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
15
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
16
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
import { parseComputeResultText, startAutoSettlePoller, } from "./autoSettlePickup.js";
|
|
18
|
+
import { PAY_A2A_TOOL, payA2aAgent } from "./a2a.js";
|
|
19
|
+
import { X402_HTTP_TOOL, x402HttpCall } from "./httpX402.js";
|
|
17
20
|
import { log } from "./log.js";
|
|
18
21
|
export async function startBridgeServer(opts) {
|
|
19
22
|
const server = new Server({ name: opts.name, version: opts.version }, { capabilities: { tools: {} } });
|
|
20
|
-
// ---- listTools —
|
|
23
|
+
// ---- listTools — upstream tools (if any) + local tools --------------
|
|
21
24
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
22
|
-
const upstreamTools = await opts.upstream.base.listTools();
|
|
23
|
-
log.debug("listTools", { count: upstreamTools.tools.length });
|
|
24
|
-
return { tools: upstreamTools.tools };
|
|
25
|
+
const upstreamTools = opts.upstream ? await opts.upstream.base.listTools() : { tools: [] };
|
|
26
|
+
log.debug("listTools", { count: upstreamTools.tools.length + 2 });
|
|
27
|
+
return { tools: [...upstreamTools.tools, PAY_A2A_TOOL, X402_HTTP_TOOL] };
|
|
25
28
|
});
|
|
26
29
|
// ---- callTool — forward through x402-aware wrapper ------------------
|
|
27
|
-
|
|
30
|
+
//
|
|
31
|
+
// Long-running tools: the upstream resource-server emits
|
|
32
|
+
// `notifications/progress` every ~25s while it inline-waits for an
|
|
33
|
+
// on-chain X402Delivered event. We forward each one to the host using
|
|
34
|
+
// the host's own progressToken (from extra._meta) so its idle timer
|
|
35
|
+
// resets and the request doesn't get cancelled at 60s. Without this,
|
|
36
|
+
// any compute taking longer than ~55s gets killed at the host's MCP
|
|
37
|
+
// client timeout, regardless of what the server eventually returns.
|
|
38
|
+
//
|
|
39
|
+
// `resetTimeoutOnProgress: true` on the upstream call applies the same
|
|
40
|
+
// protection on the bridge → resource-server hop. `timeout: 300_000`
|
|
41
|
+
// gives the upstream up to 5 minutes (well past anything we'd run
|
|
42
|
+
// inline) before bridge gives up.
|
|
43
|
+
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
|
|
28
44
|
const { name, arguments: args } = req.params;
|
|
29
45
|
log.info("callTool.start", { name });
|
|
46
|
+
// Local tool: pay an external A2A agent (not an upstream MCP tool).
|
|
47
|
+
if (name === PAY_A2A_TOOL.name) {
|
|
48
|
+
try {
|
|
49
|
+
return await payA2aAgent({
|
|
50
|
+
signer: opts.signer,
|
|
51
|
+
network: opts.network,
|
|
52
|
+
autoTopup: opts.autoTopup,
|
|
53
|
+
rpcTimeoutMs: opts.a2aRpcTimeoutMs,
|
|
54
|
+
}, (args ?? {}));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
+
log.error("pay_a2a_agent.failed", { err: msg });
|
|
59
|
+
return { content: [{ type: "text", text: `pay_a2a_agent error: ${msg}` }], isError: true };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Local tool: call an x402-protected HTTP endpoint (not an upstream MCP
|
|
63
|
+
// tool). Auth headers + spend limits come from local policy; payment is
|
|
64
|
+
// signed with the bridge's delegate wallet.
|
|
65
|
+
if (name === X402_HTTP_TOOL.name) {
|
|
66
|
+
try {
|
|
67
|
+
return await x402HttpCall({
|
|
68
|
+
signer: opts.signer,
|
|
69
|
+
network: opts.network,
|
|
70
|
+
autoTopup: opts.autoTopup,
|
|
71
|
+
}, (args ?? {}));
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
log.error("x402_http_call.failed", { err: msg });
|
|
76
|
+
return { content: [{ type: "text", text: `x402_http_call error: ${msg}` }], isError: true };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Beyond the local tools above, everything is an upstream resource-server
|
|
80
|
+
// tool. In local-tools-only mode there's no upstream to forward to.
|
|
81
|
+
if (!opts.upstream) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: `unknown tool: ${name} (no upstream resource server configured)` }],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const up = opts.upstream;
|
|
88
|
+
const hostProgressToken = extra._meta?.progressToken;
|
|
30
89
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
90
|
+
// x402MCPClient's typed callTool options omit `onprogress`, but at
|
|
91
|
+
// runtime the underlying mcpClient.callTool receives the options
|
|
92
|
+
// object unchanged — so onprogress IS honoured by the SDK. Cast
|
|
93
|
+
// around the type gap; remove once @x402/mcp upstream broadens its
|
|
94
|
+
// option type.
|
|
95
|
+
const upstreamOptions = {
|
|
96
|
+
timeout: 300_000,
|
|
97
|
+
resetTimeoutOnProgress: true,
|
|
98
|
+
onprogress: hostProgressToken !== undefined
|
|
99
|
+
? (p) => {
|
|
100
|
+
extra
|
|
101
|
+
.sendNotification({
|
|
102
|
+
method: "notifications/progress",
|
|
103
|
+
params: {
|
|
104
|
+
progressToken: hostProgressToken,
|
|
105
|
+
progress: p.progress,
|
|
106
|
+
total: p.total,
|
|
107
|
+
message: p.message,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
.catch((err) => {
|
|
111
|
+
log.debug("progress.forwardFailed", {
|
|
112
|
+
err: err.message,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
: undefined,
|
|
117
|
+
};
|
|
118
|
+
const result = await up.x402.callTool(name, (args ?? {}), upstreamOptions);
|
|
119
|
+
const paymentMade = result.paymentMade ?? false;
|
|
120
|
+
log.info("callTool.done", { name, paymentMade });
|
|
121
|
+
// F-3a auto-refund hook. If the server's inline wait returned
|
|
122
|
+
// status:pending for a paid compute, start a background poller
|
|
123
|
+
// that watches get_compute_result(jobId). On terminal `failed`
|
|
124
|
+
// we fire batchScheme.refund() so the buyer recovers funds
|
|
125
|
+
// without manual intervention.
|
|
126
|
+
// Skip silently when:
|
|
127
|
+
// - this wasn't a paid compute tool (name doesn't start with compute_)
|
|
128
|
+
// - payment didn't actually happen this turn (already in channel cache)
|
|
129
|
+
// - response isn't the pending shape (server returned an actual
|
|
130
|
+
// result inline, or some non-JSON text like "hello world ...")
|
|
131
|
+
// - we don't have a channelId to act on
|
|
132
|
+
// - the payment scheme wasn't batch-settlement (exact settles on
|
|
133
|
+
// the first response so the buyer already has its PAYMENT-RESPONSE)
|
|
134
|
+
if (paymentMade && name.startsWith("compute_")) {
|
|
135
|
+
maybeStartAutoSettlePoller(up, result, name);
|
|
136
|
+
}
|
|
36
137
|
// Strip bridge-internal fields (paymentMade, paymentResponse) before
|
|
37
138
|
// returning to host — they're not part of the MCP CallToolResult shape.
|
|
38
139
|
return {
|
|
@@ -53,4 +154,47 @@ export async function startBridgeServer(opts) {
|
|
|
53
154
|
await server.connect(transport);
|
|
54
155
|
log.info("bridge.ready");
|
|
55
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Inspect a freshly-returned paid compute result. If the server
|
|
159
|
+
* reported `status: "pending"` (its inline-wait timed out) AND the
|
|
160
|
+
* payment used batch-settlement, kick off the background poller so
|
|
161
|
+
* the eventual result-poll picks up the PAYMENT-RESPONSE and updates
|
|
162
|
+
* the buyer's local cumulative. All failure paths are silent
|
|
163
|
+
* best-effort, never throwing into the caller.
|
|
164
|
+
*
|
|
165
|
+
* Deferred-settle replaces F-3a: the server never settles the pending
|
|
166
|
+
* voucher, so failed compute leaves the buyer's budget intact and
|
|
167
|
+
* there's nothing to refund. The poller now only applies settle when
|
|
168
|
+
* the compute succeeds and the server attaches PAYMENT-RESPONSE.
|
|
169
|
+
*/
|
|
170
|
+
function maybeStartAutoSettlePoller(upstream, result, toolName) {
|
|
171
|
+
const content = result.content;
|
|
172
|
+
const text = content?.[0]?.type === "text" ? content[0]?.text : undefined;
|
|
173
|
+
if (!text)
|
|
174
|
+
return;
|
|
175
|
+
const parsed = parseComputeResultText(text);
|
|
176
|
+
if (parsed?.status !== "pending" || !parsed.jobId)
|
|
177
|
+
return;
|
|
178
|
+
const payResp = result.paymentResponse;
|
|
179
|
+
const schemeName = payResp?.scheme;
|
|
180
|
+
const channelId = payResp?.extra?.channelState?.channelId;
|
|
181
|
+
if (schemeName !== "batch-settlement" || !channelId) {
|
|
182
|
+
log.debug("autoSettle.skipped", {
|
|
183
|
+
jobId: parsed.jobId,
|
|
184
|
+
reason: schemeName !== "batch-settlement"
|
|
185
|
+
? "scheme_not_batch_settlement"
|
|
186
|
+
: "no_channel_id",
|
|
187
|
+
scheme: schemeName,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
startAutoSettlePoller({
|
|
192
|
+
base: upstream.base,
|
|
193
|
+
batchScheme: upstream.batchScheme,
|
|
194
|
+
}, {
|
|
195
|
+
jobId: parsed.jobId,
|
|
196
|
+
channelId,
|
|
197
|
+
toolName,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
56
200
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/docs/policy.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Host policy (`policy.json`) — 레퍼런스
|
|
2
|
+
|
|
3
|
+
`x402_http_call` 도구가 읽는 **host별 인증 헤더 주입 + 지출 가드레일** 설정. 서비스마다 다른 부분을
|
|
4
|
+
코드가 아닌 데이터로 두어, 새 서비스 추가가 한 줄이고 어떤 호스트도 하드코딩되지 않습니다.
|
|
5
|
+
|
|
6
|
+
## 위치
|
|
7
|
+
|
|
8
|
+
- 기본: `~/.hpp-x402/policy.json` (디렉토리는 `$HPP_X402_HOME`로 변경 가능)
|
|
9
|
+
- override: `$HPP_X402_CRED_FILE` (파일 경로 직접 지정)
|
|
10
|
+
- bridge가 **매 도구 호출마다 읽음** → 수정 후 재시작 불필요
|
|
11
|
+
- 정확 경로 확인: `hpp-x402-policy path`
|
|
12
|
+
|
|
13
|
+
## 스키마
|
|
14
|
+
|
|
15
|
+
```jsonc
|
|
16
|
+
{
|
|
17
|
+
"_defaults": {
|
|
18
|
+
"allowUnlisted": false, // 미등록 host 허용? (기본 false = 거부 = allowlist)
|
|
19
|
+
"limits": { "requireHttps": true, "maxPerCallAtomic": "1000000" }
|
|
20
|
+
},
|
|
21
|
+
"<host>": { // 정확 일치(포트 포함, 소문자). wildcard 없음
|
|
22
|
+
"headers": { "<HeaderName>": "<value-source>" },
|
|
23
|
+
"limits": {
|
|
24
|
+
"requireHttps": true, // https 강제 (로컬 http면 false)
|
|
25
|
+
"maxPerCallAtomic": "5000000", // 1회 상한 (atomic, USDC.e 6dec → 5000000 = 5)
|
|
26
|
+
"cooldownMs": 300000, // 결제 간 최소 간격(ms) — 멱등(H2)
|
|
27
|
+
"maxPerDayAtomic": "20000000" // (파싱만, 일일 ledger는 후속)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`host`는 `new URL(url).host`(소문자). 정책에 없고 `allowUnlisted=false`면 **결제 전 거부**.
|
|
34
|
+
|
|
35
|
+
## value-source (헤더 값 출처)
|
|
36
|
+
|
|
37
|
+
비밀을 파일에 직접 안 쓰고 참조로 둡니다. 호출 시 **로컬에서만** 해석 → 헤더로 주입(LLM/로그 미노출).
|
|
38
|
+
해석 실패 시 **무인증 전송 없이 에러로 중단**.
|
|
39
|
+
|
|
40
|
+
| 형식 | 의미 |
|
|
41
|
+
|------|------|
|
|
42
|
+
| `file:<path>#<field>` | JSON 파일 필드 (`~` 확장, 중첩 `a.b.c`) |
|
|
43
|
+
| `env:<VAR>` | 환경변수 |
|
|
44
|
+
| `keychain://<svc>/<acct>` | OS 키체인 |
|
|
45
|
+
| `literal:<value>` | 인라인 평문 (**dev 전용**) |
|
|
46
|
+
|
|
47
|
+
## 가드레일
|
|
48
|
+
|
|
49
|
+
| 키 | 효과 |
|
|
50
|
+
|----|------|
|
|
51
|
+
| `allowUnlisted` (_defaults) | false면 등록된 host만 호출 가능 (deny-by-default) |
|
|
52
|
+
| `requireHttps` | https 아닌 대상 거부 (로컬 http는 host별 false) |
|
|
53
|
+
| `maxPerCallAtomic` | 1회 결제 상한 — **서버가 준 402 금액**에 적용(악성 서버 방어) |
|
|
54
|
+
| `cooldownMs` | 직전 결제 후 이 시간 내 재결제는 직전 결과 반환(재결제 X) |
|
|
55
|
+
|
|
56
|
+
> 온체인 Safe AllowanceModule 일일 한도가 **최종 하드 상한**(정책 파일로 못 넘음).
|
|
57
|
+
|
|
58
|
+
## CLI — `hpp-x402-policy`
|
|
59
|
+
|
|
60
|
+
수동 JSON 편집 대신 사용 가능:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
hpp-x402-policy path # 파일 경로
|
|
64
|
+
hpp-x402-policy show # 전체 JSON
|
|
65
|
+
hpp-x402-policy list # host 요약
|
|
66
|
+
|
|
67
|
+
# host 추가/수정 (--header 반복 가능, --max-per-call은 USDC 단위 → atomic 자동 변환)
|
|
68
|
+
hpp-x402-policy set hub-stage.hpp.io \
|
|
69
|
+
--header X-Api-Key=file:~/.hpphub/config.json#api_key \
|
|
70
|
+
--max-per-call 5 --cooldown-ms 300000
|
|
71
|
+
|
|
72
|
+
hpp-x402-policy set localhost:4000 \
|
|
73
|
+
--header X-Api-Key=file:~/.hpphub/config.json#api_key \
|
|
74
|
+
--max-per-call 5 --https false # 로컬 http 허용
|
|
75
|
+
|
|
76
|
+
hpp-x402-policy unset <host> # host 삭제
|
|
77
|
+
hpp-x402-policy unset <host> --header NAME # 헤더 1개 삭제
|
|
78
|
+
|
|
79
|
+
hpp-x402-policy defaults --allow-unlisted false --max-per-call 1 --https true
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 예시 (완성형)
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
{
|
|
86
|
+
"_defaults": { "allowUnlisted": false, "limits": { "requireHttps": true, "maxPerCallAtomic": "1000000" } },
|
|
87
|
+
"hub-stage.hpp.io": {
|
|
88
|
+
"headers": { "X-Api-Key": "file:~/.hpphub/config.json#api_key" },
|
|
89
|
+
"limits": { "requireHttps": true, "maxPerCallAtomic": "5000000", "cooldownMs": 300000 }
|
|
90
|
+
},
|
|
91
|
+
"localhost:4000": {
|
|
92
|
+
"headers": { "X-Api-Key": "file:~/.hpphub/config.json#api_key" },
|
|
93
|
+
"limits": { "requireHttps": false, "maxPerCallAtomic": "5000000", "cooldownMs": 300000 }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 보안 메모
|
|
99
|
+
|
|
100
|
+
- 파일 퍼미션 0600 권장 (CLI는 0600으로 기록). `literal:` 평문은 dev 전용 — 운영은 `file:`/`keychain://`.
|
|
101
|
+
- value-source는 로컬에서만 해석, 결과는 **대상 host 요청 헤더로만** 전송. 로그/도구 args에 노출 안 됨.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hpp-io/x402-mcp-bridge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "MCP stdio bridge that lets Claude Desktop / OpenClaw / other MCP hosts make x402 payments on HPP — autoTopup from a Safe via AllowanceModule, signs EIP-3009 with a delegate EOA.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
"bin": {
|
|
10
10
|
"x402-mcp-bridge": "bin/x402-mcp-bridge.js",
|
|
11
11
|
"hpp-x402-safe-setup": "bin/hpp-x402-safe-setup.js",
|
|
12
|
-
"hpp-x402-safe-revoke": "bin/hpp-x402-safe-revoke.js"
|
|
12
|
+
"hpp-x402-safe-revoke": "bin/hpp-x402-safe-revoke.js",
|
|
13
|
+
"hpp-x402-keychain": "bin/hpp-x402-keychain.js",
|
|
14
|
+
"hpp-x402-channel": "bin/hpp-x402-channel.js",
|
|
15
|
+
"hpp-x402-policy": "bin/hpp-x402-policy.js"
|
|
13
16
|
},
|
|
14
17
|
"files": [
|
|
15
18
|
"dist",
|
|
16
19
|
"bin",
|
|
20
|
+
"docs/policy.md",
|
|
17
21
|
"README.md",
|
|
18
22
|
"LICENSE"
|
|
19
23
|
],
|
|
@@ -24,6 +28,7 @@
|
|
|
24
28
|
"build": "tsc",
|
|
25
29
|
"dev": "tsx src/index.ts",
|
|
26
30
|
"typecheck": "tsc --noEmit",
|
|
31
|
+
"postinstall": "patch-package || true",
|
|
27
32
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
28
33
|
},
|
|
29
34
|
"publishConfig": {
|
|
@@ -32,14 +37,16 @@
|
|
|
32
37
|
},
|
|
33
38
|
"dependencies": {
|
|
34
39
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
35
|
-
"@
|
|
36
|
-
"@x402/
|
|
37
|
-
"@x402/
|
|
40
|
+
"@napi-rs/keyring": "^1.3.0",
|
|
41
|
+
"@x402/core": "^2.13.0",
|
|
42
|
+
"@x402/evm": "^2.13.0",
|
|
43
|
+
"@x402/mcp": "^2.13.0",
|
|
38
44
|
"viem": "^2.21.54",
|
|
39
45
|
"zod": "^3.23.8"
|
|
40
46
|
},
|
|
41
47
|
"devDependencies": {
|
|
42
48
|
"@types/node": "^20.0.0",
|
|
49
|
+
"patch-package": "^8.0.1",
|
|
43
50
|
"tsx": "^4.0.0",
|
|
44
51
|
"typescript": "^5.4.0"
|
|
45
52
|
}
|