@haven_ai/mcp 0.6.0-alpha.0 → 0.7.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
@@ -121,21 +121,16 @@ Environment variable form:
121
121
  - `haven_close_task_budget` (#3329)
122
122
  - `haven_submit` (#3329 — relays a local signer signature by `task_budget_id`; a `payment_id` is refused on this runtime, which signs and submits payments inline)
123
123
 
124
- ### `idempotencyKey` is deprecated — send `idempotency_key`
124
+ ### `idempotencyKey` is removed — send `idempotency_key`
125
125
 
126
126
  The tools that take an idempotency key (`haven_send`, `haven_pay_mcp_tool`,
127
- `haven_quote_x402`, `haven_pay_x402_quote`, `haven_pay_x402`) now accept
128
- **`idempotency_key`**, the spelling the hosted Haven MCP surface and every other
129
- Haven wire contract use. `idempotencyKey` still works and returns a deprecation
130
- warning; it will be removed in a future release.
127
+ `haven_quote_x402`, `haven_pay_x402_quote`, `haven_pay_x402`) take
128
+ **`idempotency_key`**, the spelling the hosted Haven MCP surface uses.
131
129
 
132
- Send **one** of them. Sending both with different values is refused
133
- (`AMBIGUOUS_IDEMPOTENCY_KEY`) with nothing contacted or spent — the key decides
134
- whether a retry is the same payment or a second one, so Haven will not guess
135
- which scope you meant. Sending both with the same value is fine.
136
-
137
- The warning arrives in an optional `warnings` array on the success result. It is
138
- additive: a caller that ignores it sees the response it always saw.
130
+ The legacy `idempotencyKey` spelling is **refused**, not silently accepted or
131
+ dropped: any call that sets it — alone or alongside `idempotency_key` — fails
132
+ with `IDEMPOTENCY_KEY_RENAMED` before anything is contacted or spent. Send
133
+ `idempotency_key` instead.
139
134
 
140
135
  ## First-launch consent
141
136
 
@@ -159,7 +154,7 @@ Tools this server will expose to your agent runtime:
159
154
 
160
155
  On-chain budget (the real spend gate — enforced by the agent's
161
156
  signed delegation, not by Haven):
162
- • up to 50.000000 USDC per 1440 min
157
+ • up to 25 USDC per 1440 min
163
158
 
164
159
  Anything above the on-chain budget is declined before any money
165
160
  moves — it is not queued, and no one is asked to review it. If the
package/dist/cli.cjs CHANGED
@@ -238,6 +238,17 @@ async function warnIfCredentialFilePermissive(path, log = (message) => process.s
238
238
  );
239
239
  }
240
240
  }
241
+ var TASK_BUDGET_MIN_SIGNER_VERSION = "0.6.0-alpha.0";
242
+ var SIGNER_CAPABILITY_KEY = "haven/signer-compatibility";
243
+ function taskSignerCompatibilityNotice() {
244
+ return {
245
+ task_sign_context_version: sdk.TASK_SIGN_CONTEXT_VERSION,
246
+ min_signer_version: TASK_BUDGET_MIN_SIGNER_VERSION,
247
+ signer_capability: SIGNER_CAPABILITY_KEY,
248
+ check: `Call next_tool with next_arguments EXACTLY as given \u2014 the signer fetches the exact bytes by task_budget_id. If haven_sign answers SIGNING_ERROR with a message starting "Pass payment_id (preferred for delegation-rail x402", the signer predates task budgets and signed nothing: first close this budget with haven_close_task_budget { task_budget_id } to release it, then update the connector by running \`${sdk.connectorUpgradeCommand()}\` and the repair line it prints, then open the budget again.`,
249
+ fallback: `haven_sign answered SIGNING_ERROR with a message starting "Pass payment_id (preferred for delegation-rail x402" \u2014 the signer predates task budgets and signed nothing: call haven_close_task_budget { task_budget_id } to release the pending budget, then update the connector by running \`${sdk.connectorUpgradeCommand()}\` and the repair line it prints, then open the budget again. There is no relay fallback for this signing context.`
250
+ };
251
+ }
241
252
  var headersSchema = v3.z.record(v3.z.string(), v3.z.string()).optional();
