@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/dist/index.d.cts CHANGED
@@ -6,20 +6,8 @@ interface HavenCredentialFile {
6
6
  apiKey: string;
7
7
  delegateKey: string;
8
8
  agentId?: string;
9
- /**
10
- * The Haven account (smart account) the agent spends from — #2908, the
11
- * account-vocabulary name, added EXPLICITLY here (this shape and the
12
- * signer's are separate types; the epic's review found they must not be
13
- * assumed to match). Same value as `safeAddress`.
14
- */
9
+ /** The Haven account (smart account) the agent spends from. */
15
10
  accountAddress?: string;
16
- /**
17
- * @deprecated #2908 — same value as {@link HavenCredentialFile.accountAddress}.
18
- * Removed from this shape in the release after the one carrying #2908
19
- * (#2914). The credential-FILE keys are read permanently — see
20
- * `readAccountAddressField`.
21
- */
22
- safeAddress?: string;
23
11
  delegateAddress?: string;
24
12
  chainId?: number;
25
13
  network?: string;
@@ -55,9 +43,9 @@ interface HavenCredentialSource {
55
43
  * 1. Explicit `path` argument (typically from `--credentials <path>`).
56
44
  * 2. `HAVEN_CREDENTIALS` env var pointing at a credential JSON file.
57
45
  * 3. Inline env vars: `HAVEN_API_KEY` + `HAVEN_DELEGATE_KEY` (+ optional
58
- * `HAVEN_AGENT_ID`, `HAVEN_ACCOUNT_ADDRESS` — or, until #2914, its
59
- * pre-#2908 spellings `HAVEN_WALLET_ADDRESS` / `HAVEN_SAFE_ADDRESS` —
60
- * and `HAVEN_API_URL`).
46
+ * `HAVEN_AGENT_ID`, `HAVEN_ACCOUNT_ADDRESS` and `HAVEN_API_URL`). The
47
+ * pre-#2908 spellings `HAVEN_WALLET_ADDRESS` / `HAVEN_SAFE_ADDRESS` are
48
+ * retired as of #2914.
61
49
  *
62
50
  * The inline-env path exists so that runtime config snippets emitted by the
63
51
  * Haven dashboard (Claude Desktop / Cursor / generic MCP configs) can be a
@@ -79,7 +67,79 @@ declare function loadCredentials(source?: string | HavenCredentialSource | undef
79
67
  */
80
68
 
81
69
  type HavenMcpToolName = 'haven_send' | 'haven_pay_mcp_tool' | 'haven_quote_x402' | 'haven_pay_x402_quote' | 'haven_pay_x402' | 'haven_resume_x402_payment' | 'haven_get_payment_status' | 'haven_get_resume_state' | 'haven_get_agent' | 'haven_get_allowances' | 'haven_list_receipts' | 'haven_verify_receipt' | 'haven_sweep_delegate' | 'haven_discover_tools' | 'haven_submit_catalog_entry';
82
- declare const toolSchemas: Record<HavenMcpToolName, z.ZodRawShape>;
70
+ declare const toolSchemas: {
71
+ readonly haven_send: {
72
+ readonly asset: z.ZodEnum<["ETH", "USDC"]>;
73
+ readonly recipient: z.ZodString;
74
+ readonly amount: z.ZodString;
75
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
76
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
77
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
78
+ };
79
+ readonly haven_pay_mcp_tool: {
80
+ readonly merchant_url: z.ZodString;
81
+ readonly tool_name: z.ZodString;
82
+ readonly arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
83
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
84
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
85
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
86
+ };
87
+ readonly haven_quote_x402: {
88
+ readonly url: z.ZodString;
89
+ readonly method: z.ZodOptional<z.ZodString>;
90
+ readonly headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
91
+ readonly body: z.ZodOptional<z.ZodString>;
92
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
93
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
94
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
95
+ };
96
+ readonly haven_pay_x402_quote: {
97
+ readonly quote: z.ZodUnknown;
98
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
99
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
100
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
101
+ };
102
+ readonly haven_pay_x402: {
103
+ readonly url: z.ZodString;
104
+ readonly method: z.ZodOptional<z.ZodString>;
105
+ readonly headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
106
+ readonly body: z.ZodOptional<z.ZodString>;
107
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
108
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
109
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
110
+ };
111
+ readonly haven_resume_x402_payment: {
112
+ readonly payment_id: z.ZodOptional<z.ZodString>;
113
+ readonly resume_state: z.ZodOptional<z.ZodUnknown>;
114
+ };
115
+ readonly haven_get_payment_status: {
116
+ readonly payment_id: z.ZodString;
117
+ };
118
+ readonly haven_get_resume_state: {
119
+ readonly payment_id: z.ZodString;
120
+ };
121
+ readonly haven_get_agent: {};
122
+ readonly haven_get_allowances: {};
123
+ readonly haven_sweep_delegate: {};
124
+ readonly haven_discover_tools: {
125
+ readonly category: z.ZodOptional<z.ZodString>;
126
+ readonly search: z.ZodOptional<z.ZodString>;
127
+ readonly rail: z.ZodOptional<z.ZodEnum<["x402", "mpp"]>>;
128
+ readonly verified: z.ZodOptional<z.ZodEnum<["any", "verified", "operator"]>>;
129
+ };
130
+ readonly haven_submit_catalog_entry: {
131
+ readonly resource_url: z.ZodString;
132
+ readonly website: z.ZodOptional<z.ZodString>;
133
+ };
134
+ readonly haven_list_receipts: {
135
+ readonly limit: z.ZodOptional<z.ZodNumber>;
136
+ /** #3128: the previous page's next_cursor (a receipt id). */
137
+ readonly cursor: z.ZodOptional<z.ZodString>;
138
+ };
139
+ readonly haven_verify_receipt: {
140
+ readonly receipt: z.ZodUnknown;
141
+ };
142
+ };
83
143
  /**
84
144
  * MCP tool descriptions, composed from the shared semantic source in
85
145
  * `@haven_ai/sdk`'s `tool-descriptions.ts`. Keeping both the SDK tool-calling
@@ -110,7 +170,25 @@ interface ToolFailure {
110
170
  paymentId?: string;
111
171
  status?: string;
112
172
  phase?: string;
173
+ /**
174
+ * @deprecated since #3103 — read `next_action`. Kept with the same value for
175
+ * one release (the #2908 pattern) and removed in the release after the one
176
+ * carrying #3103.
177
+ */
113
178
  nextAction?: string;
179
+ /**
180
+ * #3103 (epic #3105, decision 10): the same value as `nextAction`, spelled
181
+ * the way the hosted server and the signer spell it. Dual-emitted for one
182
+ * release (the #2908 pattern) before `nextAction` is dropped.
183
+ */
184
+ next_action?: string;
185
+ /** #3101 (epic #3105, decision 7): the typed next-step family, additive; `next_tool` never null. */
186
+ next_tool?: string;
187
+ next_tool_server?: string;
188
+ next_tool_name?: string;
189
+ next_tool_server_role?: 'hosted' | 'signer';
190
+ next_arguments?: Record<string, unknown>;
191
+ next_tool_omitted_reason?: string;
114
192
  resume_state?: unknown;
115
193
  body?: unknown;
116
194
  /**
@@ -162,7 +240,7 @@ interface ConsentInput {
162
240
  /** Agent identity from the credential file, when present. */
163
241
  agentId?: string;
164
242
  /** Haven wallet (Safe) the agent spends from. */
165
- safeAddress?: string;
243
+ accountAddress?: string;
166
244
  /** Agent's delegate EOA — the local signer. */
167
245
  delegateAddress?: string;
168
246
  /** Chain the agent operates on. */
@@ -202,8 +280,8 @@ interface CredentialIdentitySeed {
202
280
  apiKey: string;
203
281
  apiUrl?: string;
204
282
  agentId?: string;
205
- /** Safe address from the credential file, used as a fallback. */
206
- safeAddress?: string;
283
+ /** Account address from the credential file, used as a fallback. */
284
+ accountAddress?: string;
207
285
  /** Delegate address from the credential file, used before live allowance metadata is available. */
208
286
  delegateAddress?: string;
209
287
  /** Chain from the credential file, used as a fallback. */
@@ -267,7 +345,7 @@ declare function createHavenMcpServer(options?: HavenMcpServerOptions): Promise<
267
345
  * `agent_tool_invocations` rows are always attributed to the right tool.
268
346
  */
269
347
  declare const MCP_NAME = "@haven_ai/mcp";
270
- declare const MCP_VERSION = "0.2.1-alpha.0";
348
+ declare const MCP_VERSION = "0.4.0-alpha.0";
271
349
  /**
272
350
  * Refuse to start on an unsupported Node (#1161).
273
351
  *
package/dist/index.d.ts CHANGED
@@ -6,20 +6,8 @@ interface HavenCredentialFile {
6
6
  apiKey: string;
7
7
  delegateKey: string;
8
8
  agentId?: string;
9
- /**
10
- * The Haven account (smart account) the agent spends from — #2908, the
11
- * account-vocabulary name, added EXPLICITLY here (this shape and the
12
- * signer's are separate types; the epic's review found they must not be
13
- * assumed to match). Same value as `safeAddress`.
14
- */
9
+ /** The Haven account (smart account) the agent spends from. */
15
10
  accountAddress?: string;
16
- /**
17
- * @deprecated #2908 — same value as {@link HavenCredentialFile.accountAddress}.
18
- * Removed from this shape in the release after the one carrying #2908
19
- * (#2914). The credential-FILE keys are read permanently — see
20
- * `readAccountAddressField`.
21
- */
22
- safeAddress?: string;
23
11
  delegateAddress?: string;
24
12
  chainId?: number;
25
13
  network?: string;
@@ -55,9 +43,9 @@ interface HavenCredentialSource {
55
43
  * 1. Explicit `path` argument (typically from `--credentials <path>`).
56
44
  * 2. `HAVEN_CREDENTIALS` env var pointing at a credential JSON file.
57
45
  * 3. Inline env vars: `HAVEN_API_KEY` + `HAVEN_DELEGATE_KEY` (+ optional
58
- * `HAVEN_AGENT_ID`, `HAVEN_ACCOUNT_ADDRESS` — or, until #2914, its
59
- * pre-#2908 spellings `HAVEN_WALLET_ADDRESS` / `HAVEN_SAFE_ADDRESS` —
60
- * and `HAVEN_API_URL`).
46
+ * `HAVEN_AGENT_ID`, `HAVEN_ACCOUNT_ADDRESS` and `HAVEN_API_URL`). The
47
+ * pre-#2908 spellings `HAVEN_WALLET_ADDRESS` / `HAVEN_SAFE_ADDRESS` are
48
+ * retired as of #2914.
61
49
  *
62
50
  * The inline-env path exists so that runtime config snippets emitted by the
63
51
  * Haven dashboard (Claude Desktop / Cursor / generic MCP configs) can be a
@@ -79,7 +67,79 @@ declare function loadCredentials(source?: string | HavenCredentialSource | undef
79
67
  */
80
68
 
81
69
  type HavenMcpToolName = 'haven_send' | 'haven_pay_mcp_tool' | 'haven_quote_x402' | 'haven_pay_x402_quote' | 'haven_pay_x402' | 'haven_resume_x402_payment' | 'haven_get_payment_status' | 'haven_get_resume_state' | 'haven_get_agent' | 'haven_get_allowances' | 'haven_list_receipts' | 'haven_verify_receipt' | 'haven_sweep_delegate' | 'haven_discover_tools' | 'haven_submit_catalog_entry';
82
- declare const toolSchemas: Record<HavenMcpToolName, z.ZodRawShape>;
70
+ declare const toolSchemas: {
71
+ readonly haven_send: {
72
+ readonly asset: z.ZodEnum<["ETH", "USDC"]>;
73
+ readonly recipient: z.ZodString;
74
+ readonly amount: z.ZodString;
75
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
76
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
77
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
78
+ };
79
+ readonly haven_pay_mcp_tool: {
80
+ readonly merchant_url: z.ZodString;
81
+ readonly tool_name: z.ZodString;
82
+ readonly arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
83
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
84
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
85
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
86
+ };
87
+ readonly haven_quote_x402: {
88
+ readonly url: z.ZodString;
89
+ readonly method: z.ZodOptional<z.ZodString>;
90
+ readonly headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
91
+ readonly body: z.ZodOptional<z.ZodString>;
92
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
93
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
94
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
95
+ };
96
+ readonly haven_pay_x402_quote: {
97
+ readonly quote: z.ZodUnknown;
98
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
99
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
100
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
101
+ };
102
+ readonly haven_pay_x402: {
103
+ readonly url: z.ZodString;
104
+ readonly method: z.ZodOptional<z.ZodString>;
105
+ readonly headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
106
+ readonly body: z.ZodOptional<z.ZodString>;
107
+ readonly idempotency_key: z.ZodOptional<z.ZodString>;
108
+ /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
109
+ readonly idempotencyKey: z.ZodOptional<z.ZodString>;
110
+ };
111
+ readonly haven_resume_x402_payment: {
112
+ readonly payment_id: z.ZodOptional<z.ZodString>;
113
+ readonly resume_state: z.ZodOptional<z.ZodUnknown>;
114
+ };
115
+ readonly haven_get_payment_status: {
116
+ readonly payment_id: z.ZodString;
117
+ };
118
+ readonly haven_get_resume_state: {
119
+ readonly payment_id: z.ZodString;
120
+ };
121
+ readonly haven_get_agent: {};
122
+ readonly haven_get_allowances: {};
123
+ readonly haven_sweep_delegate: {};
124
+ readonly haven_discover_tools: {
125
+ readonly category: z.ZodOptional<z.ZodString>;
126
+ readonly search: z.ZodOptional<z.ZodString>;
127
+ readonly rail: z.ZodOptional<z.ZodEnum<["x402", "mpp"]>>;
128
+ readonly verified: z.ZodOptional<z.ZodEnum<["any", "verified", "operator"]>>;
129
+ };
130
+ readonly haven_submit_catalog_entry: {
131
+ readonly resource_url: z.ZodString;
132
+ readonly website: z.ZodOptional<z.ZodString>;
133
+ };
134
+ readonly haven_list_receipts: {
135
+ readonly limit: z.ZodOptional<z.ZodNumber>;
136
+ /** #3128: the previous page's next_cursor (a receipt id). */
137
+ readonly cursor: z.ZodOptional<z.ZodString>;
138
+ };
139
+ readonly haven_verify_receipt: {
140
+ readonly receipt: z.ZodUnknown;
141
+ };
142
+ };
83
143
  /**
84
144
  * MCP tool descriptions, composed from the shared semantic source in
85
145
  * `@haven_ai/sdk`'s `tool-descriptions.ts`. Keeping both the SDK tool-calling
@@ -110,7 +170,25 @@ interface ToolFailure {
110
170
  paymentId?: string;
111
171
  status?: string;
112
172
  phase?: string;
173
+ /**
174
+ * @deprecated since #3103 — read `next_action`. Kept with the same value for
175
+ * one release (the #2908 pattern) and removed in the release after the one
176
+ * carrying #3103.
177
+ */
113
178
  nextAction?: string;
179
+ /**
180
+ * #3103 (epic #3105, decision 10): the same value as `nextAction`, spelled
181
+ * the way the hosted server and the signer spell it. Dual-emitted for one
182
+ * release (the #2908 pattern) before `nextAction` is dropped.
183
+ */
184
+ next_action?: string;
185
+ /** #3101 (epic #3105, decision 7): the typed next-step family, additive; `next_tool` never null. */
186
+ next_tool?: string;
187
+ next_tool_server?: string;
188
+ next_tool_name?: string;
189
+ next_tool_server_role?: 'hosted' | 'signer';
190
+ next_arguments?: Record<string, unknown>;
191
+ next_tool_omitted_reason?: string;
114
192
  resume_state?: unknown;
115
193
  body?: unknown;
116
194
  /**
@@ -162,7 +240,7 @@ interface ConsentInput {
162
240
  /** Agent identity from the credential file, when present. */
163
241
  agentId?: string;
164
242
  /** Haven wallet (Safe) the agent spends from. */
165
- safeAddress?: string;
243
+ accountAddress?: string;
166
244
  /** Agent's delegate EOA — the local signer. */
167
245
  delegateAddress?: string;
168
246
  /** Chain the agent operates on. */
@@ -202,8 +280,8 @@ interface CredentialIdentitySeed {
202
280
  apiKey: string;
203
281
  apiUrl?: string;
204
282
  agentId?: string;
205
- /** Safe address from the credential file, used as a fallback. */
206
- safeAddress?: string;
283
+ /** Account address from the credential file, used as a fallback. */
284
+ accountAddress?: string;
207
285
  /** Delegate address from the credential file, used before live allowance metadata is available. */
208
286
  delegateAddress?: string;
209
287
  /** Chain from the credential file, used as a fallback. */
@@ -267,7 +345,7 @@ declare function createHavenMcpServer(options?: HavenMcpServerOptions): Promise<
267
345
  * `agent_tool_invocations` rows are always attributed to the right tool.
268
346
  */
269
347
  declare const MCP_NAME = "@haven_ai/mcp";
270
- declare const MCP_VERSION = "0.2.1-alpha.0";
348
+ declare const MCP_VERSION = "0.4.0-alpha.0";
271
349
  /**
272
350
  * Refuse to start on an unsupported Node (#1161).
273
351
  *
package/dist/index.js CHANGED
@@ -54,7 +54,6 @@ async function loadCredentialsFromFile(path) {
54
54
  delegateKey,
55
55
  agentId: stringField(raw.agent_id ?? raw.agentId),
56
56
  accountAddress,
57
- safeAddress: accountAddress,
58
57
  delegateAddress: stringField(raw.delegate_address ?? raw.delegateAddress),
59
58
  chainId: numberField(raw.chain_id ?? raw.chainId),
60
59
  network: stringField(raw.network),
@@ -84,13 +83,11 @@ async function loadCredentialsFromSplitFiles(identityPath, signerPath) {
84
83
  identity.agent_id ?? identity.agentId,
85
84
  signer.agent_id ?? signer.agentId
86
85
  ),
87
- ...accountAddressTwins(
88
- matchingStringField(
89
- "account_address",
90
- readAccountAddressField(identity),
91
- readAccountAddressField(signer),
92
- { caseInsensitive: true }
93
- )
86
+ accountAddress: matchingStringField(
87
+ "account_address",
88
+ readAccountAddressField(identity),
89
+ readAccountAddressField(signer),
90
+ { caseInsensitive: true }
94
91
  ),
95
92
  delegateAddress: matchingStringField(
96
93
  "delegate_address",
@@ -145,7 +142,7 @@ function loadCredentialsFromEnv() {
145
142
  apiKey,
146
143
  delegateKey,
147
144
  agentId: stringField(process.env.HAVEN_AGENT_ID),
148
- ...accountAddressTwins(readAccountAddressEnv(process.env)),
145
+ accountAddress: readAccountAddressEnv(process.env),
149
146
  chainId: numberField(process.env.HAVEN_CHAIN_ID),
150
147
  network: stringField(process.env.HAVEN_NETWORK),
151
148
  apiUrl: stringField(process.env.HAVEN_API_URL)
@@ -155,10 +152,22 @@ function readAccountAddressField(raw) {
155
152
  return stringField(raw.account_address ?? raw.safe_address ?? raw.safeAddress);
156
153
  }
157
154
  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 };
155
+ const current = stringField(env.HAVEN_ACCOUNT_ADDRESS);
156
+ for (const name of ["HAVEN_WALLET_ADDRESS", "HAVEN_SAFE_ADDRESS"]) {
157
+ const retired = stringField(env[name]);
158
+ if (!retired) continue;
159
+ if (!current) {
160
+ throw new Error(
161
+ `${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.`
162
+ );
163
+ }
164
+ if (retired.toLowerCase() !== current.toLowerCase()) {
165
+ throw new Error(
166
+ `${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.`
167
+ );
168
+ }
169
+ }
170
+ return current;
162
171
  }
163
172
  function stringField(value) {
164
173
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
@@ -293,11 +302,14 @@ var toolSchemas = {
293
302
  website: z.string().optional()
294
303
  },
295
304
  haven_list_receipts: {
296
- limit: z.number().int().min(1).max(100).optional()
305
+ limit: z.number().int().min(1).max(100).optional(),
306
+ /** #3128: the previous page's next_cursor (a receipt id). */
307
+ cursor: z.string().min(1).optional()
297
308
  },
298
309
  haven_verify_receipt: {
299
310
  receipt: z.unknown()
300
311
  }
312
+ // #3101: keys survive on the type (see the hosted server's contracts.ts).
301
313
  };
302
314
  var toolDescriptions = {
303
315
  haven_send: composeDescription(toolDescriptions$1.send),
@@ -488,12 +500,32 @@ function createToolHandlers(haven) {
488
500
  source: entry.source,
489
501
  domain_verified: entry.domainVerified,
490
502
  verified_payable: entry.verifiedPayable,
491
- // Which Haven pay tool reaches this entry from the local MCP surface.
503
+ // Which Haven pay tool reaches this entry from the local MCP surface,
504
+ // and — #3100 (epic #3105, decision 4) — the arguments that tool
505
+ // accepts VERBATIM, spelled in ITS vocabulary (`merchant_url`, not
506
+ // the entry's `resource_url`). The local surface keeps pointing at
507
+ // its pay tools: they need no cap, so a verbatim hint exists.
492
508
  // #1328: the 'mpp' rail's only-ever catalog row (the Haven MPP demo
493
509
  // resource) is delisted with the mpp_demo retirement, so this
494
510
  // fallback is unreachable today; it stays x402 rather than naming a
495
511
  // deleted tool in case a future non-demo 'mpp' rail entry appears.
496
- suggested_tool: entry.protocol === "mcp" ? "haven_pay_mcp_tool" : "haven_pay_x402"
512
+ // A row without a tool_name cannot get a verbatim hint —
513
+ // haven_pay_mcp_tool requires tool_name — so it gets the REASON
514
+ // instead of a hint its own tool refuses (haven-reviewer on #3113;
515
+ // the epic's "omitted + reason, never null" shape, decision 3).
516
+ ...entry.protocol === "mcp" ? entry.toolName ? {
517
+ suggested_tool: "haven_pay_mcp_tool",
518
+ suggested_arguments: {
519
+ merchant_url: entry.resourceUrl,
520
+ tool_name: entry.toolName,
521
+ ...entry.toolArguments ? { arguments: entry.toolArguments } : {}
522
+ }
523
+ } : {
524
+ 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"
525
+ } : {
526
+ suggested_tool: "haven_pay_x402",
527
+ suggested_arguments: { url: entry.resourceUrl }
528
+ }
497
529
  }));
498
530
  });
