@haven_ai/mcp 0.2.1-alpha.0 → 0.4.0-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/README.md CHANGED
@@ -44,9 +44,12 @@ Create a private JSON file from the values in the Haven agent handoff:
44
44
  `delegate_key` is required. Without it the MCP server cannot sign locally.
45
45
  `account_address` is the name the connector writes since #2908; a file that
46
46
  still says `safe_address` (or `safeAddress`) is read the same way, permanently.
47
- The same holds for the environment: `HAVEN_ACCOUNT_ADDRESS` is read first, and
48
- the older `HAVEN_WALLET_ADDRESS` / `HAVEN_SAFE_ADDRESS` are accepted for one
49
- release (#2914 removes them).
47
+ The environment is **not** the same: only `HAVEN_ACCOUNT_ADDRESS` is read.
48
+ `HAVEN_WALLET_ADDRESS` and `HAVEN_SAFE_ADDRESS` were accepted for one release
49
+ and #2914 removed them, so a machine still configured through either resolves
50
+ no account address at all. The credential-FILE fallback above is permanent —
51
+ a file on disk never rewrites itself — and the environment is not, which is
52
+ the whole difference between the two paragraphs.
50
53
 
51
54
  The Haven connector may also write split credentials:
52
55
 
package/dist/cli.cjs CHANGED
@@ -56,7 +56,6 @@ async function loadCredentialsFromFile(path) {
56
56
  delegateKey,
57
57
  agentId: stringField(raw.agent_id ?? raw.agentId),
58
58
  accountAddress,
59
- safeAddress: accountAddress,
60
59
  delegateAddress: stringField(raw.delegate_address ?? raw.delegateAddress),
61
60
  chainId: numberField(raw.chain_id ?? raw.chainId),
62
61
  network: stringField(raw.network),
@@ -86,13 +85,11 @@ async function loadCredentialsFromSplitFiles(identityPath, signerPath) {
86
85
  identity.agent_id ?? identity.agentId,
87
86
  signer.agent_id ?? signer.agentId
88
87
  ),
89
- ...accountAddressTwins(
90
- matchingStringField(
91
- "account_address",
92
- readAccountAddressField(identity),
93
- readAccountAddressField(signer),
94
- { caseInsensitive: true }
95
- )
88
+ accountAddress: matchingStringField(
89
+ "account_address",
90
+ readAccountAddressField(identity),
91
+ readAccountAddressField(signer),
92
+ { caseInsensitive: true }
96
93
  ),
97
94
  delegateAddress: matchingStringField(
98
95
  "delegate_address",
@@ -147,7 +144,7 @@ function loadCredentialsFromEnv() {
147
144
  apiKey,
148
145
  delegateKey,
149
146
  agentId: stringField(process.env.HAVEN_AGENT_ID),
150
- ...accountAddressTwins(readAccountAddressEnv(process.env)),
147
+ accountAddress: readAccountAddressEnv(process.env),
151
148
  chainId: numberField(process.env.HAVEN_CHAIN_ID),
152
149
  network: stringField(process.env.HAVEN_NETWORK),
153
150
  apiUrl: stringField(process.env.HAVEN_API_URL)
@@ -157,10 +154,22 @@ function readAccountAddressField(raw) {
157
154
  return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
158
155
  }
159
156
  function readAccountAddressEnv(env) {
160
- return stringField(env.HAVEN_ACCOUNT_ADDRESS ?? env.HAVEN_WALLET_ADDRESS ?? env.HAVEN_SAFE_ADDRESS);
161
- }
162
- function accountAddressTwins(address) {
163
- return { accountAddress: address, safeAddress: address };
157
+ const current = stringField(env.HAVEN_ACCOUNT_ADDRESS);
158
+ for (const name of ["HAVEN_WALLET_ADDRESS", "HAVEN_SAFE_ADDRESS"]) {
159
+ const retired = stringField(env[name]);
160
+ if (!retired) continue;
161
+ if (!current) {
162
+ throw new Error(
163
+ `${name} is retired (#2906) \u2014 Haven accounts are addressed as accounts, not Safes. Set HAVEN_ACCOUNT_ADDRESS to the same value. Refusing rather than ignoring it, because an unset account address silently skips the sweep-destination check.`
164
+ );
165
+ }
166
+ if (retired.toLowerCase() !== current.toLowerCase()) {
167
+ throw new Error(
168
+ `${name} and HAVEN_ACCOUNT_ADDRESS were both set to different addresses. Remove the retired name, or make them match \u2014 picking one silently would hide the mismatch.`
169
+ );
170
+ }
171
+ }
172
+ return current;
164
173
  }
165
174
  function stringField(value) {
166
175
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
@@ -295,11 +304,14 @@ var toolSchemas = {
295
304
  website: v3.z.string().optional()
296
305
  },
297
306
  haven_list_receipts: {
298
- limit: v3.z.number().int().min(1).max(100).optional()
307
+ limit: v3.z.number().int().min(1).max(100).optional(),
308
+ /** #3128: the previous page's next_cursor (a receipt id). */
309
+ cursor: v3.z.string().min(1).optional()
299
310
  },
300
311
  haven_verify_receipt: {
301
312
  receipt: v3.z.unknown()
302
313
  }
314
+ // #3101: keys survive on the type (see the hosted server's contracts.ts).
303
315
  };
304
316
  var toolDescriptions = {
305
317
  haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
@@ -490,12 +502,32 @@ function createToolHandlers(haven) {
490
502
  source: entry.source,
491
503
  domain_verified: entry.domainVerified,
492
504
  verified_payable: entry.verifiedPayable,
493
- // Which Haven pay tool reaches this entry from the local MCP surface.
505
+ // Which Haven pay tool reaches this entry from the local MCP surface,
506
+ // and — #3100 (epic #3105, decision 4) — the arguments that tool
507
+ // accepts VERBATIM, spelled in ITS vocabulary (`merchant_url`, not
508
+ // the entry's `resource_url`). The local surface keeps pointing at
509
+ // its pay tools: they need no cap, so a verbatim hint exists.
494
510
  // #1328: the 'mpp' rail's only-ever catalog row (the Haven MPP demo
495
511
  // resource) is delisted with the mpp_demo retirement, so this
496
512
  // fallback is unreachable today; it stays x402 rather than naming a
497
513
  // deleted tool in case a future non-demo 'mpp' rail entry appears.
498
- suggested_tool: entry.protocol === "mcp" ? "haven_pay_mcp_tool" : "haven_pay_x402"
514
+ // A row without a tool_name cannot get a verbatim hint —
515
+ // haven_pay_mcp_tool requires tool_name — so it gets the REASON
516
+ // instead of a hint its own tool refuses (haven-reviewer on #3113;
517
+ // the epic's "omitted + reason, never null" shape, decision 3).
518
+ ...entry.protocol === "mcp" ? entry.toolName ? {
519
+ suggested_tool: "haven_pay_mcp_tool",
520
+ suggested_arguments: {
521
+ merchant_url: entry.resourceUrl,
522
+ tool_name: entry.toolName,
523
+ ...entry.toolArguments ? { arguments: entry.toolArguments } : {}
524
+ }
525
+ } : {
526
+ suggested_tool_omitted_reason: "this catalog row carries no tool_name, which haven_pay_mcp_tool requires; read the merchant's tool list yourself, then call haven_pay_mcp_tool with merchant_url, tool_name and arguments"
527
+ } : {
528
+ suggested_tool: "haven_pay_x402",
529
+ suggested_arguments: { url: entry.resourceUrl }
530
+ }
499
531
  }));
500
532
  });
501
533
  },
@@ -515,7 +547,7 @@ function createToolHandlers(haven) {
515
547
  },
516
548
  haven_list_receipts: async (input) => {
517
549
  const args = objectInput("haven_list_receipts", input);
518
- return runTool(async () => haven.listReceipts({ limit: args.limit }));
550
+ return runTool(async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor }));
519
551
  },