242
253
  var toolSchemas = {
243
254
  haven_send: {
@@ -245,18 +256,24 @@ var toolSchemas = {
245
256
  recipient: v3.z.string().min(1),
246
257
  amount: v3.z.string().min(1),
247
258
  idempotency_key: v3.z.string().optional(),
248
- /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
249
- idempotencyKey: v3.z.string().optional(),
259
+ /** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
260
+ idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
250
261
  /** #3329: spend against an open task budget instead of the agent's period budget. */
251
- task_budget_id: v3.z.string().min(1).optional()
262
+ task_budget_id: v3.z.string().min(1).optional(),
263
+ /**
264
+ * #3330: spend against an open sub-budget this agent HOLDS (it is the
265
+ * sub-agent B) instead of the agent's own budget. Mutually exclusive
266
+ * with task_budget_id — the backend refuses a body naming both.
267
+ */
268
+ sub_budget_id: v3.z.string().min(1).optional()
252
269
  },
253
270
  haven_pay_mcp_tool: {
254
271
  merchant_url: v3.z.string().url(),
255
272
  tool_name: v3.z.string().min(1),
256
273
  arguments: v3.z.record(v3.z.string(), v3.z.unknown()).optional(),
257
274
  idempotency_key: v3.z.string().optional(),
258
- /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
259
- idempotencyKey: v3.z.string().optional()
275
+ /** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
276
+ idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key")
260
277
  },
261
278
  haven_quote_x402: {
262
279
  url: v3.z.string().url(),
@@ -264,16 +281,18 @@ var toolSchemas = {
264
281
  headers: headersSchema,
265
282
  body: v3.z.string().optional(),
266
283
  idempotency_key: v3.z.string().optional(),
267
- /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
268
- idempotencyKey: v3.z.string().optional()
284
+ /** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
285
+ idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key")
269
286
  },
270
287
  haven_pay_x402_quote: {
271
288
  quote: v3.z.unknown(),
272
289
  idempotency_key: v3.z.string().optional(),
273
- /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
274
- idempotencyKey: v3.z.string().optional(),
290
+ /** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
291
+ idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
275
292
  /** #3329: spend against an open task budget instead of the agent's period budget. */
276
- task_budget_id: v3.z.string().min(1).optional()
293
+ task_budget_id: v3.z.string().min(1).optional(),
294
+ /** #3330: spend against an open sub-budget this agent holds (it is sub-agent B). Mutually exclusive with task_budget_id. */
295
+ sub_budget_id: v3.z.string().min(1).optional()
277
296
  },
278
297
  haven_pay_x402: {
279
298
  url: v3.z.string().url(),
@@ -281,10 +300,12 @@ var toolSchemas = {
281
300
  headers: headersSchema,
282
301
  body: v3.z.string().optional(),
283
302
  idempotency_key: v3.z.string().optional(),
284
- /** Legacy spelling, accepted during the #2366 window. Warns; do not use. */
285
- idempotencyKey: v3.z.string().optional(),
303
+ /** REMOVED (#3411): declared only so the resolver can refuse it by name, never accepted. */
304
+ idempotencyKey: v3.z.string().optional().describe("REMOVED (#3411): refused \u2014 send idempotency_key"),
286
305
  /** #3329: spend against an open task budget instead of the agent's period budget. */
287
- task_budget_id: v3.z.string().min(1).optional()
306
+ task_budget_id: v3.z.string().min(1).optional(),
307
+ /** #3330: spend against an open sub-budget this agent holds (it is sub-agent B). Mutually exclusive with task_budget_id. */
308
+ sub_budget_id: v3.z.string().min(1).optional()
288
309
  },
289
310
  haven_resume_x402_payment: {
290
311
  payment_id: v3.z.string().optional(),
@@ -312,7 +333,9 @@ var toolSchemas = {
312
333
  haven_list_receipts: {
313
334
  limit: v3.z.number().int().min(1).max(100).optional(),
314
335
  /** #3128: the previous page's next_cursor (a receipt id). */
315
- cursor: v3.z.string().min(1).optional()
336
+ cursor: v3.z.string().min(1).optional(),
337
+ /** #3423: drop each row's payload echoes; the default shape is unchanged. */
338
+ compact: v3.z.boolean().optional().describe("true drops challengePayload, selectedPayment and protocolReceiptPayload from each row; omit for the full row.")
316
339
  },
317
340
  haven_verify_receipt: {
318
341
  receipt: v3.z.unknown()
@@ -401,12 +424,15 @@ function createToolHandlers(haven) {
401
424
  token: args.asset,
402
425
  amount: args.amount,
403
426
  to: args.recipient,
404
- // #1207: was accepted by the schema but silently dropped — now
405
- // carried to the backend's replay contract.
406
- idempotencyKey: args.idempotencyKey,
427
+ // #1207/#3411: was accepted by the schema but silently dropped — now
428
+ // carried to the backend's replay contract via the resolved carrier.
429
+ idempotencyKey: args.resolvedIdempotencyKey,
407
430
  // #3329: spend against an open task budget instead of the
408
431
  // agent's period budget, when the caller names one.
409
- ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
432
+ ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
433
+ // #3330: spend through a sub-budget this agent holds (it is
434
+ // sub-agent B). Exactly one of the two ids, never both.
435
+ ...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
410
436
  });
411
437
  return {
412
438
  payment_id: result.paymentId,
@@ -444,7 +470,7 @@ function createToolHandlers(haven) {
444
470
  body: JSON.stringify(envelope)
445
471
  };
446
472
  let merchantUrl = args.merchant_url;
447
- const idempotencyKey = args.idempotencyKey;
473
+ const idempotencyKey = args.resolvedIdempotencyKey;
448
474
  const attempt = () => haven.fetch(merchantUrl, init, { idempotencyKey });
449
475
  let response = await attempt();
450
476
  if (!response.ok) {
@@ -478,7 +504,7 @@ function createToolHandlers(haven) {
478
504
  if ("success" in pf) return pf;
479
505
  const { args, warnings } = pf;
480
506
  try {
481
- const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey });
507
+ const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.resolvedIdempotencyKey });
482
508
  return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
483
509
  } catch (err) {
484
510
  return normalizeError(err);
@@ -505,8 +531,9 @@ function createToolHandlers(haven) {
505
531
  }
506
532
  return runTool(async () => {
507
533
  const response = await haven.payX402Quote(args.quote, {
508
- idempotencyKey: args.idempotencyKey,
509
- ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
534
+ idempotencyKey: args.resolvedIdempotencyKey,
535
+ ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
536
+ ...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
510
537
  });
511
538
  return responsePayload(response);
512
539
  }, warnings);
@@ -517,8 +544,9 @@ function createToolHandlers(haven) {
517
544
  const { args, warnings } = pf;
518
545
  return runTool(async () => {
519
546
  const response = await haven.fetch(args.url, requestInit(args), {
520
- idempotencyKey: args.idempotencyKey,
521
- ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
547
+ idempotencyKey: args.resolvedIdempotencyKey,
548
+ ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
549
+ ...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
522
550
  });
523
551
  return responsePayload(response);
524
552
  }, warnings);
@@ -615,7 +643,9 @@ function createToolHandlers(haven) {
615
643
  },
616
644
  haven_list_receipts: async (input) => {
617
645
  const args = objectInput("haven_list_receipts", input);
618
- return runTool(async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor }));
646
+ return runTool(
647
+ async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor, compact: args.compact === true })
648
+ );
619
649
  },
620
650
  haven_verify_receipt: async (input) => {
621
651
  const args = objectInput("haven_verify_receipt", input);
@@ -643,7 +673,8 @@ function createToolHandlers(haven) {
643
673
  task_budget: result.taskBudget,
644
674
  next_action: "sign",
645
675
  next_tool: "mcp__haven-signer__haven_sign",
646
- next_arguments: { task_budget_id: result.taskBudget.id }
676
+ next_arguments: { task_budget_id: result.taskBudget.id },
677
+ signer_compatibility: taskSignerCompatibilityNotice()
647
678
  };
648
679
  });
649
680
  },