499
531
  },
@@ -513,7 +545,7 @@ function createToolHandlers(haven) {
513
545
  },
514
546
  haven_list_receipts: async (input) => {
515
547
  const args = objectInput("haven_list_receipts", input);
516
- return runTool(async () => haven.listReceipts({ limit: args.limit }));
548
+ return runTool(async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor }));
517
549
  },
518
550
  haven_verify_receipt: async (input) => {
519
551
  const args = objectInput("haven_verify_receipt", input);
@@ -658,7 +690,11 @@ function normalizeError(err) {
658
690
  message: err.message,
659
691
  statusCode: err.statusCode,
660
692
  nextAction: err.nextAction,
661
- retry_with_new_quote: err.retryWithNewQuote
693
+ next_action: err.nextAction,
694
+ retry_with_new_quote: err.retryWithNewQuote,
695
+ // #3103: the local runtime's one decision site — no tool can act until
696
+ // the merchant recovers; the message carries retry_after_s.
697
+ next_tool_omitted_reason: "the merchant needs to recover first; re-quote after the retry_after_s in the message"
662
698
  };
663
699
  }
664
700
  if (err instanceof HavenPaymentStateError) {
@@ -671,6 +707,7 @@ function normalizeError(err) {
671
707
  status: err.status,
672
708
  phase: err.phase,
673
709
  nextAction: err.nextAction,
710
+ next_action: err.nextAction,
674
711
  resume_state: err.resumeState,
675
712
  body: err.body
676
713
  };
@@ -692,6 +729,7 @@ function normalizeError(err) {
692
729
  paymentId: err.paymentId,
693
730
  phase: stringOrUndefined(body?.phase),
694
731
  nextAction: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? AgentPaymentNextAction.StopAndTellUser,
732
+ next_action: stringOrUndefined(body?.nextAction) ?? stringOrUndefined(body?.next_action) ?? AgentPaymentNextAction.StopAndTellUser,
695
733
  body: err.body
696
734
  };
697
735
  }