520
552
  haven_verify_receipt: async (input) => {
521
553
  const args = objectInput("haven_verify_receipt", input);
@@ -660,7 +692,11 @@ function normalizeError(err) {
660
692
  message: err.message,
661
693
  statusCode: err.statusCode,
662
694
  nextAction: err.nextAction,
663
- retry_with_new_quote: err.retryWithNewQuote
695
+ next_action: err.nextAction,
696
+ retry_with_new_quote: err.retryWithNewQuote,
697
+ // #3103: the local runtime's one decision site — no tool can act until
698
+ // the merchant recovers; the message carries retry_after_s.
699
+ next_tool_omitted_reason: "the merchant needs to recover first; re-quote after the retry_after_s in the message"
664
700
  };
665
701
  }
666
702
  if (err instanceof sdk.HavenPaymentStateError) {
@@ -673,6 +709,7 @@ function normalizeError(err) {
673
709
  status: err.status,
674
710
  phase: err.phase,
675
711
  nextAction: err.nextAction,
712
+ next_action: err.nextAction,
676
713
  resume_state: err.resumeState,
677
714
  body: err.body
678
715
  };
@@ -694,6 +731,7 @@ function normalizeError(err) {
694
731
  paymentId: err.paymentId,
695
732
  phase: stringOrUndefined(body?.phase),
696
733
  nextAction: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? sdk.AgentPaymentNextAction.StopAndTellUser,
734
+ next_action: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? sdk.AgentPaymentNextAction.StopAndTellUser,
697
735
  body: err.body
698
736
  };
699
737
  }
