@keeperhub/wallet 0.1.11 → 0.1.12

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.
@@ -0,0 +1,1206 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/mcp-server.ts
21
+ var mcp_server_exports = {};
22
+ __export(mcp_server_exports, {
23
+ __test__: () => __test__,
24
+ buildMcpServer: () => buildMcpServer,
25
+ runMcpServer: () => runMcpServer
26
+ });
27
+ module.exports = __toCommonJS(mcp_server_exports);
28
+ var import_node_fs = require("fs");
29
+ var import_node_path3 = require("path");
30
+ var import_node_url = require("url");
31
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
32
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
33
+ var import_zod = require("zod");
34
+
35
+ // src/balance.ts
36
+ var import_viem2 = require("viem");
37
+
38
+ // src/chains.ts
39
+ var import_viem = require("viem");
40
+ var import_chains = require("viem/chains");
41
+ var tempo = (0, import_viem.defineChain)({
42
+ id: 4217,
43
+ name: "Tempo",
44
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
45
+ rpcUrls: {
46
+ default: {
47
+ http: [process.env.TEMPO_RPC_URL ?? "https://rpc.tempo.xyz"]
48
+ }
49
+ },
50
+ blockExplorers: {
51
+ default: { name: "Tempo Explorer", url: "https://explorer.tempo.xyz" }
52
+ }
53
+ });
54
+ var BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
55
+ var TEMPO_USDC_E = "0x20c000000000000000000000b9537d11c60e8b50";
56
+
57
+ // src/balance.ts
58
+ var USDC_DECIMALS = 6;
59
+ async function checkBalance(wallet, opts = {}) {
60
+ const baseClient = opts.baseClient ?? (0, import_viem2.createPublicClient)({
61
+ chain: import_chains.base,
62
+ transport: (0, import_viem2.http)()
63
+ });
64
+ const tempoClient = opts.tempoClient ?? (0, import_viem2.createPublicClient)({
65
+ chain: tempo,
66
+ transport: (0, import_viem2.http)()
67
+ });
68
+ const [baseRaw, tempoRaw] = await Promise.all([
69
+ baseClient.readContract({
70
+ address: BASE_USDC,
71
+ abi: import_viem2.erc20Abi,
72
+ functionName: "balanceOf",
73
+ args: [wallet.walletAddress]
74
+ }),
75
+ tempoClient.readContract({
76
+ address: TEMPO_USDC_E,
77
+ abi: import_viem2.erc20Abi,
78
+ functionName: "balanceOf",
79
+ args: [wallet.walletAddress]
80
+ })
81
+ ]);
82
+ return {
83
+ base: {
84
+ chain: "base",
85
+ token: "USDC",
86
+ amount: (0, import_viem2.formatUnits)(baseRaw, USDC_DECIMALS),
87
+ address: wallet.walletAddress
88
+ },
89
+ tempo: {
90
+ chain: "tempo",
91
+ token: "USDC.e",
92
+ amount: (0, import_viem2.formatUnits)(tempoRaw, USDC_DECIMALS),
93
+ address: wallet.walletAddress
94
+ }
95
+ };
96
+ }
97
+
98
+ // src/fund.ts
99
+ var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
100
+ var COINBASE_HOST = "pay.coinbase.com";
101
+ var COINBASE_PATH = "/buy/select-asset";
102
+ function fund(walletAddress) {
103
+ if (!EVM_ADDRESS_RE.test(walletAddress)) {
104
+ throw new Error(`Invalid EVM wallet address: ${walletAddress}`);
105
+ }
106
+ const params = new URLSearchParams({
107
+ defaultNetwork: "base",
108
+ defaultAsset: "USDC",
109
+ addresses: JSON.stringify({ [walletAddress]: ["base"] }),
110
+ presetCryptoAmount: "5"
111
+ });
112
+ const coinbaseOnrampUrl = `https://${COINBASE_HOST}${COINBASE_PATH}?${params.toString()}`;
113
+ const disclaimer = "If the Coinbase page does not pre-fill, paste your address manually. For Tempo USDC.e, transfer from an exchange or another wallet to the address above -- Onramp does not support Tempo directly. Coinbase sessionToken URLs are the 2025+ canonical form; legacy query-param URLs may drop prefill on some accounts.";
114
+ return {
115
+ coinbaseOnrampUrl,
116
+ tempoAddress: walletAddress,
117
+ disclaimer
118
+ };
119
+ }
120
+
121
+ // src/mpp-detect.ts
122
+ var MPP_PREFIX = "Payment ";
123
+ function parseMppChallenge(response) {
124
+ const header = response.headers.get("WWW-Authenticate");
125
+ if (!header) {
126
+ return null;
127
+ }
128
+ if (!header.startsWith(MPP_PREFIX)) {
129
+ return null;
130
+ }
131
+ const serialized = header.slice(MPP_PREFIX.length).trim();
132
+ if (serialized.length === 0) {
133
+ return null;
134
+ }
135
+ return { serialized };
136
+ }
137
+
138
+ // src/payment-signer.ts
139
+ var import_node_crypto3 = require("crypto");
140
+
141
+ // src/hmac.ts
142
+ var import_node_crypto = require("crypto");
143
+ function computeSignature(secret, method, path, subOrgId, body, timestamp) {
144
+ const bodyDigest = (0, import_node_crypto.createHash)("sha256").update(body).digest("hex");
145
+ const signingString = `${method}
146
+ ${path}
147
+ ${subOrgId}
148
+ ${bodyDigest}
149
+ ${timestamp}`;
150
+ return (0, import_node_crypto.createHmac)("sha256", secret).update(signingString).digest("hex");
151
+ }
152
+ function buildHmacHeaders(secret, method, path, subOrgId, body) {
153
+ const timestamp = String(Math.floor(Date.now() / 1e3));
154
+ const signature = computeSignature(
155
+ secret,
156
+ method,
157
+ path,
158
+ subOrgId,
159
+ body,
160
+ timestamp
161
+ );
162
+ return {
163
+ "X-KH-Sub-Org": subOrgId,
164
+ "X-KH-Timestamp": timestamp,
165
+ "X-KH-Signature": signature
166
+ };
167
+ }
168
+
169
+ // src/types.ts
170
+ var KeeperHubError = class extends Error {
171
+ code;
172
+ constructor(code, message) {
173
+ super(message);
174
+ this.name = "KeeperHubError";
175
+ this.code = code;
176
+ }
177
+ };
178
+ var WalletConfigMissingError = class extends Error {
179
+ constructor() {
180
+ super(
181
+ "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
182
+ );
183
+ this.name = "WalletConfigMissingError";
184
+ }
185
+ };
186
+ var WalletConfigCorruptError = class extends Error {
187
+ path;
188
+ constructor(path, reason) {
189
+ super(
190
+ `Wallet config at ${path} is unreadable: ${reason}. Repair the file by hand or delete it to re-provision a new wallet (this will abandon any funds held in the current wallet).`
191
+ );
192
+ this.name = "WalletConfigCorruptError";
193
+ this.path = path;
194
+ }
195
+ };
196
+
197
+ // src/client.ts
198
+ var TRAILING_SLASH = /\/$/;
199
+ function defaultCodeForStatus(status) {
200
+ if (status === 401) {
201
+ return "HMAC_INVALID";
202
+ }
203
+ if (status === 403) {
204
+ return "POLICY_BLOCKED";
205
+ }
206
+ if (status === 404) {
207
+ return "NOT_FOUND";
208
+ }
209
+ if (status === 502) {
210
+ return "TURNKEY_UPSTREAM";
211
+ }
212
+ return `HTTP_${status}`;
213
+ }
214
+ var KeeperHubClient = class {
215
+ baseUrl;
216
+ fetchImpl;
217
+ wallet;
218
+ constructor(wallet, opts = {}) {
219
+ this.wallet = wallet;
220
+ const envBase = process.env.KEEPERHUB_API_URL;
221
+ this.baseUrl = (opts.baseUrl ?? envBase ?? "https://app.keeperhub.com").replace(TRAILING_SLASH, "");
222
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
223
+ }
224
+ /**
225
+ * HMAC-signed POST/GET to any /api/agentic-wallet/* route except
226
+ * /provision. Path MUST start with a leading slash. Body is
227
+ * JSON.stringify'd (or the empty string for GET).
228
+ *
229
+ * Error mapping: non-2xx/non-202 surface as `KeeperHubError(code,
230
+ * message)` where `code` is the server-supplied field or the default
231
+ * taxonomy (`HMAC_INVALID`, `POLICY_BLOCKED`, `NOT_FOUND`,
232
+ * `TURNKEY_UPSTREAM`, `HTTP_<status>`). 202 ask-tier surfaces as an
233
+ * AskTierResponse envelope.
234
+ */
235
+ async request(method, path, body) {
236
+ const bodyStr = body === void 0 ? "" : JSON.stringify(body);
237
+ const hmacHeaders = buildHmacHeaders(
238
+ this.wallet.hmacSecret,
239
+ method,
240
+ path,
241
+ this.wallet.subOrgId,
242
+ bodyStr
243
+ );
244
+ const headers = method === "POST" ? { ...hmacHeaders, "content-type": "application/json" } : { ...hmacHeaders };
245
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
246
+ method,
247
+ headers,
248
+ body: method === "POST" ? bodyStr : void 0
249
+ });
250
+ if (response.status === 202) {
251
+ const data = await response.json();
252
+ return { _status: 202, approvalRequestId: data.approvalRequestId };
253
+ }
254
+ if (!response.ok) {
255
+ let code = "UNKNOWN";
256
+ let message = `HTTP ${response.status}`;
257
+ try {
258
+ const data = await response.json();
259
+ code = data.code ?? defaultCodeForStatus(response.status);
260
+ message = data.error ?? message;
261
+ } catch {
262
+ }
263
+ throw new KeeperHubError(code, message);
264
+ }
265
+ return await response.json();
266
+ }
267
+ };
268
+
269
+ // src/storage.ts
270
+ var import_node_crypto2 = require("crypto");
271
+ var import_promises = require("fs/promises");
272
+ var import_node_os = require("os");
273
+ var import_node_path = require("path");
274
+ async function readWalletConfig() {
275
+ const walletPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
276
+ let raw;
277
+ try {
278
+ raw = await (0, import_promises.readFile)(walletPath, "utf-8");
279
+ } catch (err) {
280
+ if (err.code === "ENOENT") {
281
+ throw new WalletConfigMissingError();
282
+ }
283
+ throw err;
284
+ }
285
+ let parsed;
286
+ try {
287
+ parsed = JSON.parse(raw);
288
+ } catch (err) {
289
+ const reason = err instanceof Error ? err.message : String(err);
290
+ throw new WalletConfigCorruptError(walletPath, reason);
291
+ }
292
+ if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
293
+ throw new WalletConfigCorruptError(walletPath, "missing required fields");
294
+ }
295
+ return parsed;
296
+ }
297
+ async function writeWalletConfig(config) {
298
+ const walletPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
299
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(walletPath), { recursive: true, mode: 448 });
300
+ const suffix = (0, import_node_crypto2.randomBytes)(8).toString("hex");
301
+ const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
302
+ await (0, import_promises.writeFile)(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
303
+ await (0, import_promises.chmod)(tmpPath, 384);
304
+ await (0, import_promises.rename)(tmpPath, walletPath);
305
+ }
306
+
307
+ // src/workflow-slug.ts
308
+ var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
309
+ function extractKeeperHubWorkflowSlug(url) {
310
+ if (!url || url.length === 0) {
311
+ return { ok: false, reason: "EMPTY_URL" };
312
+ }
313
+ const match = KEEPERHUB_WORKFLOW_RE.exec(url);
314
+ if (!match || !match[1]) {
315
+ return { ok: false, reason: "URL_PATTERN_MISMATCH" };
316
+ }
317
+ return { ok: true, slug: match[1] };
318
+ }
319
+
320
+ // src/x402-detect.ts
321
+ function isX402Shape(value) {
322
+ if (typeof value !== "object" || value === null) {
323
+ return false;
324
+ }
325
+ const v = value;
326
+ if (v.x402Version !== 2) {
327
+ return false;
328
+ }
329
+ if (!Array.isArray(v.accepts) || v.accepts.length === 0) {
330
+ return false;
331
+ }
332
+ const first = v.accepts[0];
333
+ if (first.scheme !== "exact") {
334
+ return false;
335
+ }
336
+ return true;
337
+ }
338
+ async function parseX402Challenge(response) {
339
+ const headerB64 = response.headers.get("PAYMENT-REQUIRED");
340
+ if (headerB64) {
341
+ try {
342
+ const decoded = JSON.parse(
343
+ Buffer.from(headerB64, "base64").toString("utf-8")
344
+ );
345
+ if (isX402Shape(decoded)) {
346
+ return decoded;
347
+ }
348
+ } catch {
349
+ }
350
+ }
351
+ try {
352
+ const clone = response.clone();
353
+ const body = await clone.json();
354
+ if (isX402Shape(body)) {
355
+ return body;
356
+ }
357
+ } catch {
358
+ }
359
+ return null;
360
+ }
361
+
362
+ // src/payment-signer.ts
363
+ var TEMPO_CHAIN_ID = 4217;
364
+ var DEFAULT_APPROVAL_POLL = { intervalMs: 2e3, maxAttempts: 150 };
365
+ var VALID_AFTER_PAST_SLACK_SECONDS = 60;
366
+ var NONCE_BYTES = 32;
367
+ async function sleep(ms) {
368
+ await new Promise((resolve) => setTimeout(resolve, ms));
369
+ }
370
+ function selectProtocol(x402, mpp, hint) {
371
+ const h = hint ?? "auto";
372
+ if (h === "x402") {
373
+ if (!x402) {
374
+ throw new KeeperHubError(
375
+ "X402_NOT_OFFERED",
376
+ "x402 is not offered by this endpoint"
377
+ );
378
+ }
379
+ return "x402";
380
+ }
381
+ if (h === "mpp") {
382
+ if (!mpp) {
383
+ throw new KeeperHubError(
384
+ "MPP_NOT_OFFERED",
385
+ "mpp is not offered by this endpoint"
386
+ );
387
+ }
388
+ return "mpp";
389
+ }
390
+ if (x402) return "x402";
391
+ if (mpp) return "mpp";
392
+ return null;
393
+ }
394
+ function createPaymentSigner(opts = {}) {
395
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
396
+ const walletLoader = opts.walletLoader ?? readWalletConfig;
397
+ const clientFactory = opts.clientFactory ?? ((wallet) => new KeeperHubClient(wallet, { fetch: fetchImpl }));
398
+ const pollCfg = opts.approval ?? DEFAULT_APPROVAL_POLL;
399
+ async function signOrPoll(client, body) {
400
+ const result = await client.request(
401
+ "POST",
402
+ "/api/agentic-wallet/sign",
403
+ body
404
+ );
405
+ if ("_status" in result && result._status === 202) {
406
+ const approvalRequestId = result.approvalRequestId;
407
+ for (let attempt = 0; attempt < pollCfg.maxAttempts; attempt++) {
408
+ await sleep(pollCfg.intervalMs);
409
+ const status = await client.request(
410
+ "GET",
411
+ `/api/agentic-wallet/approval-request/${approvalRequestId}`
412
+ );
413
+ if ("status" in status && status.status !== "pending") {
414
+ if (status.status === "rejected") {
415
+ throw new KeeperHubError(
416
+ "APPROVAL_REJECTED",
417
+ "User rejected the operation"
418
+ );
419
+ }
420
+ const retry = await client.request(
421
+ "POST",
422
+ "/api/agentic-wallet/sign",
423
+ body
424
+ );
425
+ if ("_status" in retry) {
426
+ throw new KeeperHubError(
427
+ "APPROVAL_LOOP",
428
+ "Sign returned 202 again after approval"
429
+ );
430
+ }
431
+ return retry.signature;
432
+ }
433
+ }
434
+ throw new KeeperHubError(
435
+ "APPROVAL_TIMEOUT",
436
+ `No human response within ${pollCfg.intervalMs * pollCfg.maxAttempts}ms`
437
+ );
438
+ }
439
+ return result.signature;
440
+ }
441
+ async function payViaMpp(response, mpp, wallet, retry) {
442
+ const slug = extractKeeperHubWorkflowSlug(response.url);
443
+ if (!slug.ok) {
444
+ throw new KeeperHubError(
445
+ "UNSUPPORTED_RECIPIENT",
446
+ `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`
447
+ );
448
+ }
449
+ const client = clientFactory(wallet);
450
+ const signature = await signOrPoll(client, {
451
+ chain: "tempo",
452
+ workflowSlug: slug.slug,
453
+ paymentChallenge: {
454
+ kind: "mpp",
455
+ serialized: mpp.serialized,
456
+ chainId: TEMPO_CHAIN_ID
457
+ }
458
+ });
459
+ const headers = new Headers(retry?.headers);
460
+ headers.set("Authorization", `Payment ${signature}`);
461
+ return fetchImpl(response.url, {
462
+ method: retry?.method ?? "POST",
463
+ headers,
464
+ body: retry?.body ?? void 0
465
+ });
466
+ }
467
+ async function payViaX402(response, x402, wallet, retry) {
468
+ const accept = x402.accepts[0];
469
+ if (!accept) {
470
+ throw new KeeperHubError(
471
+ "X402_EMPTY_ACCEPTS",
472
+ "x402 challenge has no accepts entries"
473
+ );
474
+ }
475
+ const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);
476
+ if (!slug.ok) {
477
+ throw new KeeperHubError(
478
+ "UNSUPPORTED_RECIPIENT",
479
+ `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`
480
+ );
481
+ }
482
+ const now = Math.floor(Date.now() / 1e3);
483
+ const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
484
+ const validBefore = now + accept.maxTimeoutSeconds;
485
+ const nonce = `0x${(0, import_node_crypto3.randomBytes)(NONCE_BYTES).toString("hex")}`;
486
+ const client = clientFactory(wallet);
487
+ const signature = await signOrPoll(client, {
488
+ chain: "base",
489
+ workflowSlug: slug.slug,
490
+ paymentChallenge: {
491
+ kind: "x402",
492
+ payTo: accept.payTo,
493
+ amount: accept.amount,
494
+ validAfter,
495
+ validBefore,
496
+ nonce
497
+ }
498
+ });
499
+ const paymentSigPayload = {
500
+ x402Version: 2,
501
+ accepted: accept,
502
+ payload: {
503
+ signature,
504
+ authorization: {
505
+ from: wallet.walletAddress,
506
+ to: accept.payTo,
507
+ value: accept.amount,
508
+ validAfter: String(validAfter),
509
+ validBefore: String(validBefore),
510
+ nonce
511
+ }
512
+ }
513
+ };
514
+ const paymentSigHeader = Buffer.from(
515
+ JSON.stringify(paymentSigPayload)
516
+ ).toString("base64");
517
+ const retryUrl = x402.resource.url || response.url;
518
+ const headers = new Headers(retry?.headers);
519
+ headers.set("PAYMENT-SIGNATURE", paymentSigHeader);
520
+ return fetchImpl(retryUrl, {
521
+ method: retry?.method ?? "POST",
522
+ headers,
523
+ body: retry?.body ?? void 0
524
+ });
525
+ }
526
+ async function pay(response, options) {
527
+ if (response.status !== 402) {
528
+ return response;
529
+ }
530
+ const x402 = await parseX402Challenge(response);
531
+ const mpp = parseMppChallenge(response);
532
+ if (!(x402 || mpp)) {
533
+ return response;
534
+ }
535
+ const wallet = await walletLoader();
536
+ const protocol = selectProtocol(x402, mpp, options?.paymentHint);
537
+ if (protocol === "x402") {
538
+ return payViaX402(response, x402, wallet, options);
539
+ }
540
+ if (protocol === "mpp") {
541
+ return payViaMpp(response, mpp, wallet, options);
542
+ }
543
+ return response;
544
+ }
545
+ return {
546
+ pay,
547
+ async fetch(input, init) {
548
+ const first = await fetchImpl(input, init);
549
+ if (first.status !== 402) {
550
+ return first;
551
+ }
552
+ return pay(first, {
553
+ body: init?.body ?? void 0,
554
+ headers: init?.headers,
555
+ method: init?.method,
556
+ paymentHint: init?.paymentHint
557
+ });
558
+ }
559
+ };
560
+ }
561
+ var paymentSigner = createPaymentSigner();
562
+
563
+ // src/provision.ts
564
+ var TRAILING_SLASH2 = /\/$/;
565
+ var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
566
+ var ProvisionResponseInvalidError = class extends Error {
567
+ code = "PROVISION_RESPONSE_INVALID";
568
+ constructor(message) {
569
+ super(message);
570
+ this.name = "ProvisionResponseInvalidError";
571
+ }
572
+ };
573
+ var ProvisionHttpError = class extends Error {
574
+ code = "PROVISION_HTTP_ERROR";
575
+ status;
576
+ body;
577
+ constructor(status, body) {
578
+ super(`provision failed: HTTP ${status}: ${body}`);
579
+ this.name = "ProvisionHttpError";
580
+ this.status = status;
581
+ this.body = body;
582
+ }
583
+ };
584
+ function resolveBaseUrl(override) {
585
+ const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
586
+ return candidate.replace(TRAILING_SLASH2, "");
587
+ }
588
+ function isNonEmptyString(value) {
589
+ return typeof value === "string" && value.length > 0;
590
+ }
591
+ function validateProvisionResponse(data) {
592
+ if (typeof data !== "object" || data === null) {
593
+ throw new ProvisionResponseInvalidError(
594
+ "provision response is not an object"
595
+ );
596
+ }
597
+ const { subOrgId, walletAddress, hmacSecret } = data;
598
+ if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
599
+ throw new ProvisionResponseInvalidError(
600
+ "provision response missing subOrgId, walletAddress, or hmacSecret"
601
+ );
602
+ }
603
+ if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
604
+ throw new ProvisionResponseInvalidError(
605
+ `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
606
+ );
607
+ }
608
+ return {
609
+ subOrgId,
610
+ walletAddress,
611
+ hmacSecret
612
+ };
613
+ }
614
+ async function provisionWallet(options = {}) {
615
+ const baseUrl = resolveBaseUrl(options.baseUrl);
616
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
617
+ const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
618
+ method: "POST",
619
+ headers: { "content-type": "application/json" },
620
+ body: "{}",
621
+ signal: AbortSignal.timeout(3e4)
622
+ });
623
+ if (!response.ok) {
624
+ const text = await response.text();
625
+ throw new ProvisionHttpError(response.status, text);
626
+ }
627
+ const raw = await response.json();
628
+ const data = validateProvisionResponse(raw);
629
+ await writeWalletConfig(data);
630
+ return data;
631
+ }
632
+
633
+ // src/safety-config.ts
634
+ var import_promises2 = require("fs/promises");
635
+ var import_node_os2 = require("os");
636
+ var import_node_path2 = require("path");
637
+ var DEFAULT_SAFETY_CONFIG = {
638
+ auto_approve_max_usd: 5,
639
+ ask_threshold_usd: 50,
640
+ block_threshold_usd: 100,
641
+ allowlisted_contracts: [
642
+ "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
643
+ // Base USDC
644
+ "0x20c000000000000000000000b9537d11c60e8b50"
645
+ // Tempo USDC.e
646
+ ]
647
+ };
648
+ function getSafetyPath() {
649
+ return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "safety.json");
650
+ }
651
+ async function loadSafetyConfig() {
652
+ const path = getSafetyPath();
653
+ let raw;
654
+ try {
655
+ raw = await (0, import_promises2.readFile)(path, "utf-8");
656
+ } catch (err) {
657
+ if (err.code === "ENOENT") {
658
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(path), { recursive: true, mode: 448 });
659
+ await (0, import_promises2.writeFile)(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
660
+ mode: 420
661
+ });
662
+ await (0, import_promises2.chmod)(path, 420);
663
+ return DEFAULT_SAFETY_CONFIG;
664
+ }
665
+ throw err;
666
+ }
667
+ const parsed = JSON.parse(raw);
668
+ return validateAndMerge(parsed);
669
+ }
670
+ var THRESHOLD_KEYS = [
671
+ "auto_approve_max_usd",
672
+ "ask_threshold_usd",
673
+ "block_threshold_usd"
674
+ ];
675
+ function validateAndMerge(partial) {
676
+ const merged = {
677
+ auto_approve_max_usd: partial.auto_approve_max_usd ?? DEFAULT_SAFETY_CONFIG.auto_approve_max_usd,
678
+ ask_threshold_usd: partial.ask_threshold_usd ?? DEFAULT_SAFETY_CONFIG.ask_threshold_usd,
679
+ block_threshold_usd: partial.block_threshold_usd ?? DEFAULT_SAFETY_CONFIG.block_threshold_usd,
680
+ allowlisted_contracts: partial.allowlisted_contracts ?? DEFAULT_SAFETY_CONFIG.allowlisted_contracts
681
+ };
682
+ for (const key of THRESHOLD_KEYS) {
683
+ const v = merged[key];
684
+ if (!(Number.isFinite(v) && v >= 0)) {
685
+ throw new Error(
686
+ `safety.json: ${key} must be a non-negative finite number; got ${String(v)}`
687
+ );
688
+ }
689
+ }
690
+ if (merged.ask_threshold_usd < merged.auto_approve_max_usd) {
691
+ throw new Error(
692
+ "safety.json: ask_threshold_usd must be >= auto_approve_max_usd"
693
+ );
694
+ }
695
+ if (merged.block_threshold_usd < merged.ask_threshold_usd) {
696
+ throw new Error(
697
+ "safety.json: block_threshold_usd must be >= ask_threshold_usd"
698
+ );
699
+ }
700
+ if (!Array.isArray(merged.allowlisted_contracts)) {
701
+ throw new Error("safety.json: allowlisted_contracts must be an array");
702
+ }
703
+ merged.allowlisted_contracts = merged.allowlisted_contracts.map(
704
+ (a) => a.toLowerCase()
705
+ );
706
+ return merged;
707
+ }
708
+
709
+ // src/mcp-server.ts
710
+ var BODY_TEXT_CAP_BYTES = 256 * 1024;
711
+ var USDC_DECIMALS2 = 1e6;
712
+ var HTTP_TIMEOUT_MS = 3e4;
713
+ var ACCEPT_CONTROL_CHARS_RE = (
714
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: deliberately matching control chars + Unicode separators + bidi-overrides to neutralise log-injection / hidden-text vectors before rendering upstream-supplied strings
715
+ /[\u0000-\u001f\u007f-\u009f\u2028\u2029\u200b-\u200f\u202a-\u202e]/g
716
+ );
717
+ var KEEPERHUB_BASE_URL_TRAILING = /\/$/;
718
+ function resolveKeeperhubBaseUrl() {
719
+ const candidate = process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
720
+ return candidate.replace(KEEPERHUB_BASE_URL_TRAILING, "");
721
+ }
722
+ function readPackageVersion() {
723
+ try {
724
+ const here = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(__filename));
725
+ const pkgPath = (0, import_node_path3.join)(here, "..", "package.json");
726
+ const raw = (0, import_node_fs.readFileSync)(pkgPath, "utf-8");
727
+ const parsed = JSON.parse(raw);
728
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
729
+ return parsed.version;
730
+ }
731
+ } catch {
732
+ }
733
+ return "0.0.0";
734
+ }
735
+ function sanitise(input) {
736
+ return input.replace(ACCEPT_CONTROL_CHARS_RE, "");
737
+ }
738
+ function logEvent(event, data) {
739
+ const entry = {
740
+ level: "info",
741
+ event,
742
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
743
+ ...data
744
+ };
745
+ process.stderr.write(`${JSON.stringify(entry)}
746
+ `);
747
+ }
748
+ async function withToolLogging(toolName, fn) {
749
+ const startMs = Date.now();
750
+ logEvent("mcp.tool.called", { tool: toolName });
751
+ try {
752
+ const result = await fn();
753
+ logEvent("mcp.tool.completed", {
754
+ tool: toolName,
755
+ duration_ms: Date.now() - startMs,
756
+ success: true
757
+ });
758
+ return result;
759
+ } catch (error) {
760
+ const message = error instanceof Error ? error.message : String(error);
761
+ logEvent("mcp.tool.error", {
762
+ tool: toolName,
763
+ duration_ms: Date.now() - startMs,
764
+ success: false,
765
+ error: message
766
+ });
767
+ throw error;
768
+ }
769
+ }
770
+ function structuredError(payload) {
771
+ return {
772
+ content: [{ type: "text", text: JSON.stringify(payload) }],
773
+ isError: true
774
+ };
775
+ }
776
+ function structuredOk(payload) {
777
+ return {
778
+ content: [{ type: "text", text: JSON.stringify(payload) }]
779
+ };
780
+ }
781
+ function defaultDeps() {
782
+ return {
783
+ readWalletConfig,
784
+ provisionWallet: () => provisionWallet(),
785
+ loadSafetyConfig,
786
+ checkBalance: (wallet) => checkBalance(wallet),
787
+ paymentSigner,
788
+ fetchImpl: globalThis.fetch
789
+ };
790
+ }
791
+ var provisionInflight = null;
792
+ async function ensureWallet(deps) {
793
+ try {
794
+ const wallet = await deps.readWalletConfig();
795
+ return {
796
+ provisioned: false,
797
+ walletAddress: wallet.walletAddress,
798
+ subOrgId: wallet.subOrgId,
799
+ hmacSecret: wallet.hmacSecret
800
+ };
801
+ } catch (err) {
802
+ if (err instanceof WalletConfigCorruptError) {
803
+ throw err;
804
+ }
805
+ if (!(err instanceof WalletConfigMissingError)) {
806
+ throw err;
807
+ }
808
+ if (provisionInflight === null) {
809
+ provisionInflight = (async () => {
810
+ try {
811
+ const minted = await deps.provisionWallet();
812
+ logEvent("mcp.wallet.provisioned", {
813
+ walletAddress: minted.walletAddress
814
+ });
815
+ return minted;
816
+ } finally {
817
+ provisionInflight = null;
818
+ }
819
+ })();
820
+ }
821
+ const wallet = await provisionInflight;
822
+ return {
823
+ provisioned: true,
824
+ walletAddress: wallet.walletAddress,
825
+ subOrgId: wallet.subOrgId,
826
+ hmacSecret: wallet.hmacSecret
827
+ };
828
+ }
829
+ }
830
+ function resetProvisionInflightForTests() {
831
+ provisionInflight = null;
832
+ }
833
+ function microUsdcToUsd(microUsdc) {
834
+ return Number(microUsdc) / USDC_DECIMALS2;
835
+ }
836
+ function extractX402AmountMicro(x402) {
837
+ if (!x402) {
838
+ return null;
839
+ }
840
+ let min = null;
841
+ for (const accept of x402.accepts) {
842
+ if (!/^\d+$/.test(accept.amount)) {
843
+ continue;
844
+ }
845
+ const candidate = BigInt(accept.amount);
846
+ if (min === null || candidate < min) {
847
+ min = candidate;
848
+ }
849
+ }
850
+ return min;
851
+ }
852
+ function parseUsdcAmount(decimal) {
853
+ const match = /^(\d+)(?:\.(\d+))?$/.exec(decimal);
854
+ if (!match) {
855
+ return null;
856
+ }
857
+ const whole = match[1] ?? "0";
858
+ const fracRaw = match[2] ?? "";
859
+ const fracPadded = `${fracRaw}000000`.slice(0, 6);
860
+ try {
861
+ return BigInt(whole) * BigInt(USDC_DECIMALS2) + BigInt(fracPadded);
862
+ } catch {
863
+ return null;
864
+ }
865
+ }
866
+ function pickResponseFormat(requested, contentType) {
867
+ if (requested) {
868
+ return requested;
869
+ }
870
+ const ct = contentType.toLowerCase();
871
+ if (ct.startsWith("text/") || ct.includes("json") || ct.includes("xml") || ct.includes("yaml")) {
872
+ return "text";
873
+ }
874
+ return "base64";
875
+ }
876
+ var callWorkflowInputSchema = {
877
+ slug: import_zod.z.string().min(1).describe("KeeperHub workflow slug"),
878
+ body: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional().describe("JSON body forwarded to the workflow's input schema"),
879
+ paymentHint: import_zod.z.enum(["auto", "x402", "mpp"]).optional().describe(
880
+ "Payment protocol preference. 'auto' (default) prefers x402 when offered, MPP otherwise."
881
+ ),
882
+ responseFormat: import_zod.z.enum(["text", "base64", "json"]).optional().describe(
883
+ "How to render the response body. Defaults to 'text'. Non-text content-types force base64."
884
+ )
885
+ };
886
+ async function loadSafetyOrError(deps) {
887
+ try {
888
+ return { safety: await deps.loadSafetyConfig() };
889
+ } catch (err) {
890
+ const message = err instanceof Error ? err.message : String(err);
891
+ return {
892
+ error: structuredError({
893
+ code: "SAFETY_CONFIG_INVALID",
894
+ message: sanitise(
895
+ `~/.keeperhub/safety.json is unreadable: ${message}. Repair the file by hand or delete it to fall back to defaults.`
896
+ )
897
+ })
898
+ };
899
+ }
900
+ }
901
+ function classifyFetchError(err) {
902
+ if (err instanceof Error) {
903
+ if (err.name === "AbortError" || err.name === "TimeoutError") {
904
+ return {
905
+ code: "UPSTREAM_TIMEOUT",
906
+ message: `Upstream request exceeded ${HTTP_TIMEOUT_MS}ms (${err.message}). Try again, or check https://status.keeperhub.com.`
907
+ };
908
+ }
909
+ if (err instanceof TypeError && err.message.includes("fetch failed")) {
910
+ const cause = typeof err.cause?.code === "string" ? err.cause.code : void 0;
911
+ return {
912
+ code: "UPSTREAM_UNREACHABLE",
913
+ message: `Could not reach KeeperHub upstream (${cause ?? err.message}). Check your network connectivity, then retry.`
914
+ };
915
+ }
916
+ }
917
+ return null;
918
+ }
919
+ function toolErrorEnvelope(err) {
920
+ if (err instanceof WalletConfigCorruptError) {
921
+ return structuredError({
922
+ code: "WALLET_CONFIG_CORRUPT",
923
+ message: sanitise(err.message),
924
+ path: err.path
925
+ });
926
+ }
927
+ if (err instanceof KeeperHubError) {
928
+ return structuredError({
929
+ code: err.code,
930
+ message: sanitise(err.message)
931
+ });
932
+ }
933
+ const fetchClassification = classifyFetchError(err);
934
+ if (fetchClassification) {
935
+ return structuredError({
936
+ code: fetchClassification.code,
937
+ message: sanitise(fetchClassification.message)
938
+ });
939
+ }
940
+ const message = err instanceof Error ? err.message : String(err);
941
+ return structuredError({
942
+ code: "INTERNAL_ERROR",
943
+ message: sanitise(message)
944
+ });
945
+ }
946
+ async function handleCallWorkflow(args, deps) {
947
+ const safetyResult = await loadSafetyOrError(deps);
948
+ if ("error" in safetyResult) {
949
+ return safetyResult.error;
950
+ }
951
+ const { safety } = safetyResult;
952
+ let ensured;
953
+ try {
954
+ ensured = await ensureWallet(deps);
955
+ } catch (err) {
956
+ return toolErrorEnvelope(err);
957
+ }
958
+ const baseUrl = resolveKeeperhubBaseUrl();
959
+ const url = `${baseUrl}/api/mcp/workflows/${encodeURIComponent(args.slug)}/call`;
960
+ const bodyJson = JSON.stringify(args.body ?? {});
961
+ let probe;
962
+ try {
963
+ probe = await deps.fetchImpl(url, {
964
+ method: "POST",
965
+ headers: { "content-type": "application/json" },
966
+ body: bodyJson,
967
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
968
+ });
969
+ } catch (err) {
970
+ return toolErrorEnvelope(err);
971
+ }
972
+ if (probe.status === 402) {
973
+ const x402 = await parseX402Challenge(probe);
974
+ const mpp = parseMppChallenge(probe);
975
+ const amountMicro = extractX402AmountMicro(x402);
976
+ if (amountMicro !== null) {
977
+ const blockMicro = BigInt(
978
+ Math.round(safety.block_threshold_usd * USDC_DECIMALS2)
979
+ );
980
+ if (amountMicro > blockMicro) {
981
+ const attemptedUsd = microUsdcToUsd(amountMicro);
982
+ return structuredError({
983
+ code: "POLICY_BLOCKED",
984
+ message: sanitise(
985
+ `Payment of ${attemptedUsd} USD exceeds local safety cap of ${safety.block_threshold_usd} USD (block_threshold_usd in ~/.keeperhub/safety.json).`
986
+ ),
987
+ threshold_usd: safety.block_threshold_usd,
988
+ attempted_usd: attemptedUsd,
989
+ ...ensured.provisioned ? {
990
+ provisioned: true,
991
+ walletAddress: ensured.walletAddress,
992
+ fundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl
993
+ } : {}
994
+ });
995
+ }
996
+ const balanceSnap = await deps.checkBalance({
997
+ subOrgId: ensured.subOrgId,
998
+ walletAddress: ensured.walletAddress,
999
+ hmacSecret: ensured.hmacSecret
1000
+ });
1001
+ const baseBalance = parseUsdcAmount(balanceSnap.base.amount);
1002
+ if (baseBalance !== null && baseBalance < amountMicro) {
1003
+ const fundInfo = fund(ensured.walletAddress);
1004
+ return structuredError({
1005
+ code: "INSUFFICIENT_FUNDS",
1006
+ message: sanitise(
1007
+ `Wallet ${ensured.walletAddress} has ${balanceSnap.base.amount} Base USDC; payment requires ${microUsdcToUsd(amountMicro)} USD.`
1008
+ ),
1009
+ needed_usd: microUsdcToUsd(amountMicro),
1010
+ balance_usd: Number(balanceSnap.base.amount),
1011
+ funding_url: fundInfo.coinbaseOnrampUrl,
1012
+ walletAddress: ensured.walletAddress,
1013
+ ...ensured.provisioned ? { provisioned: true } : {}
1014
+ });
1015
+ }
1016
+ }
1017
+ if (!(x402 || mpp)) {
1018
+ const text = await probe.text();
1019
+ return structuredError({
1020
+ code: "PAYMENT_REQUIRED_UNPARSEABLE",
1021
+ message: sanitise(
1022
+ `Upstream returned 402 with no parseable x402 or MPP challenge. Body: ${text.slice(0, 512)}`
1023
+ )
1024
+ });
1025
+ }
1026
+ }
1027
+ let final;
1028
+ try {
1029
+ final = await deps.paymentSigner.fetch(url, {
1030
+ method: "POST",
1031
+ headers: { "content-type": "application/json" },
1032
+ body: bodyJson,
1033
+ paymentHint: args.paymentHint ?? "auto",
1034
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
1035
+ });
1036
+ } catch (err) {
1037
+ const env = toolErrorEnvelope(err);
1038
+ if (ensured.provisioned && env.isError) {
1039
+ const parsed = JSON.parse(env.content[0]?.text ?? "{}");
1040
+ return structuredError({
1041
+ ...parsed,
1042
+ provisioned: true,
1043
+ walletAddress: ensured.walletAddress,
1044
+ fundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl
1045
+ });
1046
+ }
1047
+ return env;
1048
+ }
1049
+ const paid = probe.status === 402 && final.status !== 402;
1050
+ const protocolUsed = paid ? final.headers.get("x402-protocol") ?? "x402" : void 0;
1051
+ const HEADER_ALLOWLIST = /* @__PURE__ */ new Set([
1052
+ "content-type",
1053
+ "content-length",
1054
+ "x402-protocol",
1055
+ "x-execution-id",
1056
+ "execution-id",
1057
+ "x-ratelimit-limit",
1058
+ "x-ratelimit-remaining",
1059
+ "x-ratelimit-reset",
1060
+ "retry-after"
1061
+ ]);
1062
+ const headersOut = {};
1063
+ for (const [k, v] of final.headers.entries()) {
1064
+ if (HEADER_ALLOWLIST.has(k.toLowerCase())) {
1065
+ headersOut[k] = v;
1066
+ }
1067
+ }
1068
+ const executionId = final.headers.get("x-execution-id") ?? final.headers.get("execution-id");
1069
+ const contentType = final.headers.get("content-type") ?? "";
1070
+ const responseFormat = pickResponseFormat(args.responseFormat, contentType);
1071
+ const buf = Buffer.from(await final.arrayBuffer());
1072
+ const truncated = buf.byteLength > BODY_TEXT_CAP_BYTES;
1073
+ const sliced = truncated ? buf.subarray(0, BODY_TEXT_CAP_BYTES) : buf;
1074
+ let bodyOut;
1075
+ if (responseFormat === "base64") {
1076
+ bodyOut = sliced.toString("base64");
1077
+ } else {
1078
+ bodyOut = sliced.toString("utf-8");
1079
+ if (responseFormat === "json") {
1080
+ try {
1081
+ const reparsed = JSON.parse(bodyOut);
1082
+ bodyOut = JSON.stringify(reparsed);
1083
+ } catch {
1084
+ }
1085
+ }
1086
+ }
1087
+ const result = {
1088
+ status: final.status,
1089
+ headers: headersOut,
1090
+ bodyText: bodyOut,
1091
+ paid,
1092
+ responseFormat
1093
+ };
1094
+ if (truncated) {
1095
+ result.bodyTruncated = true;
1096
+ }
1097
+ if (protocolUsed) {
1098
+ result.protocolUsed = protocolUsed;
1099
+ }
1100
+ if (executionId) {
1101
+ result.executionId = executionId;
1102
+ }
1103
+ if (ensured.provisioned) {
1104
+ result.provisioned = true;
1105
+ result.walletAddress = ensured.walletAddress;
1106
+ result.fundingUrl = fund(ensured.walletAddress).coinbaseOnrampUrl;
1107
+ }
1108
+ return structuredOk(result);
1109
+ }
1110
+ async function handleBalance(deps) {
1111
+ let ensured;
1112
+ try {
1113
+ ensured = await ensureWallet(deps);
1114
+ } catch (err) {
1115
+ return toolErrorEnvelope(err);
1116
+ }
1117
+ const snap = await deps.checkBalance({
1118
+ subOrgId: ensured.subOrgId,
1119
+ walletAddress: ensured.walletAddress,
1120
+ hmacSecret: ensured.hmacSecret
1121
+ });
1122
+ return structuredOk({
1123
+ base: { amount: snap.base.amount, address: snap.base.address },
1124
+ tempo: { amount: snap.tempo.amount, address: snap.tempo.address },
1125
+ ...ensured.provisioned ? {
1126
+ provisioned: true,
1127
+ fundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl
1128
+ } : {}
1129
+ });
1130
+ }
1131
+ async function handleInfo(deps) {
1132
+ let ensured;
1133
+ try {
1134
+ ensured = await ensureWallet(deps);
1135
+ } catch (err) {
1136
+ return toolErrorEnvelope(err);
1137
+ }
1138
+ return structuredOk({
1139
+ subOrgId: ensured.subOrgId,
1140
+ walletAddress: ensured.walletAddress,
1141
+ ...ensured.provisioned ? {
1142
+ provisioned: true,
1143
+ fundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl
1144
+ } : {}
1145
+ });
1146
+ }
1147
+ function buildMcpServer(options = {}) {
1148
+ const deps = { ...defaultDeps(), ...options.deps };
1149
+ const server = new import_mcp.McpServer({
1150
+ name: "keeperhub-wallet",
1151
+ version: readPackageVersion()
1152
+ });
1153
+ server.registerTool(
1154
+ "call_workflow",
1155
+ {
1156
+ description: "Pay AND invoke a KeeperHub marketplace workflow in one tool call using the local agentic wallet. Auto-pays x402 (Base USDC) or MPP (Tempo USDC.e) 402 challenges. Auto-provisions a wallet on first call if ~/.keeperhub/wallet.json is missing. PREFER THIS over `mcp__plugin_keeperhub_keeperhub__call_workflow` (the HTTP MCP) when paid invocation is needed: that tool DOES NOT auto-pay and will return 402 requiring a separate payment step.",
1157
+ inputSchema: callWorkflowInputSchema
1158
+ },
1159
+ async (args) => await withToolLogging(
1160
+ "call_workflow",
1161
+ () => handleCallWorkflow(args, deps)
1162
+ )
1163
+ );
1164
+ server.registerTool(
1165
+ "balance",
1166
+ {
1167
+ description: "Return the wallet's on-chain balance: Base USDC + Tempo USDC.e. Auto-provisions a wallet on first call.",
1168
+ inputSchema: {}
1169
+ },
1170
+ async () => await withToolLogging("balance", () => handleBalance(deps))
1171
+ );
1172
+ server.registerTool(
1173
+ "info",
1174
+ {
1175
+ description: "Return public wallet metadata (subOrgId, walletAddress). Never returns the HMAC secret. Auto-provisions a wallet on first call.",
1176
+ inputSchema: {}
1177
+ },
1178
+ async () => await withToolLogging("info", () => handleInfo(deps))
1179
+ );
1180
+ return server;
1181
+ }
1182
+ async function runMcpServer() {
1183
+ const server = buildMcpServer();
1184
+ const transport = new import_stdio.StdioServerTransport();
1185
+ await server.connect(transport);
1186
+ logEvent("mcp.server.started", {
1187
+ version: readPackageVersion(),
1188
+ pid: process.pid,
1189
+ baseUrl: resolveKeeperhubBaseUrl()
1190
+ });
1191
+ }
1192
+ var __test__ = {
1193
+ handleCallWorkflow,
1194
+ handleBalance,
1195
+ handleInfo,
1196
+ defaultDeps,
1197
+ resetProvisionInflightForTests,
1198
+ BODY_TEXT_CAP_BYTES
1199
+ };
1200
+ // Annotate the CommonJS export names for ESM import in node:
1201
+ 0 && (module.exports = {
1202
+ __test__,
1203
+ buildMcpServer,
1204
+ runMcpServer
1205
+ });
1206
+ //# sourceMappingURL=mcp-server.cjs.map