@@ -658,7 +689,8 @@ function createToolHandlers(haven) {
658
689
  task_budget: result.taskBudget,
659
690
  next_action: "sign",
660
691
  next_tool: "mcp__haven-signer__haven_sign",
661
- next_arguments: { task_budget_id: args.task_budget_id }
692
+ next_arguments: { task_budget_id: args.task_budget_id },
693
+ signer_compatibility: taskSignerCompatibilityNotice()
662
694
  };
663
695
  });
664
696
  },
@@ -715,28 +747,20 @@ function preflight(name, input) {
715
747
  }
716
748
  const idem = resolveIdempotencyKey(args);
717
749
  if ("success" in idem) return idem;
718
- if (idem.key !== void 0) args = { ...args, idempotencyKey: idem.key };
750
+ if (idem.key !== void 0) args = { ...args, resolvedIdempotencyKey: idem.key };
719
751
  return { args, warnings: idem.warnings };
720
752
  }
721
753
  function resolveIdempotencyKey(args) {
722
754
  const modern = typeof args.idempotency_key === "string" ? args.idempotency_key : void 0;
723
755
  const legacy = typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0;
724
- if (modern !== void 0 && legacy !== void 0 && modern !== legacy) {
756
+ if (legacy !== void 0) {
725
757
  return {
726
758
  success: false,
727
- code: "AMBIGUOUS_IDEMPOTENCY_KEY",
728
- message: "Both idempotency_key and idempotencyKey were sent with different values. Nothing was contacted or spent. Send exactly one \u2014 idempotency_key is the current spelling; idempotencyKey is deprecated and will be removed."
759
+ code: "IDEMPOTENCY_KEY_RENAMED",
760
+ message: "idempotencyKey has been renamed to idempotency_key and is no longer accepted. Send idempotency_key instead. Nothing was contacted or spent."
729
761
  };
730
762
  }
731
763
  if (modern !== void 0) return { key: modern, warnings: [] };
732
- if (legacy !== void 0) {
733
- return {
734
- key: legacy,
735
- warnings: [
736
- "idempotencyKey is deprecated and will be removed in a future release of @haven_ai/mcp. Send idempotency_key instead \u2014 it is the spelling the hosted Haven MCP surface and every other Haven wire contract use."
737
- ]
738
- };
739
- }
740
764
  return { warnings: [] };
741
765
  }
742
766
  function buildMcpToolsCallEnvelope(toolName, args) {
@@ -967,7 +991,7 @@ function renderConsentBlock(input, hash) {
967
991
  lines.push("signed delegation, not by Haven):");
968
992
  for (const a of input.allowanceSummary) {
969
993
  const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : " (no reset)";
970
- lines.push(` \u2022 up to ${a.amount} ${a.token}${reset}`);
994
+ lines.push(` \u2022 up to ${describeBudgetAmount(a, input.chainId)}${reset}`);
971
995
  }
972
996
  }
973
997
  lines.push("");
@@ -1064,7 +1088,14 @@ async function consentInputFromClient(haven, seed, toolNames) {
1064
1088
  }
1065
1089
  const liveAllowanceSummary = list.map((a) => ({
1066
1090
  token: a.tokenSymbol ?? "UNKNOWN",
1067
- amount: a.onchain?.amount ?? a.configuredAmount ?? "0",
1091
+ // #3410: `onchain.amount` is the atomic budget (`budget_atomic` on the
1092
+ // backend). The old `a.configuredAmount` fallback here was unreachable
1093
+ // — `onchain` is required in the SDK types and dereferenced
1094
+ // unconditionally, so a response without it throws into the seed path —
1095
+ // and it was the one HUMAN-decimal value in this chain; a human number
1096
+ // must never reach an atomic field. '0' keeps the (defensive) shape for
1097
+ // a malformed onchain object without inventing a unit.
1098
+ amount: a.onchain?.amount ?? "0",
1068
1099
  resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
1069
1100
  }));
1070
1101
  allowanceSummary = liveAllowanceSummary;
@@ -1087,6 +1118,11 @@ function isAllowanceSummary(value) {
1087
1118
  function derivePrefix(apiKey) {
1088
1119
  return apiKey.slice(0, 12);
1089
1120
  }
1121
+ function describeBudgetAmount(a, chainId) {
1122
+ const token = chainId === void 0 ? null : sdk.resolveTokenBySymbol(chainId, a.token);
1123
+ if (!token) return `${a.amount} ${a.token} (atomic units)`;
1124
+ return `${sdk.formatTokenAmount(a.amount, token.decimals)} ${a.token}`;
1125
+ }
1090
1126
  function registeredToolNames() {
1091
1127
  return Object.keys(toolSchemas);
1092
1128
  }
@@ -1111,7 +1147,7 @@ async function resolveHavenClient(options = {}) {
1111
1147
  return { client, credentials };
1112
1148
  }
1113
1149
  var MCP_NAME = "@haven_ai/mcp";
1114
- var MCP_VERSION = "0.6.0-alpha.0";
1150
+ var MCP_VERSION = "0.7.0-alpha.0";
1115
1151
  var MCP_INSTRUCTIONS = [
1116
1152
  "Haven local MCP server: signs in-process with the delegate key it holds on",
1117
1153
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",