@@ -710,7 +748,11 @@ function normalizeError(err) {
710
748
  success: false,
711
749
  code: "UNKNOWN_ERROR",
712
750
  message: err instanceof Error ? err.message : String(err),
713
- nextAction: sdk.AgentPaymentNextAction.StopAndTellUser
751
+ nextAction: sdk.AgentPaymentNextAction.StopAndTellUser,
752
+ next_action: sdk.AgentPaymentNextAction.StopAndTellUser,
753
+ // #3103: the second local decision site — nothing structured can follow an
754
+ // error this runtime did not recognise.
755
+ next_tool_omitted_reason: "an error this runtime does not recognise; tell the user what the message says"
714
756
  };
715
757
  }
716
758
  function stringOrUndefined(value) {
@@ -723,7 +765,7 @@ function computeConsentHash(input) {
723
765
  input.apiKeyPrefix,
724
766
  input.apiUrl ?? "",
725
767
  input.agentId ?? "",
726
- (input.safeAddress ?? "").toLowerCase(),
768
+ (input.accountAddress ?? "").toLowerCase(),
727
769
  (input.delegateAddress ?? "").toLowerCase(),
728
770
  input.chainId ?? ""
729
771
  ].join("|");
@@ -742,7 +784,7 @@ function renderConsentBlock(input, hash) {
742
784
  ];
743
785
  if (input.apiUrl) lines.push(`Haven API: ${input.apiUrl}`);
744
786
  if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
745
- if (input.safeAddress) lines.push(`Haven wallet (Safe): ${input.safeAddress}`);
787
+ if (input.accountAddress) lines.push(`Haven wallet (Safe): ${input.accountAddress}`);
746
788
  if (input.delegateAddress) lines.push(`Delegate (local signer): ${input.delegateAddress}`);
747
789
  if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
748
790
  lines.push("");
@@ -849,14 +891,14 @@ async function writeAckFile(path$1, hash) {
849
891
  }
850
892
  async function consentInputFromClient(haven, seed, toolNames) {
851
893
  let allowanceSummary = seed.allowanceSummary ?? [];
852
- let safeAddress = seed.safeAddress;
894
+ let accountAddress = seed.accountAddress;
853
895
  let delegateAddress = seed.delegateAddress;
854
896
  let chainId = seed.chainId;
855
897
  try {
856
898
  const summary = await haven.getAllowances();
857
899
  const list = isAllowanceSummary(summary) ? summary.allowances : Array.isArray(summary) ? summary : [];
858
900
  if (isAllowanceSummary(summary)) {
859
- safeAddress = summary.accountAddress ?? summary.safeAddress ?? safeAddress;
901
+ accountAddress = summary.accountAddress ?? accountAddress;
860
902
  delegateAddress = summary.delegateAddress;
861
903
  chainId = typeof summary.chainId === "number" ? summary.chainId : chainId;
862
904
  }
@@ -872,7 +914,7 @@ async function consentInputFromClient(haven, seed, toolNames) {
872
914
  apiKeyPrefix: derivePrefix(seed.apiKey),
873
915
  apiUrl: seed.apiUrl,
874
916
  agentId: seed.agentId,
875
- safeAddress,
917
+ accountAddress,
876
918
  delegateAddress,
877
919
  chainId,
878
920
  toolNames,
@@ -906,7 +948,7 @@ async function resolveHavenClient(options = {}) {
906
948
  return { client, credentials };
907
949
  }
908
950
  var MCP_NAME = "@haven_ai/mcp";
909
- var MCP_VERSION = "0.2.1-alpha.0";
951
+ var MCP_VERSION = "0.4.0-alpha.0";
910
952
  var MCP_INSTRUCTIONS = [
911
953
  "Haven local MCP server: signs in-process with the delegate key it holds on",
912
954
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",
@@ -915,7 +957,8 @@ var MCP_INSTRUCTIONS = [
915
957
  "",
916
958
  "To pay: haven_discover_tools to find a payable service, then",
917
959
  "haven_pay_mcp_tool for MCP merchants or haven_pay_x402 for any x402",
918
- "paywall. Tool responses carry nextAction \u2014 follow it; description prose",
960
+ "paywall. Tool responses carry next_action (nextAction is the same value, kept one more",
961
+ "release) and, when no tool follows, next_tool_omitted_reason \u2014 follow them; description prose",
919
962
  "is fallback, not the source of truth.",
920
963
  "",
921
964
  "A payment outside the agent budget is declined before any money moves. Haven",
@@ -979,7 +1022,7 @@ async function runConsentGate(haven, credentials, options) {
979
1022
  apiKey: credentials.apiKey,
980
1023
  apiUrl: credentials.apiUrl,
981
1024
  agentId: credentials.agentId,
982
- safeAddress: credentials.accountAddress ?? credentials.safeAddress,
1025
+ accountAddress: credentials.accountAddress,
983
1026
  delegateAddress: credentials.delegateAddress,
984
1027
  chainId: credentials.chainId,
985
1028
  allowanceSummary: credentials.allowanceSummary