@@ -708,7 +746,11 @@ function normalizeError(err) {
708
746
  success: false,
709
747
  code: "UNKNOWN_ERROR",
710
748
  message: err instanceof Error ? err.message : String(err),
711
- nextAction: AgentPaymentNextAction.StopAndTellUser
749
+ nextAction: AgentPaymentNextAction.StopAndTellUser,
750
+ next_action: AgentPaymentNextAction.StopAndTellUser,
751
+ // #3103: the second local decision site — nothing structured can follow an
752
+ // error this runtime did not recognise.
753
+ next_tool_omitted_reason: "an error this runtime does not recognise; tell the user what the message says"
712
754
  };
713
755
  }
714
756
  function stringOrUndefined(value) {
@@ -721,7 +763,7 @@ function computeConsentHash(input) {
721
763
  input.apiKeyPrefix,
722
764
  input.apiUrl ?? "",
723
765
  input.agentId ?? "",
724
- (input.safeAddress ?? "").toLowerCase(),
766
+ (input.accountAddress ?? "").toLowerCase(),
725
767
  (input.delegateAddress ?? "").toLowerCase(),
726
768
  input.chainId ?? ""
727
769
  ].join("|");
@@ -740,7 +782,7 @@ function renderConsentBlock(input, hash) {
740
782
  ];
741
783
  if (input.apiUrl) lines.push(`Haven API: ${input.apiUrl}`);
742
784
  if (input.agentId) lines.push(`Agent ID: ${input.agentId}`);
743
- if (input.safeAddress) lines.push(`Haven wallet (Safe): ${input.safeAddress}`);
785
+ if (input.accountAddress) lines.push(`Haven wallet (Safe): ${input.accountAddress}`);
744
786
  if (input.delegateAddress) lines.push(`Delegate (local signer): ${input.delegateAddress}`);
745
787
  if (typeof input.chainId === "number") lines.push(`Chain ID: ${input.chainId}`);
746
788
  lines.push("");
@@ -847,14 +889,14 @@ async function writeAckFile(path, hash) {
847
889
  }
848
890
  async function consentInputFromClient(haven, seed, toolNames) {
849
891
  let allowanceSummary = seed.allowanceSummary ?? [];
850
- let safeAddress = seed.safeAddress;
892
+ let accountAddress = seed.accountAddress;
851
893
  let delegateAddress = seed.delegateAddress;
852
894
  let chainId = seed.chainId;
853
895
  try {
854
896
  const summary = await haven.getAllowances();
855
897
  const list = isAllowanceSummary(summary) ? summary.allowances : Array.isArray(summary) ? summary : [];
856
898
  if (isAllowanceSummary(summary)) {
857
- safeAddress = summary.accountAddress ?? summary.safeAddress ?? safeAddress;
899
+ accountAddress = summary.accountAddress ?? accountAddress;
858
900
  delegateAddress = summary.delegateAddress;
859
901
  chainId = typeof summary.chainId === "number" ? summary.chainId : chainId;
860
902
  }
@@ -870,7 +912,7 @@ async function consentInputFromClient(haven, seed, toolNames) {
870
912
  apiKeyPrefix: derivePrefix(seed.apiKey),
871
913
  apiUrl: seed.apiUrl,
872
914
  agentId: seed.agentId,
873
- safeAddress,
915
+ accountAddress,
874
916
  delegateAddress,
875
917
  chainId,
876
918
  toolNames,
@@ -912,7 +954,7 @@ async function createHavenMcpServer(options = {}) {
912
954
  return buildMcpServer(haven);
913
955
  }
914
956
  var MCP_NAME = "@haven_ai/mcp";
915
- var MCP_VERSION = "0.2.1-alpha.0";
957
+ var MCP_VERSION = "0.4.0-alpha.0";
916
958
  var MCP_INSTRUCTIONS = [
917
959
  "Haven local MCP server: signs in-process with the delegate key it holds on",
918
960
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",
@@ -921,7 +963,8 @@ var MCP_INSTRUCTIONS = [
921
963
  "",
922
964
  "To pay: haven_discover_tools to find a payable service, then",
923
965
  "haven_pay_mcp_tool for MCP merchants or haven_pay_x402 for any x402",
924
- "paywall. Tool responses carry nextAction \u2014 follow it; description prose",
966
+ "paywall. Tool responses carry next_action (nextAction is the same value, kept one more",
967
+ "release) and, when no tool follows, next_tool_omitted_reason \u2014 follow them; description prose",
925
968
  "is fallback, not the source of truth.",
926
969
  "",
927
970
  "A payment outside the agent budget is declined before any money moves. Haven",
@@ -985,7 +1028,7 @@ async function runConsentGate(haven, credentials, options) {
985
1028
  apiKey: credentials.apiKey,
986
1029
  apiUrl: credentials.apiUrl,
987
1030
  agentId: credentials.agentId,
988
- safeAddress: credentials.accountAddress ?? credentials.safeAddress,
1031
+ accountAddress: credentials.accountAddress,
989
1032
  delegateAddress: credentials.delegateAddress,
990
1033
  chainId: credentials.chainId,
991
1034
  allowanceSummary: credentials.allowanceSummary