@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 @@
1
+ {"version":3,"sources":["../src/mcp-server.ts","../src/balance.ts","../src/chains.ts","../src/fund.ts","../src/mpp-detect.ts","../src/payment-signer.ts","../src/hmac.ts","../src/types.ts","../src/client.ts","../src/storage.ts","../src/workflow-slug.ts","../src/x402-detect.ts","../src/provision.ts","../src/safety-config.ts"],"sourcesContent":["// stdio MCP server exposing 3 tools backed by @keeperhub/wallet:\n//\n// - call_workflow : pay-and-invoke a KeeperHub marketplace workflow\n// - balance : on-chain balance snapshot (Base USDC + Tempo USDC.e)\n// - info : public wallet metadata (subOrgId, walletAddress)\n//\n// Design intent: the user wants to install one package (`@keeperhub/wallet`)\n// and immediately call paid workflows from Claude Code without writing a Node\n// script. The `call_workflow` tool wraps the same `paymentSigner.fetch` flow\n// that the README documents for direct use, with two safety augments:\n//\n// 1. block_threshold_usd from ~/.keeperhub/safety.json is enforced inline\n// BEFORE the payment signs. The PreToolUse hook cannot see the 402\n// challenge (it fires on the MCP tool call, where there is no payment\n// shape yet — see hook.ts:hasPaymentShape), so the gate that would\n// normally fire there is replicated here.\n// 2. Auto-provisioning: on first tool call, if ~/.keeperhub/wallet.json is\n// missing we run the same provision flow as `keeperhub-wallet add` so\n// the user does not need to run a CLI ceremony. The provisioned wallet\n// starts with zero balance; the next 402 round-trip will surface\n// INSUFFICIENT_FUNDS with a Coinbase Onramp URL.\n//\n// Stdio transport: stdin/stdout are reserved for the MCP protocol. ALL\n// diagnostic output (`mcp.tool.called/completed/error` events) goes to\n// stderr — writing to stdout would corrupt the JSON-RPC stream.\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { type BalanceSnapshot, checkBalance } from \"./balance.js\";\nimport { fund } from \"./fund.js\";\nimport { parseMppChallenge } from \"./mpp-detect.js\";\nimport {\n\tpaymentSigner as defaultPaymentSigner,\n\ttype PaymentSigner,\n} from \"./payment-signer.js\";\nimport { provisionWallet } from \"./provision.js\";\nimport { loadSafetyConfig, type SafetyConfig } from \"./safety-config.js\";\nimport { readWalletConfig } from \"./storage.js\";\nimport {\n\tKeeperHubError,\n\ttype WalletConfig,\n\tWalletConfigCorruptError,\n\tWalletConfigMissingError,\n} from \"./types.js\";\nimport { parseX402Challenge, type X402Challenge } from \"./x402-detect.js\";\n\n// 256 KB ceiling on bodyText returned to the model. Larger bodies are\n// truncated with `bodyTruncated: true`. Prevents single-tool responses from\n// torching context windows.\nconst BODY_TEXT_CAP_BYTES = 256 * 1024;\nconst USDC_DECIMALS = 1_000_000;\n\n// Abort outbound HTTP after this long. Picked to comfortably exceed normal\n// Cloudflare-fronted KH response time (typically <2s) but bound the\n// \"wallet tool is hanging\" failure mode users would otherwise see when\n// upstream is wedged (TCP open, no response). Hits BOTH the 402 probe and\n// the paymentSigner round-trip so neither leg can hang indefinitely.\nconst HTTP_TIMEOUT_MS = 30_000;\n\n// Sanitise upstream-supplied strings before rendering them to the agent.\n// Pattern copied verbatim from KeeperHub server lib/mcp/tools.ts\n// ACCEPT_CONTROL_CHARS_RE — strips C0/C1 control chars, line/paragraph\n// separators, zero-width chars, and bidi-overrides. Defends against\n// log-injection / hidden-text vectors when echoing 402 messages.\nconst ACCEPT_CONTROL_CHARS_RE =\n\t// 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\n\t/[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029\\u200b-\\u200f\\u202a-\\u202e]/g;\n\nconst KEEPERHUB_BASE_URL_TRAILING = /\\/$/;\n\nfunction resolveKeeperhubBaseUrl(): string {\n\tconst candidate =\n\t\tprocess.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\";\n\treturn candidate.replace(KEEPERHUB_BASE_URL_TRAILING, \"\");\n}\n\nfunction readPackageVersion(): string {\n\ttry {\n\t\tconst here = dirname(fileURLToPath(import.meta.url));\n\t\tconst pkgPath = join(here, \"..\", \"package.json\");\n\t\tconst raw = readFileSync(pkgPath, \"utf-8\");\n\t\tconst parsed = JSON.parse(raw) as { version?: string };\n\t\tif (typeof parsed.version === \"string\" && parsed.version.length > 0) {\n\t\t\treturn parsed.version;\n\t\t}\n\t} catch {\n\t\t// fall through\n\t}\n\treturn \"0.0.0\";\n}\n\nfunction sanitise(input: string): string {\n\treturn input.replace(ACCEPT_CONTROL_CHARS_RE, \"\");\n}\n\n// ---- Structured logging (stderr-only) -------------------------------------\n\nfunction logEvent(event: string, data: Record<string, unknown>): void {\n\tconst entry = {\n\t\tlevel: \"info\",\n\t\tevent,\n\t\tts: new Date().toISOString(),\n\t\t...data,\n\t};\n\t// stderr is mandatory: stdout carries the MCP JSON-RPC stream.\n\tprocess.stderr.write(`${JSON.stringify(entry)}\\n`);\n}\n\nasync function withToolLogging<T>(\n\ttoolName: string,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst startMs = Date.now();\n\tlogEvent(\"mcp.tool.called\", { tool: toolName });\n\ttry {\n\t\tconst result = await fn();\n\t\tlogEvent(\"mcp.tool.completed\", {\n\t\t\ttool: toolName,\n\t\t\tduration_ms: Date.now() - startMs,\n\t\t\tsuccess: true,\n\t\t});\n\t\treturn result;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlogEvent(\"mcp.tool.error\", {\n\t\t\ttool: toolName,\n\t\t\tduration_ms: Date.now() - startMs,\n\t\t\tsuccess: false,\n\t\t\terror: message,\n\t\t});\n\t\tthrow error;\n\t}\n}\n\n// ---- Tool result envelopes -----------------------------------------------\n\ntype StructuredErrorPayload = {\n\tcode: string;\n\tmessage: string;\n\t[k: string]: unknown;\n};\n\ntype ToolContent = { type: \"text\"; text: string };\n\ntype ToolResult = {\n\tcontent: ToolContent[];\n\tisError?: boolean;\n};\n\nfunction structuredError(payload: StructuredErrorPayload): ToolResult {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: JSON.stringify(payload) }],\n\t\tisError: true,\n\t};\n}\n\nfunction structuredOk(payload: Record<string, unknown>): ToolResult {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: JSON.stringify(payload) }],\n\t};\n}\n\n// ---- Dependency injection (test seam) -------------------------------------\n\n/**\n * Test seam: every external boundary the handlers touch is collected here so\n * `tests/unit/mcp-server.test.ts` can inject mocks without monkey-patching\n * module globals. Production code passes nothing — defaults bind to the real\n * implementations and the public API of `buildMcpServer()` does not change.\n */\nexport type McpServerDeps = {\n\treadWalletConfig: () => Promise<WalletConfig>;\n\tprovisionWallet: () => Promise<WalletConfig>;\n\tloadSafetyConfig: () => Promise<SafetyConfig>;\n\tcheckBalance: (wallet: WalletConfig) => Promise<BalanceSnapshot>;\n\tpaymentSigner: PaymentSigner;\n\tfetchImpl: typeof fetch;\n};\n\nfunction defaultDeps(): McpServerDeps {\n\treturn {\n\t\treadWalletConfig,\n\t\tprovisionWallet: () => provisionWallet(),\n\t\tloadSafetyConfig,\n\t\tcheckBalance: (wallet) => checkBalance(wallet),\n\t\tpaymentSigner: defaultPaymentSigner,\n\t\tfetchImpl: globalThis.fetch,\n\t};\n}\n\n// ---- Auto-provisioning ----------------------------------------------------\n\ntype EnsureWalletResult = {\n\tprovisioned: boolean;\n\twalletAddress: `0x${string}`;\n\tsubOrgId: string;\n\thmacSecret: string;\n};\n\n/**\n * In-process gate against the concurrent-provision race. Without this, two\n * tool calls hitting an empty `~/.keeperhub/wallet.json` simultaneously\n * (`info` + `balance` from a single Claude session is enough — the SDK\n * dispatches them in parallel) both see WalletConfigMissingError, both POST\n * `/api/agentic-wallet/provision`, the server mints two distinct\n * (subOrgId, hmacSecret) triples, and last-rename-wins on\n * writeWalletConfig leaves disk holding wallet B while the loser's\n * envelope already showed wallet A's address to the user. Subsequent\n * calls then sign with B's HMAC against A's subOrgId and 401 forever,\n * with funds potentially trapped in the orphaned wallet A.\n *\n * The cache is scoped to a single in-flight provision attempt; on settle\n * (success or failure) it clears, so callers after the first successful\n * provision read the on-disk config via the normal `readWalletConfig`\n * path and never enter the provision branch again.\n */\nlet provisionInflight: Promise<WalletConfig> | null = null;\n\nasync function ensureWallet(deps: McpServerDeps): Promise<EnsureWalletResult> {\n\ttry {\n\t\tconst wallet = await deps.readWalletConfig();\n\t\treturn {\n\t\t\tprovisioned: false,\n\t\t\twalletAddress: wallet.walletAddress,\n\t\t\tsubOrgId: wallet.subOrgId,\n\t\t\thmacSecret: wallet.hmacSecret,\n\t\t};\n\t} catch (err) {\n\t\t// Fail-closed for corrupt configs: the file's PRESENCE means the user\n\t\t// (or a prior install) intentionally created a wallet there. Auto-\n\t\t// minting a replacement would silently abandon any funds held by the\n\t\t// existing wallet. Surface a structured error with the path so the\n\t\t// user can repair or delete the file deliberately.\n\t\tif (err instanceof WalletConfigCorruptError) {\n\t\t\tthrow err;\n\t\t}\n\t\tif (!(err instanceof WalletConfigMissingError)) {\n\t\t\tthrow err;\n\t\t}\n\t\t// Coalesce concurrent provision attempts onto a single in-flight\n\t\t// promise. The first caller to enter the catch sets the slot;\n\t\t// subsequent callers await the same promise rather than firing a\n\t\t// second provision request. On settle the slot clears so any later\n\t\t// invocation (e.g. user manually deleted wallet.json again) goes\n\t\t// through a fresh provision.\n\t\tif (provisionInflight === null) {\n\t\t\tprovisionInflight = (async (): Promise<WalletConfig> => {\n\t\t\t\ttry {\n\t\t\t\t\tconst minted = await deps.provisionWallet();\n\t\t\t\t\tlogEvent(\"mcp.wallet.provisioned\", {\n\t\t\t\t\t\twalletAddress: minted.walletAddress,\n\t\t\t\t\t});\n\t\t\t\t\treturn minted;\n\t\t\t\t} finally {\n\t\t\t\t\tprovisionInflight = null;\n\t\t\t\t}\n\t\t\t})();\n\t\t}\n\t\tconst wallet = await provisionInflight;\n\t\treturn {\n\t\t\tprovisioned: true,\n\t\t\twalletAddress: wallet.walletAddress,\n\t\t\tsubOrgId: wallet.subOrgId,\n\t\t\thmacSecret: wallet.hmacSecret,\n\t\t};\n\t}\n}\n\n/** Test-only: clear the in-process provision cache between test cases. */\nfunction resetProvisionInflightForTests(): void {\n\tprovisionInflight = null;\n}\n\n// ---- Payment-amount parsing for safety check -----------------------------\n\nfunction microUsdcToUsd(microUsdc: bigint): number {\n\t// 6-decimal USDC. Convert via Number for rendering only — the threshold\n\t// check itself stays in bigint.\n\treturn Number(microUsdc) / USDC_DECIMALS;\n}\n\n/**\n * Extract the lowest per-call payment amount in micro-USDC from a 402\n * challenge. Returns null when no challenge is present (caller will fall\n * through and let paymentSigner handle the no-protocol case).\n *\n * For x402: scan ALL `accepts[]` entries and pick the cheapest parseable\n * amount. Spec doesn't guarantee `accepts[0]` is the cheapest — it's just\n * the first offered. paymentSigner will pick whatever it picks for its\n * own protocol-preference reasons; if even the minimum exceeds our cap\n * we know we'd over-pay regardless. Conversely, blocking on `[0]` when\n * `[1]` is cheaper would over-block calls the user actually wants to make.\n *\n * For MPP: the serialised mppx credential includes amount fields, but the\n * client never decodes it — we let the server enforce the cap there. We\n * therefore only block on x402 amount; MPP amounts pass through to the\n * server's policy hard cap, which is the authoritative gate (GUARD-06).\n */\nfunction extractX402AmountMicro(x402: X402Challenge | null): bigint | null {\n\tif (!x402) {\n\t\treturn null;\n\t}\n\tlet min: bigint | null = null;\n\tfor (const accept of x402.accepts) {\n\t\tif (!/^\\d+$/.test(accept.amount)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst candidate = BigInt(accept.amount);\n\t\tif (min === null || candidate < min) {\n\t\t\tmin = candidate;\n\t\t}\n\t}\n\treturn min;\n}\n\n// Convert \"1.234567\" decimal-USDC string from balance.checkBalance into\n// micro-USDC bigint. Returns null when the input is not a parseable number.\nfunction parseUsdcAmount(decimal: string): bigint | null {\n\tconst match = /^(\\d+)(?:\\.(\\d+))?$/.exec(decimal);\n\tif (!match) {\n\t\treturn null;\n\t}\n\tconst whole = match[1] ?? \"0\";\n\tconst fracRaw = match[2] ?? \"\";\n\tconst fracPadded = `${fracRaw}000000`.slice(0, 6);\n\ttry {\n\t\treturn BigInt(whole) * BigInt(USDC_DECIMALS) + BigInt(fracPadded);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction pickResponseFormat(\n\trequested: \"text\" | \"base64\" | \"json\" | undefined,\n\tcontentType: string,\n): \"text\" | \"base64\" | \"json\" {\n\tif (requested) {\n\t\treturn requested;\n\t}\n\tconst ct = contentType.toLowerCase();\n\tif (\n\t\tct.startsWith(\"text/\") ||\n\t\tct.includes(\"json\") ||\n\t\tct.includes(\"xml\") ||\n\t\tct.includes(\"yaml\")\n\t) {\n\t\treturn \"text\";\n\t}\n\treturn \"base64\";\n}\n\n// ---- Tool handlers --------------------------------------------------------\n\nconst callWorkflowInputSchema = {\n\tslug: z.string().min(1).describe(\"KeeperHub workflow slug\"),\n\tbody: z\n\t\t.record(z.string(), z.unknown())\n\t\t.optional()\n\t\t.describe(\"JSON body forwarded to the workflow's input schema\"),\n\tpaymentHint: z\n\t\t.enum([\"auto\", \"x402\", \"mpp\"])\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"Payment protocol preference. 'auto' (default) prefers x402 when offered, MPP otherwise.\",\n\t\t),\n\tresponseFormat: z\n\t\t.enum([\"text\", \"base64\", \"json\"])\n\t\t.optional()\n\t\t.describe(\n\t\t\t\"How to render the response body. Defaults to 'text'. Non-text content-types force base64.\",\n\t\t),\n};\n\ntype CallWorkflowArgs = {\n\tslug: string;\n\tbody?: Record<string, unknown>;\n\tpaymentHint?: \"auto\" | \"x402\" | \"mpp\";\n\tresponseFormat?: \"text\" | \"base64\" | \"json\";\n};\n\n/**\n * Load the user's safety thresholds, returning null if the config file is\n * present-but-broken. Fail-CLOSED on a load error: the caller surfaces a\n * structured `SAFETY_CONFIG_INVALID` envelope so the user can repair the\n * file. Without this, a malformed safety.json throws past the structured-\n * error contract and the model sees a JSON-RPC transport explosion with\n * no path to recover. Missing-file is the safe default elsewhere — only\n * malformed bytes hit this path.\n */\nasync function loadSafetyOrError(\n\tdeps: McpServerDeps,\n): Promise<{ safety: SafetyConfig } | { error: ToolResult }> {\n\ttry {\n\t\treturn { safety: await deps.loadSafetyConfig() };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\terror: structuredError({\n\t\t\t\tcode: \"SAFETY_CONFIG_INVALID\",\n\t\t\t\tmessage: sanitise(\n\t\t\t\t\t`~/.keeperhub/safety.json is unreadable: ${message}. Repair the file by hand or delete it to fall back to defaults.`,\n\t\t\t\t),\n\t\t\t}),\n\t\t};\n\t}\n}\n\n/**\n * Map exceptions raised inside any tool handler into the structured-error\n * envelope contract. Throwing past this would surface as a JSON-RPC\n * transport error which the calling agent cannot programmatically recover\n * from. Specifically: corrupt wallet config, KeeperHubError-coded\n * failures, and provision/HTTP errors should all become `isError:true`\n * envelopes so Claude can read `code` and act.\n */\n/**\n * Categorise a thrown fetch failure. AbortError fires on `AbortSignal.timeout`\n * (our own timeout cap) and on transport disconnects (Claude Code shutting\n * down the MCP process mid-request). `TypeError: fetch failed` is Node's\n * undici error for DNS/TCP/TLS failures — distinct from a successful HTTP\n * response with a 4xx/5xx status. Both deserve their own envelope so models\n * can branch programmatically (retry vs surface vs give up).\n */\nfunction classifyFetchError(err: unknown): {\n\tcode: \"UPSTREAM_TIMEOUT\" | \"UPSTREAM_UNREACHABLE\";\n\tmessage: string;\n} | null {\n\tif (err instanceof Error) {\n\t\tif (err.name === \"AbortError\" || err.name === \"TimeoutError\") {\n\t\t\treturn {\n\t\t\t\tcode: \"UPSTREAM_TIMEOUT\",\n\t\t\t\tmessage: `Upstream request exceeded ${HTTP_TIMEOUT_MS}ms (${err.message}). Try again, or check https://status.keeperhub.com.`,\n\t\t\t};\n\t\t}\n\t\tif (err instanceof TypeError && err.message.includes(\"fetch failed\")) {\n\t\t\tconst cause =\n\t\t\t\ttypeof (err as { cause?: { code?: string } }).cause?.code === \"string\"\n\t\t\t\t\t? (err as { cause: { code: string } }).cause.code\n\t\t\t\t\t: undefined;\n\t\t\treturn {\n\t\t\t\tcode: \"UPSTREAM_UNREACHABLE\",\n\t\t\t\tmessage: `Could not reach KeeperHub upstream (${cause ?? err.message}). Check your network connectivity, then retry.`,\n\t\t\t};\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction toolErrorEnvelope(err: unknown): ToolResult {\n\tif (err instanceof WalletConfigCorruptError) {\n\t\treturn structuredError({\n\t\t\tcode: \"WALLET_CONFIG_CORRUPT\",\n\t\t\tmessage: sanitise(err.message),\n\t\t\tpath: err.path,\n\t\t});\n\t}\n\tif (err instanceof KeeperHubError) {\n\t\treturn structuredError({\n\t\t\tcode: err.code,\n\t\t\tmessage: sanitise(err.message),\n\t\t});\n\t}\n\tconst fetchClassification = classifyFetchError(err);\n\tif (fetchClassification) {\n\t\treturn structuredError({\n\t\t\tcode: fetchClassification.code,\n\t\t\tmessage: sanitise(fetchClassification.message),\n\t\t});\n\t}\n\tconst message = err instanceof Error ? err.message : String(err);\n\treturn structuredError({\n\t\tcode: \"INTERNAL_ERROR\",\n\t\tmessage: sanitise(message),\n\t});\n}\n\nasync function handleCallWorkflow(\n\targs: CallWorkflowArgs,\n\tdeps: McpServerDeps,\n): Promise<ToolResult> {\n\tconst safetyResult = await loadSafetyOrError(deps);\n\tif (\"error\" in safetyResult) {\n\t\treturn safetyResult.error;\n\t}\n\tconst { safety } = safetyResult;\n\n\tlet ensured: EnsureWalletResult;\n\ttry {\n\t\tensured = await ensureWallet(deps);\n\t} catch (err) {\n\t\treturn toolErrorEnvelope(err);\n\t}\n\tconst baseUrl = resolveKeeperhubBaseUrl();\n\tconst url = `${baseUrl}/api/mcp/workflows/${encodeURIComponent(args.slug)}/call`;\n\tconst bodyJson = JSON.stringify(args.body ?? {});\n\n\t// Initial fetch: lets us inspect the 402 challenge and enforce the\n\t// block_threshold + insufficient-funds checks BEFORE handing off to\n\t// paymentSigner. paymentSigner re-issues its own internal probe in\n\t// `.fetch()`, which is fine — the second probe is the same shape.\n\tlet probe: Response;\n\ttry {\n\t\tprobe = await deps.fetchImpl(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: bodyJson,\n\t\t\tsignal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\treturn toolErrorEnvelope(err);\n\t}\n\n\tif (probe.status === 402) {\n\t\tconst x402 = await parseX402Challenge(probe);\n\t\tconst mpp = parseMppChallenge(probe);\n\n\t\t// block_threshold check: only enforced for x402 because the MPP\n\t\t// serialised credential is opaque on the client. The server-side\n\t\t// Turnkey policy is the authoritative cap for MPP (GUARD-06).\n\t\tconst amountMicro = extractX402AmountMicro(x402);\n\t\tif (amountMicro !== null) {\n\t\t\tconst blockMicro = BigInt(\n\t\t\t\tMath.round(safety.block_threshold_usd * USDC_DECIMALS),\n\t\t\t);\n\t\t\tif (amountMicro > blockMicro) {\n\t\t\t\tconst attemptedUsd = microUsdcToUsd(amountMicro);\n\t\t\t\treturn structuredError({\n\t\t\t\t\tcode: \"POLICY_BLOCKED\",\n\t\t\t\t\tmessage: sanitise(\n\t\t\t\t\t\t`Payment of ${attemptedUsd} USD exceeds local safety cap of ${safety.block_threshold_usd} USD (block_threshold_usd in ~/.keeperhub/safety.json).`,\n\t\t\t\t\t),\n\t\t\t\t\tthreshold_usd: safety.block_threshold_usd,\n\t\t\t\t\tattempted_usd: attemptedUsd,\n\t\t\t\t\t...(ensured.provisioned\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tprovisioned: true,\n\t\t\t\t\t\t\t\twalletAddress: ensured.walletAddress,\n\t\t\t\t\t\t\t\tfundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {}),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Insufficient on-chain balance check (x402 only — MPP credential is\n\t\t\t// opaque). Surface a structured error pointing the user at funding\n\t\t\t// instructions instead of letting the retry burn through to a 4xx.\n\t\t\tconst balanceSnap = await deps.checkBalance({\n\t\t\t\tsubOrgId: ensured.subOrgId,\n\t\t\t\twalletAddress: ensured.walletAddress,\n\t\t\t\thmacSecret: ensured.hmacSecret,\n\t\t\t});\n\t\t\tconst baseBalance = parseUsdcAmount(balanceSnap.base.amount);\n\t\t\tif (baseBalance !== null && baseBalance < amountMicro) {\n\t\t\t\tconst fundInfo = fund(ensured.walletAddress);\n\t\t\t\treturn structuredError({\n\t\t\t\t\tcode: \"INSUFFICIENT_FUNDS\",\n\t\t\t\t\tmessage: sanitise(\n\t\t\t\t\t\t`Wallet ${ensured.walletAddress} has ${balanceSnap.base.amount} Base USDC; payment requires ${microUsdcToUsd(amountMicro)} USD.`,\n\t\t\t\t\t),\n\t\t\t\t\tneeded_usd: microUsdcToUsd(amountMicro),\n\t\t\t\t\tbalance_usd: Number(balanceSnap.base.amount),\n\t\t\t\t\tfunding_url: fundInfo.coinbaseOnrampUrl,\n\t\t\t\t\twalletAddress: ensured.walletAddress,\n\t\t\t\t\t...(ensured.provisioned ? { provisioned: true } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// No challenge at all — bail with a structured error rather than\n\t\t// passing the empty 402 through to paymentSigner.\n\t\tif (!(x402 || mpp)) {\n\t\t\tconst text = await probe.text();\n\t\t\treturn structuredError({\n\t\t\t\tcode: \"PAYMENT_REQUIRED_UNPARSEABLE\",\n\t\t\t\tmessage: sanitise(\n\t\t\t\t\t`Upstream returned 402 with no parseable x402 or MPP challenge. Body: ${text.slice(0, 512)}`,\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t}\n\n\t// Hand off to the canonical signer. paymentSigner.fetch internally\n\t// re-fires the original request, handles 402 -> /sign -> retry-with-\n\t// PAYMENT-SIGNATURE, and returns the post-payment Response.\n\tlet final: Response;\n\ttry {\n\t\tfinal = await deps.paymentSigner.fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: bodyJson,\n\t\t\tpaymentHint: args.paymentHint ?? \"auto\",\n\t\t\tsignal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\t// Use the unified envelope path so KeeperHubError, AbortError, and\n\t\t// network failures all surface as `isError:true` structured envelopes\n\t\t// the calling agent can introspect — never raw transport errors.\n\t\tconst env = toolErrorEnvelope(err);\n\t\t// Decorate with `provisioned`/`walletAddress`/`fundingUrl` when this\n\t\t// was the first call so the model can tell the user about the new\n\t\t// wallet even if the call failed.\n\t\tif (ensured.provisioned && env.isError) {\n\t\t\tconst parsed = JSON.parse(env.content[0]?.text ?? \"{}\") as Record<\n\t\t\t\tstring,\n\t\t\t\tunknown\n\t\t\t>;\n\t\t\treturn structuredError({\n\t\t\t\t...(parsed as { code: string; message: string }),\n\t\t\t\tprovisioned: true,\n\t\t\t\twalletAddress: ensured.walletAddress,\n\t\t\t\tfundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl,\n\t\t\t});\n\t\t}\n\t\treturn env;\n\t}\n\n\tconst paid = probe.status === 402 && final.status !== 402;\n\tconst protocolUsed = paid\n\t\t? (final.headers.get(\"x402-protocol\") ?? \"x402\")\n\t\t: undefined;\n\n\t// Allowlist of upstream response headers we surface to the agent. We\n\t// deliberately drop everything else: (1) Cloudflare/CDN noise (cf-ray,\n\t// nel, alt-svc) wastes context-window space, (2) reflected request\n\t// auth headers like X-KH-Signature/X-KH-Sub-Org could leak HMAC\n\t// material if a misconfigured/compromised upstream ever echoed them\n\t// (defence in depth — current upstream never does), (3) Set-Cookie\n\t// would expose session state from a compromised upstream. The fields\n\t// kept are the ones a model needs to interpret + retry: protocol,\n\t// content shape, rate-limit headroom, execution lookup.\n\tconst HEADER_ALLOWLIST: ReadonlySet<string> = new Set([\n\t\t\"content-type\",\n\t\t\"content-length\",\n\t\t\"x402-protocol\",\n\t\t\"x-execution-id\",\n\t\t\"execution-id\",\n\t\t\"x-ratelimit-limit\",\n\t\t\"x-ratelimit-remaining\",\n\t\t\"x-ratelimit-reset\",\n\t\t\"retry-after\",\n\t]);\n\tconst headersOut: Record<string, string> = {};\n\tfor (const [k, v] of final.headers.entries()) {\n\t\tif (HEADER_ALLOWLIST.has(k.toLowerCase())) {\n\t\t\theadersOut[k] = v;\n\t\t}\n\t}\n\tconst executionId =\n\t\tfinal.headers.get(\"x-execution-id\") ?? final.headers.get(\"execution-id\");\n\n\tconst contentType = final.headers.get(\"content-type\") ?? \"\";\n\tconst responseFormat = pickResponseFormat(args.responseFormat, contentType);\n\n\tconst buf = Buffer.from(await final.arrayBuffer());\n\tconst truncated = buf.byteLength > BODY_TEXT_CAP_BYTES;\n\tconst sliced = truncated ? buf.subarray(0, BODY_TEXT_CAP_BYTES) : buf;\n\tlet bodyOut: string;\n\tif (responseFormat === \"base64\") {\n\t\tbodyOut = sliced.toString(\"base64\");\n\t} else {\n\t\tbodyOut = sliced.toString(\"utf-8\");\n\t\tif (responseFormat === \"json\") {\n\t\t\ttry {\n\t\t\t\tconst reparsed: unknown = JSON.parse(bodyOut);\n\t\t\t\tbodyOut = JSON.stringify(reparsed);\n\t\t\t} catch {\n\t\t\t\t// fall through with raw text\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result: Record<string, unknown> = {\n\t\tstatus: final.status,\n\t\theaders: headersOut,\n\t\tbodyText: bodyOut,\n\t\tpaid,\n\t\tresponseFormat,\n\t};\n\tif (truncated) {\n\t\tresult.bodyTruncated = true;\n\t}\n\tif (protocolUsed) {\n\t\tresult.protocolUsed = protocolUsed;\n\t}\n\tif (executionId) {\n\t\tresult.executionId = executionId;\n\t}\n\tif (ensured.provisioned) {\n\t\tresult.provisioned = true;\n\t\tresult.walletAddress = ensured.walletAddress;\n\t\tresult.fundingUrl = fund(ensured.walletAddress).coinbaseOnrampUrl;\n\t}\n\treturn structuredOk(result);\n}\n\nasync function handleBalance(deps: McpServerDeps): Promise<ToolResult> {\n\tlet ensured: EnsureWalletResult;\n\ttry {\n\t\tensured = await ensureWallet(deps);\n\t} catch (err) {\n\t\treturn toolErrorEnvelope(err);\n\t}\n\tconst snap = await deps.checkBalance({\n\t\tsubOrgId: ensured.subOrgId,\n\t\twalletAddress: ensured.walletAddress,\n\t\thmacSecret: ensured.hmacSecret,\n\t});\n\treturn structuredOk({\n\t\tbase: { amount: snap.base.amount, address: snap.base.address },\n\t\ttempo: { amount: snap.tempo.amount, address: snap.tempo.address },\n\t\t...(ensured.provisioned\n\t\t\t? {\n\t\t\t\t\tprovisioned: true,\n\t\t\t\t\tfundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl,\n\t\t\t\t}\n\t\t\t: {}),\n\t});\n}\n\nasync function handleInfo(deps: McpServerDeps): Promise<ToolResult> {\n\t// CRITICAL: never include hmacSecret. Same rule as src/cli.ts cmdAdd at\n\t// lines 113-115 (T-34-cli-02 mitigation). The CLI prints only public\n\t// fields; the MCP server mirrors that exactly.\n\tlet ensured: EnsureWalletResult;\n\ttry {\n\t\tensured = await ensureWallet(deps);\n\t} catch (err) {\n\t\treturn toolErrorEnvelope(err);\n\t}\n\treturn structuredOk({\n\t\tsubOrgId: ensured.subOrgId,\n\t\twalletAddress: ensured.walletAddress,\n\t\t...(ensured.provisioned\n\t\t\t? {\n\t\t\t\t\tprovisioned: true,\n\t\t\t\t\tfundingUrl: fund(ensured.walletAddress).coinbaseOnrampUrl,\n\t\t\t\t}\n\t\t\t: {}),\n\t});\n}\n\n// ---- Server bootstrap -----------------------------------------------------\n\nexport type BuildMcpServerOptions = {\n\t/** Inject mock dependencies (tests). Defaults to {@link defaultDeps}. */\n\tdeps?: Partial<McpServerDeps>;\n};\n\nexport function buildMcpServer(options: BuildMcpServerOptions = {}): McpServer {\n\tconst deps: McpServerDeps = { ...defaultDeps(), ...options.deps };\n\tconst server = new McpServer({\n\t\tname: \"keeperhub-wallet\",\n\t\tversion: readPackageVersion(),\n\t});\n\n\tserver.registerTool(\n\t\t\"call_workflow\",\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"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.\",\n\t\t\tinputSchema: callWorkflowInputSchema,\n\t\t},\n\t\tasync (args) =>\n\t\t\tawait withToolLogging(\"call_workflow\", () =>\n\t\t\t\thandleCallWorkflow(args, deps),\n\t\t\t),\n\t);\n\n\tserver.registerTool(\n\t\t\"balance\",\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"Return the wallet's on-chain balance: Base USDC + Tempo USDC.e. Auto-provisions a wallet on first call.\",\n\t\t\tinputSchema: {},\n\t\t},\n\t\tasync () => await withToolLogging(\"balance\", () => handleBalance(deps)),\n\t);\n\n\tserver.registerTool(\n\t\t\"info\",\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"Return public wallet metadata (subOrgId, walletAddress). Never returns the HMAC secret. Auto-provisions a wallet on first call.\",\n\t\t\tinputSchema: {},\n\t\t},\n\t\tasync () => await withToolLogging(\"info\", () => handleInfo(deps)),\n\t);\n\n\treturn server;\n}\n\nexport async function runMcpServer(): Promise<void> {\n\tconst server = buildMcpServer();\n\tconst transport = new StdioServerTransport();\n\tawait server.connect(transport);\n\t// Positive boot signal so a maintainer (or the user grepping stderr)\n\t// can confirm the bin actually launched. Distinct from the per-tool\n\t// events because those only fire on first tool call — a server that\n\t// starts cleanly but is never invoked otherwise emits zero events.\n\tlogEvent(\"mcp.server.started\", {\n\t\tversion: readPackageVersion(),\n\t\tpid: process.pid,\n\t\tbaseUrl: resolveKeeperhubBaseUrl(),\n\t});\n}\n\n// ---- Test-only exports ----------------------------------------------------\n//\n// Exported so unit tests can call the handlers directly without spinning up\n// the stdio transport. The handlers are pure async functions of (args, deps)\n// and (deps) — they never reach for module globals after this refactor.\n\nexport const __test__ = {\n\thandleCallWorkflow,\n\thandleBalance,\n\thandleInfo,\n\tdefaultDeps,\n\tresetProvisionInflightForTests,\n\tBODY_TEXT_CAP_BYTES,\n};\n","// checkBalance() unified view (PAY-05):\n// - Base USDC balanceOf (viem publicClient on Base)\n// - Tempo USDC.e balanceOf (viem publicClient on Tempo)\n//\n// Both legs are fetched in parallel via Promise.all. The on-chain reads\n// touch only the canonical USDC contract on their respective chains\n// (read-only ERC-20 balanceOf with no state mutation).\n//\n// The /api/agentic-wallet/credit ledger is intentionally NOT read here:\n// the server endpoint exists but no debit path is wired, so surfacing the\n// balance to users implied a capability that has not shipped. Restore the\n// leg here when KEEP-305/306 lands.\n//\n// @security balance.ts does not emit balance data to stdout/stderr via the\n// global console object or util.inspect (T-34-bal-02 mitigation). Any\n// stdout emitter added here is a privacy regression; grep-enforced in\n// acceptance criteria.\nimport {\n createPublicClient,\n erc20Abi,\n formatUnits,\n http,\n type PublicClient,\n} from \"viem\";\nimport { BASE_USDC, base, TEMPO_USDC_E, tempo } from \"./chains.js\";\nimport type { WalletConfig } from \"./types.js\";\n\n// USDC and USDC.e both use 6 decimals on Base + Tempo respectively.\nconst USDC_DECIMALS = 6;\n\nexport type BalanceSnapshot = {\n base: {\n chain: \"base\";\n token: \"USDC\";\n amount: string;\n address: `0x${string}`;\n };\n tempo: {\n chain: \"tempo\";\n token: \"USDC.e\";\n amount: string;\n address: `0x${string}`;\n };\n};\n\nexport type CheckBalanceOptions = {\n /** Injectable viem client for Base (tests mock readContract). */\n baseClient?: PublicClient;\n /** Injectable viem client for Tempo (tests mock readContract). */\n tempoClient?: PublicClient;\n};\n\n/**\n * Read the wallet's on-chain balance across Base + Tempo in parallel. Both\n * legs must resolve; any single failure rejects the Promise.\n *\n * Amounts are formatted as decimal strings (6-decimal USDC precision) so the\n * caller can render them without BigInt math.\n */\nexport async function checkBalance(\n wallet: WalletConfig,\n opts: CheckBalanceOptions = {}\n): Promise<BalanceSnapshot> {\n const baseClient =\n opts.baseClient ??\n (createPublicClient({\n chain: base,\n transport: http(),\n }) as unknown as PublicClient);\n const tempoClient =\n opts.tempoClient ??\n (createPublicClient({\n chain: tempo,\n transport: http(),\n }) as unknown as PublicClient);\n\n // Promise.all fires both reads concurrently. Total elapsed ~= max(leg)\n // rather than sum(leg); SC-3 (<2s) test asserts this.\n const [baseRaw, tempoRaw] = await Promise.all([\n baseClient.readContract({\n address: BASE_USDC,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [wallet.walletAddress],\n }) as Promise<bigint>,\n tempoClient.readContract({\n address: TEMPO_USDC_E,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [wallet.walletAddress],\n }) as Promise<bigint>,\n ]);\n\n return {\n base: {\n chain: \"base\",\n token: \"USDC\",\n amount: formatUnits(baseRaw, USDC_DECIMALS),\n address: wallet.walletAddress,\n },\n tempo: {\n chain: \"tempo\",\n token: \"USDC.e\",\n amount: formatUnits(tempoRaw, USDC_DECIMALS),\n address: wallet.walletAddress,\n },\n };\n}\n","// Sources (truth):\n// - lib/agentic-wallet/sign.ts:56 -- Base USDC at\n// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (chainId 8453).\n// - lib/mpp/server.ts:3 -- Tempo USDC.e at\n// 0x20c000000000000000000000b9537d11c60e8b50 (chainId 4217).\n//\n// Tempo is not in viem/chains core as of viem 2.48.1 (the version pinned in\n// this package). Define it inline via defineChain so the only dependency is\n// viem itself. TEMPO_RPC_URL overrides the default RPC for heavy readers who\n// want to point at their own node (T-34-bal-01 mitigation).\nimport { defineChain } from \"viem\";\n\nexport { base } from \"viem/chains\";\n\nexport const tempo = defineChain({\n id: 4217,\n name: \"Tempo\",\n nativeCurrency: { decimals: 18, name: \"Ether\", symbol: \"ETH\" },\n rpcUrls: {\n default: {\n http: [process.env.TEMPO_RPC_URL ?? \"https://rpc.tempo.xyz\"],\n },\n },\n blockExplorers: {\n default: { name: \"Tempo Explorer\", url: \"https://explorer.tempo.xyz\" },\n },\n});\n\n/** Circle-issued USDC on Base mainnet. */\nexport const BASE_USDC = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\n/** Bridged USDC (USDC.e) on Tempo mainnet. NOT the same contract as BASE_USDC. */\nexport const TEMPO_USDC_E =\n \"0x20c000000000000000000000b9537d11c60e8b50\" as const;\n","// Source: 34-RESEARCH Pattern 5 + Pitfall 5.\n// Coinbase deprecated the query-param pay.coinbase.com flow in favour of\n// sessionToken URLs on 2025-07-31, but the legacy endpoint still returns a\n// working Onramp page (it just may not pre-fill the asset/network/address\n// fields). We print the legacy URL for zero-dependency ergonomics and a\n// follow-up disclaimer so users know to paste manually if prefill is dropped.\n//\n// fund() is a pure string-build: no HTTP, no process spawn, no browser\n// invocation. Callers (the CLI `keeperhub-wallet fund` subcommand, the\n// `check_balance` skill in Phase 35) decide how to display the result.\n//\n// T-34-fund-01 mitigation: the host is hard-coded (pay.coinbase.com) and the\n// only user-supplied input is the wallet address, which is regex-validated\n// against the canonical 0x-prefixed 40-hex-char EVM format before any string\n// interpolation.\n\nexport type FundInstructions = {\n /** Coinbase Onramp deeplink (legacy query-param form). */\n coinbaseOnrampUrl: string;\n /** Tempo deposit address — same as the input wallet (EVM address shared). */\n tempoAddress: `0x${string}`;\n /** Plain-ASCII guidance string; no emojis (CLAUDE.md rule). */\n disclaimer: string;\n};\n\n// 0x followed by exactly 40 hex chars, case-insensitive. Kept at module scope\n// so the regex literal is compiled once (biome/ultracite useTopLevelRegex).\nconst EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\n\n// Coinbase Onramp legacy deeplink. The host + path pair is the documented\n// entry point for query-param-style Onramp sessions.\nconst COINBASE_HOST = \"pay.coinbase.com\";\nconst COINBASE_PATH = \"/buy/select-asset\";\n\n/**\n * Build Coinbase Onramp URL + Tempo deposit address for the given wallet.\n *\n * No HTTP calls are performed. The caller is expected to either print the\n * resulting URL (CLI) or render it in a chat bubble (skill). The returned\n * `disclaimer` explains the Onramp deprecation + the Tempo external-transfer\n * fallback in plain ASCII so terminal clients with ASCII-only fonts render\n * identically to emoji-capable clients.\n *\n * @throws if `walletAddress` does not match /^0x[0-9a-fA-F]{40}$/.\n */\nexport function fund(walletAddress: string): FundInstructions {\n if (!EVM_ADDRESS_RE.test(walletAddress)) {\n throw new Error(`Invalid EVM wallet address: ${walletAddress}`);\n }\n\n // addresses is a JSON-encoded map {walletAddress: [\"base\"]} per Coinbase\n // Onramp docs. Encoding into URLSearchParams guarantees the colon,\n // brackets, and quotes are percent-escaped correctly.\n const params = new URLSearchParams({\n defaultNetwork: \"base\",\n defaultAsset: \"USDC\",\n addresses: JSON.stringify({ [walletAddress]: [\"base\"] }),\n presetCryptoAmount: \"5\",\n });\n\n const coinbaseOnrampUrl = `https://${COINBASE_HOST}${COINBASE_PATH}?${params.toString()}`;\n\n const disclaimer =\n \"If the Coinbase page does not pre-fill, paste your address manually. \" +\n \"For Tempo USDC.e, transfer from an exchange or another wallet to the \" +\n \"address above -- Onramp does not support Tempo directly. Coinbase \" +\n \"sessionToken URLs are the 2025+ canonical form; legacy query-param \" +\n \"URLs may drop prefill on some accounts.\";\n\n return {\n coinbaseOnrampUrl,\n tempoAddress: walletAddress as `0x${string}`,\n disclaimer,\n };\n}\n","// Source: lib/payments/router.ts:152-175 -- MPP WWW-Authenticate emission.\n// We forward the raw serialized challenge to /api/agentic-wallet/sign; the server\n// has mppx in its deps. Keeps client runtime dep list minimal (supply-chain T-34-02).\n\nexport type MppChallenge = { serialized: string };\n\nconst MPP_PREFIX = \"Payment \";\n\nexport function parseMppChallenge(response: Response): MppChallenge | null {\n const header = response.headers.get(\"WWW-Authenticate\");\n if (!header) {\n return null;\n }\n if (!header.startsWith(MPP_PREFIX)) {\n return null;\n }\n const serialized = header.slice(MPP_PREFIX.length).trim();\n if (serialized.length === 0) {\n return null;\n }\n return { serialized };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { KeeperHubClient } from \"./client.js\";\nimport { type MppChallenge, parseMppChallenge } from \"./mpp-detect.js\";\nimport { readWalletConfig } from \"./storage.js\";\nimport { KeeperHubError, type PaymentHint, type WalletConfig } from \"./types.js\";\nimport { extractKeeperHubWorkflowSlug } from \"./workflow-slug.js\";\nimport { parseX402Challenge, type X402Challenge } from \"./x402-detect.js\";\n\n// Tempo mainnet chain id. Forwarded to /sign so the server routes MPP\n// challenges to the correct signer. Kept in sync with\n// app/api/agentic-wallet/sign/route.ts::TEMPO_CHAIN_ID.\nconst TEMPO_CHAIN_ID = 4217;\n\n// Approval polling: 2s * 150 = 5 minute ceiling on a human response.\n// T-34-ps-04 mitigation (DoS via infinite loop).\nconst DEFAULT_APPROVAL_POLL = { intervalMs: 2000, maxAttempts: 150 };\n\n// Small clock-drift buffer on validAfter. Mirrors the server's\n// VALID_AFTER_FUTURE_SLACK_SECONDS in app/api/agentic-wallet/sign/route.ts.\nconst VALID_AFTER_PAST_SLACK_SECONDS = 60;\n\n// x402 protocol nonce: 32-byte hex (bytes32).\nconst NONCE_BYTES = 32;\n\n/**\n * Polymorphic /sign response. For `chain:\"base\"` the signature is a 132-char\n * 0x-prefixed EIP-712 hex string embedded inside the PAYMENT-SIGNATURE\n * base64-JSON payload. For `chain:\"tempo\"` it is a base64url-encoded MPP\n * credential produced by the server's mppx instance; the client forwards it\n * verbatim as the `Authorization: Payment <signature>` value. The client\n * never parses, decodes, or mutates the MPP credential -- opaque pass-through.\n */\ntype SignResponseOk = { signature: string };\n\ntype ApprovalStatus = \"pending\" | \"approved\" | \"rejected\";\n\ntype PaySignerOptions = {\n /** Override wallet loader (primarily for tests). */\n walletLoader?: () => Promise<WalletConfig>;\n /** Override KeeperHubClient factory (tests inject a mocked fetch). */\n clientFactory?: (wallet: WalletConfig) => KeeperHubClient;\n /** Replayed fetch (tests intercept the retry). */\n fetchImpl?: typeof fetch;\n /** Approval polling override: interval + max attempts. */\n approval?: { intervalMs: number; maxAttempts: number };\n};\n\n/**\n * Retry options threaded through `pay()` and `fetch()` into the post-sign\n * retry. Lets callers forward the original request body and headers so the\n * paid workflow receives the same payload on the retry as on the 402 attempt\n * -- otherwise a workflow whose input schema requires a body (e.g.\n * `{address}` on `/api/mcp/workflows/<slug>/call`) rejects the retry with\n * 400 \"Invalid JSON body\".\n */\nexport type PayRetryOptions = {\n /**\n * Body to re-send on the retry. Must be a type that can be sent twice --\n * string, ArrayBuffer, Uint8Array, FormData, URLSearchParams, or Blob.\n * ReadableStream bodies are NOT supported because the first fetch() already\n * consumed the stream; pass a string/Buffer instead.\n */\n body?: RequestInit[\"body\"];\n /**\n * Additional request headers to merge onto the retry (e.g. Content-Type).\n * The payment auth header (PAYMENT-SIGNATURE or Authorization) is set by\n * the signer and overrides any same-named header in this map.\n */\n headers?: RequestInit[\"headers\"];\n /** HTTP method for the retry. Defaults to \"POST\". */\n method?: string;\n /**\n * Per-call protocol preference. \"x402\" forces Base USDC; \"mpp\" forces Tempo\n * USDC.e; \"auto\" (default, also the behaviour when omitted) uses x402 when\n * offered, MPP otherwise. Throws KeeperHubError(\"X402_NOT_OFFERED\") or\n * KeeperHubError(\"MPP_NOT_OFFERED\") when the requested protocol is absent\n * from the challenge (KEEP-361).\n */\n paymentHint?: PaymentHint;\n};\n\n/** RequestInit extended with paymentHint for per-call protocol selection. */\nexport type FetchInit = RequestInit & { paymentHint?: PaymentHint };\n\nexport type PaymentSigner = {\n /**\n * Pays a 402 response and returns the post-payment retry Response.\n * Non-402 responses are returned unchanged.\n *\n * Pass `options.body` (and usually `options.headers`) if the paid\n * workflow's input schema requires a body -- `pay()` does not have access\n * to the original request otherwise.\n *\n * For most agent code, prefer `signer.fetch(url, init)` which threads the\n * body/headers automatically.\n */\n pay: (response: Response, options?: PayRetryOptions) => Promise<Response>;\n /**\n * `fetch(url, init)` wrapper: does the initial fetch, and on 402 calls\n * `pay()` with `init.body` + `init.headers` so the retry carries the\n * original payload. Returns whatever the retry (or first response, if not\n * 402) returns. No-op for non-402 responses.\n *\n * Pass `init.paymentHint` to force a specific payment protocol for this\n * call. Omitting it is equivalent to `paymentHint: \"auto\"` (x402-first).\n */\n fetch: (input: string | URL, init?: FetchInit) => Promise<Response>;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Pure function that decides which payment protocol to use given challenge\n * availability and caller's hint. Exported for unit testing.\n *\n * Returns \"x402\" or \"mpp\" to direct the caller to the appropriate path,\n * or null when hint is \"auto\" and no challenge is present (pay() then\n * returns the original 402 response unchanged). Throws KeeperHubError with\n * a specific code when the requested protocol is unavailable (KEEP-361).\n */\nexport function selectProtocol(\n x402: X402Challenge | null,\n mpp: MppChallenge | null,\n hint: PaymentHint | undefined\n): \"x402\" | \"mpp\" | null {\n const h = hint ?? \"auto\";\n if (h === \"x402\") {\n if (!x402) {\n throw new KeeperHubError(\n \"X402_NOT_OFFERED\",\n \"x402 is not offered by this endpoint\"\n );\n }\n return \"x402\";\n }\n if (h === \"mpp\") {\n if (!mpp) {\n throw new KeeperHubError(\n \"MPP_NOT_OFFERED\",\n \"mpp is not offered by this endpoint\"\n );\n }\n return \"mpp\";\n }\n // h === \"auto\": x402-first, then MPP, then null\n if (x402) return \"x402\";\n if (mpp) return \"mpp\";\n return null;\n}\n\nexport function createPaymentSigner(\n opts: PaySignerOptions = {}\n): PaymentSigner {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const walletLoader = opts.walletLoader ?? readWalletConfig;\n const clientFactory =\n opts.clientFactory ??\n ((wallet: WalletConfig): KeeperHubClient =>\n new KeeperHubClient(wallet, { fetch: fetchImpl }));\n const pollCfg = opts.approval ?? DEFAULT_APPROVAL_POLL;\n\n async function signOrPoll(\n client: KeeperHubClient,\n body: Record<string, unknown>\n ): Promise<string> {\n const result = await client.request<SignResponseOk>(\n \"POST\",\n \"/api/agentic-wallet/sign\",\n body\n );\n if (\"_status\" in result && result._status === 202) {\n const approvalRequestId = result.approvalRequestId;\n // Poll approval-request until status !== \"pending\" or timeout.\n for (let attempt = 0; attempt < pollCfg.maxAttempts; attempt++) {\n await sleep(pollCfg.intervalMs);\n const status = await client.request<{ status: ApprovalStatus }>(\n \"GET\",\n `/api/agentic-wallet/approval-request/${approvalRequestId}`\n );\n if (\"status\" in status && status.status !== \"pending\") {\n if (status.status === \"rejected\") {\n throw new KeeperHubError(\n \"APPROVAL_REJECTED\",\n \"User rejected the operation\"\n );\n }\n // approved -- retry the sign call (which should now return 200).\n const retry = await client.request<SignResponseOk>(\n \"POST\",\n \"/api/agentic-wallet/sign\",\n body\n );\n if (\"_status\" in retry) {\n throw new KeeperHubError(\n \"APPROVAL_LOOP\",\n \"Sign returned 202 again after approval\"\n );\n }\n return retry.signature;\n }\n }\n throw new KeeperHubError(\n \"APPROVAL_TIMEOUT\",\n `No human response within ${pollCfg.intervalMs * pollCfg.maxAttempts}ms`\n );\n }\n return (result as SignResponseOk).signature;\n }\n\n async function payViaMpp(\n response: Response,\n mpp: MppChallenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const slug = extractKeeperHubWorkflowSlug(response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `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.`\n );\n }\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"tempo\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"mpp\",\n serialized: mpp.serialized,\n chainId: TEMPO_CHAIN_ID,\n },\n });\n const headers = new Headers(retry?.headers);\n headers.set(\"Authorization\", `Payment ${signature}`);\n return fetchImpl(response.url, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function payViaX402(\n response: Response,\n x402: X402Challenge,\n wallet: WalletConfig,\n retry: PayRetryOptions | undefined\n ): Promise<Response> {\n const accept = x402.accepts[0];\n if (!accept) {\n throw new KeeperHubError(\n \"X402_EMPTY_ACCEPTS\",\n \"x402 challenge has no accepts entries\"\n );\n }\n\n const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);\n if (!slug.ok) {\n throw new KeeperHubError(\n \"UNSUPPORTED_RECIPIENT\",\n `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.`\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;\n const validBefore = now + accept.maxTimeoutSeconds;\n const nonce = `0x${randomBytes(NONCE_BYTES).toString(\"hex\")}`;\n\n const client = clientFactory(wallet);\n const signature = await signOrPoll(client, {\n chain: \"base\",\n workflowSlug: slug.slug,\n paymentChallenge: {\n kind: \"x402\",\n payTo: accept.payTo,\n amount: accept.amount,\n validAfter,\n validBefore,\n nonce,\n },\n });\n\n // x402 v2 PaymentPayload per @x402/core mechanisms-* d.ts:\n // { x402Version: 2, accepted: PaymentRequirements, payload: {...} }\n // The server's findMatchingRequirements does a deepEqual between\n // `paymentPayload.accepted` and each challenge `accepts[]` entry, so we\n // mirror the exact accept object we signed against.\n //\n // EIP-3009 inner payload: authorization.value/validAfter/validBefore/nonce\n // must be STRINGS at the wire format (per @x402/evm ExactEIP3009Payload).\n // /sign takes them as numbers; we stringify on the way out.\n const paymentSigPayload = {\n x402Version: 2,\n accepted: accept,\n payload: {\n signature,\n authorization: {\n from: wallet.walletAddress,\n to: accept.payTo,\n value: accept.amount,\n validAfter: String(validAfter),\n validBefore: String(validBefore),\n nonce,\n },\n },\n };\n const paymentSigHeader = Buffer.from(\n JSON.stringify(paymentSigPayload)\n ).toString(\"base64\");\n\n const retryUrl = x402.resource.url || response.url;\n const headers = new Headers(retry?.headers);\n headers.set(\"PAYMENT-SIGNATURE\", paymentSigHeader);\n return fetchImpl(retryUrl, {\n method: retry?.method ?? \"POST\",\n headers,\n body: retry?.body ?? undefined,\n });\n }\n\n async function pay(\n response: Response,\n options?: PayRetryOptions\n ): Promise<Response> {\n if (response.status !== 402) {\n return response;\n }\n\n const x402 = await parseX402Challenge(response);\n const mpp = parseMppChallenge(response);\n if (!(x402 || mpp)) {\n return response;\n }\n\n const wallet = await walletLoader();\n\n // Protocol selection is delegated to selectProtocol(). Default \"auto\"\n // preserves x402-first behavior. Per-call override via paymentHint\n // option (KEEP-361). Submit EXACTLY ONE credential (T-34-ps-02).\n const protocol = selectProtocol(x402, mpp, options?.paymentHint);\n if (protocol === \"x402\") {\n return payViaX402(response, x402 as X402Challenge, wallet, options);\n }\n if (protocol === \"mpp\") {\n return payViaMpp(response, mpp as MppChallenge, wallet, options);\n }\n return response;\n }\n\n return {\n pay,\n async fetch(\n input: string | URL,\n init?: FetchInit\n ): Promise<Response> {\n const first = await fetchImpl(input, init);\n if (first.status !== 402) {\n return first;\n }\n // Forward the caller's body + headers + method + paymentHint to the\n // post-sign retry so the paid workflow receives the same payload on the\n // retry as on the 402 attempt. Fixes the dropped-body bug that made any\n // workflow with a required-input schema reject the retry with 400.\n return pay(first, {\n body: init?.body ?? undefined,\n headers: init?.headers,\n method: init?.method,\n paymentHint: init?.paymentHint,\n });\n },\n };\n}\n\n// Default instance backed by the real fetch + storage.\nexport const paymentSigner: PaymentSigner = createPaymentSigner();\n","import { createHash, createHmac } from \"node:crypto\";\nimport type { HmacHeaders } from \"./types.js\";\n\n/**\n * Mirror of lib/agentic-wallet/hmac.ts::computeSignature.\n * Format (byte-for-byte identical to server):\n * `${method}\\n${path}\\n${subOrgId}\\n${sha256_hex(body)}\\n${timestamp}`\n * Post-HI-05: subOrgId is a signed field.\n *\n * @security Do NOT log the secret or the returned signature. Any stdout\n * emitter (the global console object or util.inspect) added to this\n * file is a T-34-08 violation (grep-enforced).\n */\nexport function computeSignature(\n secret: string,\n method: string,\n path: string,\n subOrgId: string,\n body: string,\n timestamp: string\n): string {\n const bodyDigest = createHash(\"sha256\").update(body).digest(\"hex\");\n const signingString = `${method}\\n${path}\\n${subOrgId}\\n${bodyDigest}\\n${timestamp}`;\n return createHmac(\"sha256\", secret).update(signingString).digest(\"hex\");\n}\n\n/**\n * Build the three X-KH-* headers that authenticate every request to\n * /api/agentic-wallet/* (except /provision, which uses the session cookie).\n *\n * Timestamp is unix seconds (Math.floor(Date.now() / 1000)); the server\n * enforces a symmetric 300-second replay window.\n */\nexport function buildHmacHeaders(\n secret: string,\n method: string,\n path: string,\n subOrgId: string,\n body: string\n): HmacHeaders {\n const timestamp = String(Math.floor(Date.now() / 1000));\n const signature = computeSignature(\n secret,\n method,\n path,\n subOrgId,\n body,\n timestamp\n );\n return {\n \"X-KH-Sub-Org\": subOrgId,\n \"X-KH-Timestamp\": timestamp,\n \"X-KH-Signature\": signature,\n };\n}\n","// Shared types across the package. Phase 34.\nexport type WalletConfig = {\n\t/** Turnkey sub-org ID returned by POST /api/agentic-wallet/provision */\n\tsubOrgId: string;\n\t/** EVM-shared wallet address (same for Base chainId 8453 and Tempo chainId 4217) */\n\twalletAddress: `0x${string}`;\n\t/** 64-char lowercase hex HMAC secret, minted server-side at provision; never logged */\n\thmacSecret: string;\n};\n\nexport type HmacHeaders = {\n\t\"X-KH-Sub-Org\": string;\n\t\"X-KH-Timestamp\": string;\n\t\"X-KH-Signature\": string;\n};\n\nexport type HookDecision = {\n\tdecision: \"allow\" | \"deny\" | \"ask\";\n\treason?: string;\n};\n\nexport class KeeperHubError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"KeeperHubError\";\n\t\tthis.code = code;\n\t}\n}\n\n/** Protocol preference for a single pay() or fetch() call. \"auto\" preserves\n * the x402-first default when both challenges are offered. */\nexport type PaymentHint = \"x402\" | \"mpp\" | \"auto\";\n\nexport class WalletConfigMissingError extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision.\",\n\t\t);\n\t\tthis.name = \"WalletConfigMissingError\";\n\t}\n}\n\n/**\n * Thrown when ~/.keeperhub/wallet.json exists but is unreadable as a wallet\n * config (malformed JSON, truncated write, missing required fields, hand-edit\n * gone wrong). Distinct from WalletConfigMissingError — the file's PRESENCE\n * means the user (or a prior install) intentionally created it, so the MCP\n * server MUST NOT silently auto-provision a replacement and abandon any funds\n * held in the existing wallet. Surface a structured error with the path so\n * the user can repair or delete the file deliberately.\n */\nexport class WalletConfigCorruptError extends Error {\n\treadonly path: string;\n\n\tconstructor(path: string, reason: string) {\n\t\tsuper(\n\t\t\t`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).`,\n\t\t);\n\t\tthis.name = \"WalletConfigCorruptError\";\n\t\tthis.path = path;\n\t}\n}\n","import { buildHmacHeaders } from \"./hmac.js\";\nimport { KeeperHubError, type WalletConfig } from \"./types.js\";\n\nexport type ClientOptions = {\n /** Defaults to process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\" */\n baseUrl?: string;\n /** Injected for tests; defaults to global fetch */\n fetch?: typeof fetch;\n};\n\n/**\n * 202 ask-tier envelope returned by /sign and /approval-request when the\n * risk classifier routes a request to the ask queue. Callers poll\n * `/api/agentic-wallet/approval-request/:id` until status !== \"pending\".\n */\nexport type AskTierResponse = {\n _status: 202;\n approvalRequestId: string;\n};\n\nconst TRAILING_SLASH = /\\/$/;\n\nfunction defaultCodeForStatus(status: number): string {\n if (status === 401) {\n return \"HMAC_INVALID\";\n }\n if (status === 403) {\n return \"POLICY_BLOCKED\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 502) {\n return \"TURNKEY_UPSTREAM\";\n }\n return `HTTP_${status}`;\n}\n\n/**\n * HMAC-signed HTTP client for the KeeperHub agentic-wallet API surface.\n * Every request to /api/agentic-wallet/* (except /provision, which uses\n * the session cookie) flows through this class.\n *\n * @security No logging of headers, body, or response bodies. Any stdout\n * emitter (the global console object or util.inspect) added to this\n * file is a T-34-08 violation (grep-enforced in CI).\n */\nexport class KeeperHubClient {\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly wallet: WalletConfig;\n\n constructor(wallet: WalletConfig, opts: ClientOptions = {}) {\n this.wallet = wallet;\n const envBase = process.env.KEEPERHUB_API_URL;\n this.baseUrl = (\n opts.baseUrl ??\n envBase ??\n \"https://app.keeperhub.com\"\n ).replace(TRAILING_SLASH, \"\");\n this.fetchImpl = opts.fetch ?? globalThis.fetch;\n }\n\n /**\n * HMAC-signed POST/GET to any /api/agentic-wallet/* route except\n * /provision. Path MUST start with a leading slash. Body is\n * JSON.stringify'd (or the empty string for GET).\n *\n * Error mapping: non-2xx/non-202 surface as `KeeperHubError(code,\n * message)` where `code` is the server-supplied field or the default\n * taxonomy (`HMAC_INVALID`, `POLICY_BLOCKED`, `NOT_FOUND`,\n * `TURNKEY_UPSTREAM`, `HTTP_<status>`). 202 ask-tier surfaces as an\n * AskTierResponse envelope.\n */\n async request<T>(\n method: \"GET\" | \"POST\",\n path: string,\n body?: unknown\n ): Promise<T | AskTierResponse> {\n const bodyStr = body === undefined ? \"\" : JSON.stringify(body);\n const hmacHeaders = buildHmacHeaders(\n this.wallet.hmacSecret,\n method,\n path,\n this.wallet.subOrgId,\n bodyStr\n );\n const headers: Record<string, string> =\n method === \"POST\"\n ? { ...hmacHeaders, \"content-type\": \"application/json\" }\n : { ...hmacHeaders };\n const response = await this.fetchImpl(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: method === \"POST\" ? bodyStr : undefined,\n });\n\n if (response.status === 202) {\n const data = (await response.json()) as {\n approvalRequestId: string;\n status: string;\n };\n return { _status: 202, approvalRequestId: data.approvalRequestId };\n }\n\n if (!response.ok) {\n let code = \"UNKNOWN\";\n let message = `HTTP ${response.status}`;\n try {\n const data = (await response.json()) as {\n code?: string;\n error?: string;\n };\n code = data.code ?? defaultCodeForStatus(response.status);\n message = data.error ?? message;\n } catch {\n // body is not JSON -- keep the default code + message\n }\n throw new KeeperHubError(code, message);\n }\n\n return (await response.json()) as T;\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { chmod, mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport {\n\ttype WalletConfig,\n\tWalletConfigCorruptError,\n\tWalletConfigMissingError,\n} from \"./types.js\";\n\n// NOTE: Every function calls `join(homedir(), \".keeperhub\", \"wallet.json\")`\n// itself. Do NOT hoist to a module-level `const WALLET_PATH` -- tests\n// override `process.env.HOME` in `beforeEach` and `homedir()` must re-read\n// that on each call. A hoisted constant would freeze the harness's original\n// HOME at import time and every test would write into the real\n// ~/.keeperhub/ directory.\n\nexport async function readWalletConfig(): Promise<WalletConfig> {\n\tconst walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(walletPath, \"utf-8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\tthrow new WalletConfigMissingError();\n\t\t}\n\t\tthrow err;\n\t}\n\t// The file's PRESENCE means it was created on purpose. Distinguish parse\n\t// failures (malformed JSON, truncated write) and field-shape failures\n\t// (missing required keys) from \"missing entirely\" so callers — including\n\t// the MCP server's auto-provision path — can refuse to silently mint a\n\t// replacement wallet over an existing-but-broken one.\n\tlet parsed: Partial<WalletConfig>;\n\ttry {\n\t\tparsed = JSON.parse(raw) as Partial<WalletConfig>;\n\t} catch (err) {\n\t\tconst reason = err instanceof Error ? err.message : String(err);\n\t\tthrow new WalletConfigCorruptError(walletPath, reason);\n\t}\n\tif (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {\n\t\tthrow new WalletConfigCorruptError(walletPath, \"missing required fields\");\n\t}\n\treturn parsed as WalletConfig;\n}\n\n/**\n * Atomic write: serialise to a sibling tmp file, fsync via the close, then\n * rename(2) into place. Concurrent provisioning races (two MCP sessions\n * spawning at once on a fresh install) collapse to last-rename-wins on the\n * final path rather than torn-write-wins. Combined with the in-process\n * promise cache in mcp-server.ts:ensureWallet this gates duplicate-mint\n * within a single process AND keeps disk consistent across multiple.\n *\n * The tmp filename includes 16 bytes of randomness so two writers don't\n * stomp each other's tmp files mid-write either.\n */\nexport async function writeWalletConfig(config: WalletConfig): Promise<void> {\n\tconst walletPath = join(homedir(), \".keeperhub\", \"wallet.json\");\n\tawait mkdir(dirname(walletPath), { recursive: true, mode: 0o700 });\n\tconst suffix = randomBytes(8).toString(\"hex\");\n\tconst tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;\n\tawait writeFile(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n\t// chmod again on the tmp before rename: writeFile honours the mode on\n\t// creation but a pre-existing tmp (extremely unlikely thanks to the\n\t// randomness) might keep looser perms.\n\tawait chmod(tmpPath, 0o600);\n\tawait rename(tmpPath, walletPath);\n}\n\nexport function getWalletConfigPath(): string {\n\treturn join(homedir(), \".keeperhub\", \"wallet.json\");\n}\n","// Server-derived payTo binding (Phase 37 fix #2 in keeperhub repo).\n//\n// The wallet only signs payments for KeeperHub-registered workflows. The\n// resource URL of the 402 challenge is matched against the canonical\n// /api/mcp/workflows/<slug>/call pattern; the slug is forwarded to /sign so\n// the server can verify payTo + amount against the workflows registry.\n//\n// URLs that don't match this pattern (e.g. arbitrary x402 services discovered\n// in the wild) are unsupported in v0.1.5 — the signer throws\n// UNSUPPORTED_RECIPIENT and refuses to round-trip. KEEP-311's generic 402\n// fetch CLI is a separate codepath with its own threat model.\n\nconst KEEPERHUB_WORKFLOW_RE =\n /\\/api\\/mcp\\/workflows\\/([a-zA-Z0-9_-]+)\\/call(?:\\/?)(?:\\?|$|#)/;\n\nexport type SlugExtractionResult =\n | { ok: true; slug: string }\n | { ok: false; reason: \"EMPTY_URL\" | \"URL_PATTERN_MISMATCH\" };\n\nexport function extractKeeperHubWorkflowSlug(\n url: string | null | undefined\n): SlugExtractionResult {\n if (!url || url.length === 0) {\n return { ok: false, reason: \"EMPTY_URL\" };\n }\n const match = KEEPERHUB_WORKFLOW_RE.exec(url);\n if (!match || !match[1]) {\n return { ok: false, reason: \"URL_PATTERN_MISMATCH\" };\n }\n return { ok: true, slug: match[1] };\n}\n","// Source: lib/payments/router.ts:48-62 (PaymentRequiredV2 server-side shape).\n// Strict parsing per 34-RESEARCH Pitfall 4 -- false-positive 402 detection is a\n// wasted /sign HMAC roundtrip and a potential agent-loop trigger.\n\nexport type X402Challenge = {\n x402Version: 2;\n accepts: Array<{\n scheme: \"exact\";\n network: string;\n asset: string;\n amount: string;\n payTo: string;\n maxTimeoutSeconds: number;\n extra: Record<string, unknown>;\n }>;\n resource: { url: string; description: string; mimeType: string };\n};\n\nfunction isX402Shape(value: unknown): value is X402Challenge {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const v = value as Record<string, unknown>;\n if (v.x402Version !== 2) {\n return false;\n }\n if (!Array.isArray(v.accepts) || v.accepts.length === 0) {\n return false;\n }\n const first = v.accepts[0] as Record<string, unknown>;\n if (first.scheme !== \"exact\") {\n return false;\n }\n return true;\n}\n\nexport async function parseX402Challenge(\n response: Response\n): Promise<X402Challenge | null> {\n // Header path (preferred -- matches lib/payments/router.ts's PAYMENT-REQUIRED emit).\n const headerB64 = response.headers.get(\"PAYMENT-REQUIRED\");\n if (headerB64) {\n try {\n const decoded: unknown = JSON.parse(\n Buffer.from(headerB64, \"base64\").toString(\"utf-8\")\n );\n if (isX402Shape(decoded)) {\n return decoded;\n }\n } catch {\n // fall through to body\n }\n }\n\n // Body path (lib/payments/router.ts also emits the PaymentRequired as the 402 body).\n try {\n const clone = response.clone();\n const body: unknown = await clone.json();\n if (isX402Shape(body)) {\n return body;\n }\n } catch {\n // not JSON\n }\n return null;\n}\n","// Wallet provisioning: shared between `keeperhub-wallet add` (CLI) and the\n// MCP server's auto-provision-on-first-call path.\n//\n// Why a shared module: the user-facing UX target is \"install the wallet\n// package and start calling paid workflows\" — manual `keeperhub-wallet add`\n// ceremony is an unnecessary speed bump. The MCP server therefore reuses\n// this exact provision flow on the first tool call when ~/.keeperhub/wallet.json\n// is missing. Keeping the call site in one module avoids the obvious\n// drift hazard of two divergent provision implementations.\n//\n// @security The HMAC secret minted by /api/agentic-wallet/provision is\n// returned to the caller AND written to ~/.keeperhub/wallet.json (chmod 0o600\n// via writeWalletConfig). Callers MUST NOT log the returned secret to stdout\n// or stderr. The CLI's `cmdAdd` and the MCP server's auto-provision branch\n// both honour this — every printed/serialised path uses only `subOrgId` and\n// `walletAddress`.\n\nimport { writeWalletConfig } from \"./storage.js\";\nimport type { WalletConfig } from \"./types.js\";\n\nconst TRAILING_SLASH = /\\/$/;\nconst WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;\n\nexport type ProvisionOptions = {\n\t/** Override KEEPERHUB_API_URL. Defaults to env var or app.keeperhub.com. */\n\tbaseUrl?: string;\n\t/** Injectable fetch for tests. */\n\tfetchImpl?: typeof fetch;\n};\n\nexport class ProvisionResponseInvalidError extends Error {\n\treadonly code = \"PROVISION_RESPONSE_INVALID\" as const;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ProvisionResponseInvalidError\";\n\t}\n}\n\nexport class ProvisionHttpError extends Error {\n\treadonly code = \"PROVISION_HTTP_ERROR\" as const;\n\treadonly status: number;\n\treadonly body: string;\n\n\tconstructor(status: number, body: string) {\n\t\tsuper(`provision failed: HTTP ${status}: ${body}`);\n\t\tthis.name = \"ProvisionHttpError\";\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nfunction resolveBaseUrl(override: string | undefined): string {\n\tconst candidate =\n\t\toverride ?? process.env.KEEPERHUB_API_URL ?? \"https://app.keeperhub.com\";\n\treturn candidate.replace(TRAILING_SLASH, \"\");\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0;\n}\n\nfunction validateProvisionResponse(data: unknown): WalletConfig {\n\tif (typeof data !== \"object\" || data === null) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t\"provision response is not an object\",\n\t\t);\n\t}\n\tconst { subOrgId, walletAddress, hmacSecret } = data as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tif (\n\t\t!(\n\t\t\tisNonEmptyString(subOrgId) &&\n\t\t\tisNonEmptyString(walletAddress) &&\n\t\t\tisNonEmptyString(hmacSecret)\n\t\t)\n\t) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t\"provision response missing subOrgId, walletAddress, or hmacSecret\",\n\t\t);\n\t}\n\tif (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {\n\t\tthrow new ProvisionResponseInvalidError(\n\t\t\t`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`,\n\t\t);\n\t}\n\treturn {\n\t\tsubOrgId,\n\t\twalletAddress: walletAddress as `0x${string}`,\n\t\thmacSecret,\n\t};\n}\n\n/**\n * Mint a new agentic wallet via POST /api/agentic-wallet/provision and\n * persist the result to ~/.keeperhub/wallet.json (chmod 0o600). Returns the\n * provisioned WalletConfig. Throws ProvisionHttpError on non-2xx and\n * ProvisionResponseInvalidError on malformed payloads.\n *\n * Used by:\n * - `keeperhub-wallet add` (manual provisioning).\n * - The MCP server's auto-provision-on-first-call path (no manual ceremony).\n */\nexport async function provisionWallet(\n\toptions: ProvisionOptions = {},\n): Promise<WalletConfig> {\n\tconst baseUrl = resolveBaseUrl(options.baseUrl);\n\tconst fetchImpl = options.fetchImpl ?? globalThis.fetch;\n\t// Bound the wedged-upstream failure mode so a hung provision endpoint\n\t// can't freeze the MCP tool call indefinitely. AbortError surfaces as\n\t// UPSTREAM_TIMEOUT in the MCP envelope; CLI users see the raw cause.\n\tconst response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"content-type\": \"application/json\" },\n\t\tbody: \"{}\",\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new ProvisionHttpError(response.status, text);\n\t}\n\tconst raw = (await response.json()) as unknown;\n\tconst data = validateProvisionResponse(raw);\n\tawait writeWalletConfig(data);\n\treturn data;\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * User-owned safety config at ~/.keeperhub/safety.json. File mode 0o644 so the\n * user can freely edit thresholds and the allowlist; server-side Turnkey policy\n * remains the authoritative hard cap (GUARD-06).\n */\nexport type SafetyConfig = {\n auto_approve_max_usd: number;\n ask_threshold_usd: number;\n block_threshold_usd: number;\n allowlisted_contracts: string[];\n};\n\n/**\n * Defaults per 34-CONTEXT lines 61-68. Thresholds bracket the Turnkey policy\n * hard cap (100 USDC). Allowlisted contracts mirror the server Turnkey policy\n * allowlist (lib/agentic-wallet/policy.ts FACILITATOR_ALLOWLIST) -- lowercased\n * for case-insensitive match against tool_input.to / paymentChallenge.payTo.\n */\nexport const DEFAULT_SAFETY_CONFIG: SafetyConfig = {\n auto_approve_max_usd: 5,\n ask_threshold_usd: 50,\n block_threshold_usd: 100,\n allowlisted_contracts: [\n \"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\", // Base USDC\n \"0x20c000000000000000000000b9537d11c60e8b50\", // Tempo USDC.e\n ],\n};\n\n// NOTE: Every function calls `join(homedir(), \".keeperhub\", \"safety.json\")`\n// itself -- matches storage.ts. Hoisting to a module-level constant would\n// freeze $HOME at import time and break tests that override process.env.HOME\n// in beforeEach.\n\nfunction getSafetyPath(): string {\n return join(homedir(), \".keeperhub\", \"safety.json\");\n}\n\nexport async function loadSafetyConfig(): Promise<SafetyConfig> {\n const path = getSafetyPath();\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeFile(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {\n mode: 0o644,\n });\n // Reassert mode in case the file already existed with looser perms.\n await chmod(path, 0o644);\n return DEFAULT_SAFETY_CONFIG;\n }\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<SafetyConfig>;\n return validateAndMerge(parsed);\n}\n\nconst THRESHOLD_KEYS = [\n \"auto_approve_max_usd\",\n \"ask_threshold_usd\",\n \"block_threshold_usd\",\n] as const;\n\nexport function validateAndMerge(partial: Partial<SafetyConfig>): SafetyConfig {\n const merged: SafetyConfig = {\n auto_approve_max_usd:\n partial.auto_approve_max_usd ??\n DEFAULT_SAFETY_CONFIG.auto_approve_max_usd,\n ask_threshold_usd:\n partial.ask_threshold_usd ?? DEFAULT_SAFETY_CONFIG.ask_threshold_usd,\n block_threshold_usd:\n partial.block_threshold_usd ?? DEFAULT_SAFETY_CONFIG.block_threshold_usd,\n allowlisted_contracts:\n partial.allowlisted_contracts ??\n DEFAULT_SAFETY_CONFIG.allowlisted_contracts,\n };\n\n for (const key of THRESHOLD_KEYS) {\n const v = merged[key];\n if (!(Number.isFinite(v) && v >= 0)) {\n throw new Error(\n `safety.json: ${key} must be a non-negative finite number; got ${String(v)}`\n );\n }\n }\n if (merged.ask_threshold_usd < merged.auto_approve_max_usd) {\n throw new Error(\n \"safety.json: ask_threshold_usd must be >= auto_approve_max_usd\"\n );\n }\n if (merged.block_threshold_usd < merged.ask_threshold_usd) {\n throw new Error(\n \"safety.json: block_threshold_usd must be >= ask_threshold_usd\"\n );\n }\n if (!Array.isArray(merged.allowlisted_contracts)) {\n throw new Error(\"safety.json: allowlisted_contracts must be an array\");\n }\n merged.allowlisted_contracts = merged.allowlisted_contracts.map((a) =>\n a.toLowerCase()\n );\n return merged;\n}\n\nexport function getSafetyConfigPath(): string {\n return getSafetyPath();\n}\n"],"mappings":"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","names":["import_node_path","import_viem","import_node_crypto","import_node_crypto","TRAILING_SLASH","import_promises","import_node_os","import_node_path","USDC_DECIMALS"]}
@@ -0,0 +1,54 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { W as WalletConfig, S as SafetyConfig, B as BalanceSnapshot, e as PaymentSigner } from './payment-signer-CyeRXcX2.cjs';
3
+ import 'viem';
4
+
5
+ type ToolContent = {
6
+ type: "text";
7
+ text: string;
8
+ };
9
+ type ToolResult = {
10
+ content: ToolContent[];
11
+ isError?: boolean;
12
+ };
13
+ /**
14
+ * Test seam: every external boundary the handlers touch is collected here so
15
+ * `tests/unit/mcp-server.test.ts` can inject mocks without monkey-patching
16
+ * module globals. Production code passes nothing — defaults bind to the real
17
+ * implementations and the public API of `buildMcpServer()` does not change.
18
+ */
19
+ type McpServerDeps = {
20
+ readWalletConfig: () => Promise<WalletConfig>;
21
+ provisionWallet: () => Promise<WalletConfig>;
22
+ loadSafetyConfig: () => Promise<SafetyConfig>;
23
+ checkBalance: (wallet: WalletConfig) => Promise<BalanceSnapshot>;
24
+ paymentSigner: PaymentSigner;
25
+ fetchImpl: typeof fetch;
26
+ };
27
+ declare function defaultDeps(): McpServerDeps;
28
+ /** Test-only: clear the in-process provision cache between test cases. */
29
+ declare function resetProvisionInflightForTests(): void;
30
+ type CallWorkflowArgs = {
31
+ slug: string;
32
+ body?: Record<string, unknown>;
33
+ paymentHint?: "auto" | "x402" | "mpp";
34
+ responseFormat?: "text" | "base64" | "json";
35
+ };
36
+ declare function handleCallWorkflow(args: CallWorkflowArgs, deps: McpServerDeps): Promise<ToolResult>;
37
+ declare function handleBalance(deps: McpServerDeps): Promise<ToolResult>;
38
+ declare function handleInfo(deps: McpServerDeps): Promise<ToolResult>;
39
+ type BuildMcpServerOptions = {
40
+ /** Inject mock dependencies (tests). Defaults to {@link defaultDeps}. */
41
+ deps?: Partial<McpServerDeps>;
42
+ };
43
+ declare function buildMcpServer(options?: BuildMcpServerOptions): McpServer;
44
+ declare function runMcpServer(): Promise<void>;
45
+ declare const __test__: {
46
+ handleCallWorkflow: typeof handleCallWorkflow;
47
+ handleBalance: typeof handleBalance;
48
+ handleInfo: typeof handleInfo;
49
+ defaultDeps: typeof defaultDeps;
50
+ resetProvisionInflightForTests: typeof resetProvisionInflightForTests;
51
+ BODY_TEXT_CAP_BYTES: number;
52
+ };
53
+
54
+ export { type BuildMcpServerOptions, type McpServerDeps, __test__, buildMcpServer, runMcpServer };
@@ -0,0 +1,54 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { W as WalletConfig, S as SafetyConfig, B as BalanceSnapshot, e as PaymentSigner } from './payment-signer-CyeRXcX2.js';
3
+ import 'viem';
4
+
5
+ type ToolContent = {
6
+ type: "text";
7
+ text: string;
8
+ };
9
+ type ToolResult = {
10
+ content: ToolContent[];
11
+ isError?: boolean;
12
+ };
13
+ /**
14
+ * Test seam: every external boundary the handlers touch is collected here so
15
+ * `tests/unit/mcp-server.test.ts` can inject mocks without monkey-patching
16
+ * module globals. Production code passes nothing — defaults bind to the real
17
+ * implementations and the public API of `buildMcpServer()` does not change.
18
+ */
19
+ type McpServerDeps = {
20
+ readWalletConfig: () => Promise<WalletConfig>;
21
+ provisionWallet: () => Promise<WalletConfig>;
22
+ loadSafetyConfig: () => Promise<SafetyConfig>;
23
+ checkBalance: (wallet: WalletConfig) => Promise<BalanceSnapshot>;
24
+ paymentSigner: PaymentSigner;
25
+ fetchImpl: typeof fetch;
26
+ };
27
+ declare function defaultDeps(): McpServerDeps;
28
+ /** Test-only: clear the in-process provision cache between test cases. */
29
+ declare function resetProvisionInflightForTests(): void;
30
+ type CallWorkflowArgs = {
31
+ slug: string;
32
+ body?: Record<string, unknown>;
33
+ paymentHint?: "auto" | "x402" | "mpp";
34
+ responseFormat?: "text" | "base64" | "json";
35
+ };
36
+ declare function handleCallWorkflow(args: CallWorkflowArgs, deps: McpServerDeps): Promise<ToolResult>;
37
+ declare function handleBalance(deps: McpServerDeps): Promise<ToolResult>;
38
+ declare function handleInfo(deps: McpServerDeps): Promise<ToolResult>;
39
+ type BuildMcpServerOptions = {
40
+ /** Inject mock dependencies (tests). Defaults to {@link defaultDeps}. */
41
+ deps?: Partial<McpServerDeps>;
42
+ };
43
+ declare function buildMcpServer(options?: BuildMcpServerOptions): McpServer;
44
+ declare function runMcpServer(): Promise<void>;
45
+ declare const __test__: {
46
+ handleCallWorkflow: typeof handleCallWorkflow;
47
+ handleBalance: typeof handleBalance;
48
+ handleInfo: typeof handleInfo;
49
+ defaultDeps: typeof defaultDeps;
50
+ resetProvisionInflightForTests: typeof resetProvisionInflightForTests;
51
+ BODY_TEXT_CAP_BYTES: number;
52
+ };
53
+
54
+ export { type BuildMcpServerOptions, type McpServerDeps, __test__, buildMcpServer, runMcpServer };