@haven_ai/mcp 0.1.37-alpha.0 → 0.2.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { composeDescription, toolDescriptions as toolDescriptions$1, HavenClient, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenPaymentStateError, HavenSigningError, HavenApiError, AgentPaymentNextAction, HavenError, verifyPaymentReceipt, discoverMerchantMcpUrl, sameUrl } from '@haven_ai/sdk';
4
+ import { composeDescription, toolDescriptions as toolDescriptions$1, HavenClient, isSupportedNodeVersion, unsupportedNodeVersionMessage, HavenPaymentStateError, HavenSigningError, HavenApiError, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, verifyPaymentReceipt, discoverMerchantMcpUrl, sameUrl } from '@haven_ai/sdk';
5
5
  import { readFile, mkdir, writeFile, stat } from 'fs/promises';
6
6
  import { z } from 'zod/v3';
7
7
  import { createHash } from 'crypto';
@@ -48,11 +48,13 @@ async function loadCredentialsFromFile(path) {
48
48
  if (!delegateKey) {
49
49
  throw new Error("Haven MCP requires delegate_key so payments can be signed locally.");
50
50
  }
51
+ const accountAddress = readAccountAddressField(raw);
51
52
  return {
52
53
  apiKey,
53
54
  delegateKey,
54
55
  agentId: stringField(raw.agent_id ?? raw.agentId),
55
- safeAddress: stringField(raw.safe_address ?? raw.safeAddress),
56
+ accountAddress,
57
+ safeAddress: accountAddress,
56
58
  delegateAddress: stringField(raw.delegate_address ?? raw.delegateAddress),
57
59
  chainId: numberField(raw.chain_id ?? raw.chainId),
58
60
  network: stringField(raw.network),
@@ -82,11 +84,13 @@ async function loadCredentialsFromSplitFiles(identityPath, signerPath) {
82
84
  identity.agent_id ?? identity.agentId,
83
85
  signer.agent_id ?? signer.agentId
84
86
  ),
85
- safeAddress: matchingStringField(
86
- "safe_address",
87
- identity.safe_address ?? identity.safeAddress,
88
- signer.safe_address ?? signer.safeAddress,
89
- { caseInsensitive: true }
87
+ ...accountAddressTwins(
88
+ matchingStringField(
89
+ "account_address",
90
+ readAccountAddressField(identity),
91
+ readAccountAddressField(signer),
92
+ { caseInsensitive: true }
93
+ )
90
94
  ),
91
95
  delegateAddress: matchingStringField(
92
96
  "delegate_address",
@@ -141,12 +145,21 @@ function loadCredentialsFromEnv() {
141
145
  apiKey,
142
146
  delegateKey,
143
147
  agentId: stringField(process.env.HAVEN_AGENT_ID),
144
- safeAddress: stringField(process.env.HAVEN_SAFE_ADDRESS),
148
+ ...accountAddressTwins(readAccountAddressEnv(process.env)),
145
149
  chainId: numberField(process.env.HAVEN_CHAIN_ID),
146
150
  network: stringField(process.env.HAVEN_NETWORK),
147
151
  apiUrl: stringField(process.env.HAVEN_API_URL)
148
152
  };
149
153
  }
154
+ function readAccountAddressField(raw) {
155
+ return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
156
+ }
157
+ function readAccountAddressEnv(env) {
158
+ return stringField(env.HAVEN_ACCOUNT_ADDRESS ?? env.HAVEN_WALLET_ADDRESS ?? env.HAVEN_SAFE_ADDRESS);
159
+ }
160
+ function accountAddressTwins(address) {
161
+ return { accountAddress: address, safeAddress: address };
162
+ }
150
163
  function stringField(value) {
151
164
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
152
165
  }
@@ -359,6 +372,8 @@ function createToolHandlers(haven) {
359
372
  const attempt = () => haven.fetch(merchantUrl, init, { idempotencyKey });
360
373
  let response = await attempt();
361
374
  if (!response.ok) {
375
+ const notReady = await merchantNotReadyErrorFor(response);
376
+ if (notReady) throw notReady;
362
377
  const discovered = await discoverMerchantMcpUrl(merchantUrl);
363
378
  if (!discovered || sameUrl(discovered, merchantUrl)) {
364
379
  throw discoveryMissError(response, merchantUrl, discovered);
@@ -367,6 +382,8 @@ function createToolHandlers(haven) {
367
382
  merchantUrl = discovered;
368
383
  const retryResponse = await attempt();
369
384
  if (!retryResponse.ok) {
385
+ const notReadyAtDiscovered = await merchantNotReadyErrorFor(retryResponse);
386
+ if (notReadyAtDiscovered) throw notReadyAtDiscovered;
370
387
  throw discoveryMissError(retryResponse, merchantUrl, discovered, inputUrl);
371
388
  }
372
389
  response = retryResponse;
@@ -600,12 +617,50 @@ function parseMaybeJson(text) {
600
617
  return text;
601
618
  }
602
619
  }
620
+ var MerchantNotReadyError = class extends Error {
621
+ code = AgentPaymentFailureCode.MerchantNotReady;
622
+ statusCode = 503;
623
+ nextAction = AgentPaymentNextAction.StopAndTellUser;
624
+ // Genuinely retryable — unlike a rejection, nothing about THIS call was
625
+ // wrong; the merchant's own wallet needs to recover first.
626
+ retryWithNewQuote = true;
627
+ constructor(message) {
628
+ super(message);
629
+ this.name = "MerchantNotReadyError";
630
+ }
631
+ };
632
+ async function merchantNotReadyErrorFor(response) {
633
+ if (response.status !== 503) return null;
634
+ let body;
635
+ try {
636
+ body = await response.clone().json();
637
+ } catch {
638
+ return null;
639
+ }
640
+ if (!body || typeof body !== "object" || body.error !== "merchant_not_ready") {
641
+ return null;
642
+ }
643
+ const { reason_code, settlements_remaining, retry_after_s } = body;
644
+ return new MerchantNotReadyError(
645
+ "The merchant refused this call: it cannot settle a payment right now" + (typeof reason_code === "string" ? ` (reason_code: ${reason_code})` : "") + (typeof settlements_remaining === "number" ? `, settlements_remaining: ${settlements_remaining}` : "") + ". No payment was created." + (typeof retry_after_s === "number" ? ` Retry after approximately ${retry_after_s}s.` : " This is often transient; retry later.")
646
+ );
647
+ }
603
648
  function discoveryMissError(response, merchantUrl, discovered, discoveredFromUrl) {
604
649
  const base = discoveredFromUrl ? `Merchant call to ${merchantUrl} failed with HTTP ${response.status} (at the DISCOVERED endpoint ${merchantUrl}, resolved from ${discoveredFromUrl} via the merchant discovery document).` : `Merchant call to ${merchantUrl} failed with HTTP ${response.status}.`;
605
650
  const guidance = discoveredFromUrl ? "" : discovered ? ` Same-origin discovery resolved the same URL (${discovered}), which still did not answer successfully.` : ` No same-origin discovery document was found at /.well-known/haven-demo-merchant or /. If ${merchantUrl} is a base merchant URL, pass the exact MCP endpoint instead (often <origin>/mcp).`;
606
651
  return new HavenApiError(`${base}${guidance}`, response.status || 400);
607
652
  }
608
653
  function normalizeError(err) {
654
+ if (err instanceof MerchantNotReadyError) {
655
+ return {
656
+ success: false,
657
+ code: err.code,
658
+ message: err.message,
659
+ statusCode: err.statusCode,
660
+ nextAction: err.nextAction,
661
+ retry_with_new_quote: err.retryWithNewQuote
662
+ };
663
+ }
609
664
  if (err instanceof HavenPaymentStateError) {
610
665
  return {
611
666
  success: false,
@@ -799,7 +854,7 @@ async function consentInputFromClient(haven, seed, toolNames) {
799
854
  const summary = await haven.getAllowances();
800
855
  const list = isAllowanceSummary(summary) ? summary.allowances : Array.isArray(summary) ? summary : [];
801
856
  if (isAllowanceSummary(summary)) {
802
- safeAddress = summary.safeAddress ?? safeAddress;
857
+ safeAddress = summary.accountAddress ?? summary.safeAddress ?? safeAddress;
803
858
  delegateAddress = summary.delegateAddress;
804
859
  chainId = typeof summary.chainId === "number" ? summary.chainId : chainId;
805
860
  }
@@ -849,7 +904,7 @@ async function resolveHavenClient(options = {}) {
849
904
  return { client, credentials };
850
905
  }
851
906
  var MCP_NAME = "@haven_ai/mcp";
852
- var MCP_VERSION = "0.1.37-alpha.0";
907
+ var MCP_VERSION = "0.2.1-alpha.0";
853
908
  var MCP_INSTRUCTIONS = [
854
909
  "Haven local MCP server: signs in-process with the delegate key it holds on",
855
910
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",
@@ -922,7 +977,7 @@ async function runConsentGate(haven, credentials, options) {
922
977
  apiKey: credentials.apiKey,
923
978
  apiUrl: credentials.apiUrl,
924
979
  agentId: credentials.agentId,
925
- safeAddress: credentials.safeAddress,
980
+ safeAddress: credentials.accountAddress ?? credentials.safeAddress,
926
981
  delegateAddress: credentials.delegateAddress,
927
982
  chainId: credentials.chainId,
928
983
  allowanceSummary: credentials.allowanceSummary