@haven_ai/mcp 0.6.0-alpha.0 → 0.8.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.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()
@@ -330,12 +353,19 @@ var toolSchemas = {
330
353
  haven_close_task_budget: {
331
354
  task_budget_id: v3.z.string().min(1)
332
355
  },
333
- // #3329: relays a signature from the local signer — either the open/close
334
- // signature for a task budget, or (schema parity with the hosted surface)
335
- // a direct-payment signature by payment_id. Exactly one of task_budget_id /
336
- // payment_id, never both or neither.
356
+ // #3518: read ONE task budget by id, any status — the status check a close
357
+ // refusal's "re-check the budget's status" points at, on the local surface
358
+ // too (schema and wording parity with the hosted read).
359
+ haven_get_task_budget: {
360
+ task_budget_id: v3.z.string().min(1)
361
+ },
362
+ // #3329 / #3506: relays a signature from the local signer — the open/close
363
+ // signature for a task budget or a sub-budget row, or (schema parity with
364
+ // the hosted surface) a direct-payment signature by payment_id. Exactly one
365
+ // of task_budget_id / sub_budget_id / payment_id, never two or none.
337
366
  haven_submit: {
338
367
  task_budget_id: v3.z.string().min(1).optional(),
368
+ sub_budget_id: v3.z.string().min(1).optional(),
339
369
  payment_id: v3.z.string().min(1).optional(),
340
370
  signature: v3.z.string().regex(/^0x[0-9a-fA-F]+$/, "signature must be a 0x-prefixed hex string")
341
371
  }
@@ -363,11 +393,19 @@ var CLOSE_TASK_BUDGET_DESCRIPTION = [
363
393
  "the signature with haven_submit. A task budget past its own deadline closes immediately, the",
364
394
  "same as the pending case."
365
395
  ].join(" ");
396
+ var GET_TASK_BUDGET_DESCRIPTION = [
397
+ "Read one task budget by id, any status.",
398
+ "Returns { task_budget } with status (pending | open | closing | closed), isExpired, maxDisplay, recipientAddress, label and expiresAt \u2014",
399
+ "the check to run after a close or submit refusal says to re-check the budget's status.",
400
+ "haven_get_agent lists live task budgets only; this reads any one, closed or expired included."
401
+ ].join(" ");
366
402
  var SUBMIT_DESCRIPTION = [
367
403
  "Relay a signature from the local signer. Pass exactly one of task_budget_id (from",
368
- "haven_open_task_budget or haven_close_task_budget) or payment_id \u2014 never both, never neither \u2014",
369
- "plus signature. For a task budget this opens or closes it on-chain and returns { task_budget,",
370
- 'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.'
404
+ "haven_open_task_budget or haven_close_task_budget), sub_budget_id (from haven_get_agent",
405
+ "pendingSubBudgetSignatures[]) or payment_id \u2014 never two, never none \u2014 plus signature.",
406
+ "For a task budget this opens or closes it on-chain and returns { task_budget,",
407
+ 'status }; a close in progress may return { task_budget, status: "closed", close_tx_hash }.',
408
+ "A sub_budget_id returns { sub_budget, status } with the next signer step, if one is owed."
371
409
  ].join(" ");
372
410
  var toolDescriptions = {
373
411
  haven_send: sdk.composeDescription(sdk.toolDescriptions.send),
@@ -387,8 +425,101 @@ var toolDescriptions = {
387
425
  haven_verify_receipt: sdk.composeDescription(sdk.toolDescriptions.verifyReceipt),
388
426
  haven_open_task_budget: OPEN_TASK_BUDGET_DESCRIPTION,
389
427
  haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
428
+ haven_get_task_budget: GET_TASK_BUDGET_DESCRIPTION,
390
429
  haven_submit: SUBMIT_DESCRIPTION
391
430
  };
431
+ function subBudgetSignStep(subBudgetId) {
432
+ return {
433
+ next_action: "sign",
434
+ next_tool: "mcp__haven-signer__haven_sign",
435
+ next_arguments: { sub_budget_id: subBudgetId }
436
+ };
437
+ }
438
+ async function submitSubBudgetTool(haven, subBudgetId, signature) {
439
+ let result;
440
+ try {
441
+ result = await haven.submitSubBudget(subBudgetId, signature);
442
+ } catch (err) {
443
+ const code = err instanceof sdk.HavenApiError ? err.body?.error_code : void 0;
444
+ if (code === "close_needs_reprepare") {
445
+ let fresh;
446
+ try {
447
+ fresh = await haven.closeSubBudget(subBudgetId);
448
+ } catch (closeErr) {
449
+ return normalizeError(closeErr);
450
+ }
451
+ if (fresh.status === "closed") {
452
+ return {
453
+ success: true,
454
+ data: {
455
+ sub_budget: fresh.subBudget,
456
+ status: "closed",
457
+ next_action: "none",
458
+ next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
459
+ }
460
+ };
461
+ }
462
+ return {
463
+ success: false,
464
+ code: "CLOSE_NEEDS_REPREPARE",
465
+ message: "The stored close operation for this sub-budget was stale, so nothing was relayed. Haven prepared a fresh one: sign it with haven_sign, then call haven_submit again with the new signature.",
466
+ statusCode: 409,
467
+ ...subBudgetSignStep(subBudgetId)
468
+ };
469
+ }
470
+ if (code === "close_outcome_unconfirmed") {
471
+ return {
472
+ success: false,
473
+ code: "CLOSE_OUTCOME_UNCONFIRMED",
474
+ message: "The close operation was sent but its outcome is not confirmed yet (on Base that can take tens of minutes). Wait, then call haven_submit again with the same arguments: it reports closed once the chain has finalised the disable, or prepares a fresh operation to sign if the earlier one did not land.",
475
+ statusCode: 502,
476
+ next_action: "check_status_later",
477
+ next_tool: "haven_submit",
478
+ next_arguments: { sub_budget_id: subBudgetId, signature }
479
+ };
480
+ }
481
+ return normalizeError(err);
482
+ }
483
+ return runTool(async () => {
484
+ const submitted = {
485
+ sub_budget: result.subBudget,
486
+ status: result.status,
487
+ ...result.closeTxHash !== void 0 ? { close_tx_hash: result.closeTxHash } : {}
488
+ };
489
+ if (result.status === "closed") {
490
+ return {
491
+ ...submitted,
492
+ next_action: "none",
493
+ next_tool_omitted_reason: "the sub-budget is closed on-chain; nothing further to sign or submit for it"
494
+ };
495
+ }
496
+ let pending;
497
+ try {
498
+ pending = await haven.listPendingSubBudgetSignatures();
499
+ } catch {
500
+ pending = null;
501
+ }
502
+ if (pending === null) {
503
+ return {
504
+ ...submitted,
505
+ next_action: "check_status_later",
506
+ next_tool: "haven_get_agent",
507
+ next_arguments: {},
508
+ reason: "This sub-budget row is open, but Haven could not say whether its tree has another row awaiting your signature. Call haven_get_agent and sign any pendingSubBudgetSignatures row."
509
+ };
510
+ }
511
+ const tree = result.subBudget.parentSubBudgetId ?? result.subBudget.id;
512
+ const sibling = pending.find(
513
+ (row) => row.purpose === "open" && row.subBudgetId !== result.subBudget.id && (row.parentSubBudgetId ?? row.subBudgetId) === tree
514
+ );
515
+ if (sibling) return { ...submitted, ...subBudgetSignStep(sibling.subBudgetId) };
516
+ return {
517
+ ...submitted,
518
+ next_action: "none",
519
+ next_tool_omitted_reason: "no other row of this sub-budget awaits your signature, so the sub-budget is fully open and the sub-agent can spend through its grant"
520
+ };
521
+ });
522
+ }
392
523
  function createToolHandlers(haven) {
393
524
  return {
394
525
  haven_send: async (input) => {
@@ -401,12 +532,15 @@ function createToolHandlers(haven) {
401
532
  token: args.asset,
402
533
  amount: args.amount,
403
534
  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,
535
+ // #1207/#3411: was accepted by the schema but silently dropped — now
536
+ // carried to the backend's replay contract via the resolved carrier.
537
+ idempotencyKey: args.resolvedIdempotencyKey,
407
538
  // #3329: spend against an open task budget instead of the
408
539
  // agent's period budget, when the caller names one.
409
- ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
540
+ ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
541
+ // #3330: spend through a sub-budget this agent holds (it is
542
+ // sub-agent B). Exactly one of the two ids, never both.
543
+ ...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
410
544
  });
411
545
  return {
412
546
  payment_id: result.paymentId,
@@ -444,7 +578,7 @@ function createToolHandlers(haven) {
444
578
  body: JSON.stringify(envelope)
445
579
  };
446
580
  let merchantUrl = args.merchant_url;
447
- const idempotencyKey = args.idempotencyKey;
581
+ const idempotencyKey = args.resolvedIdempotencyKey;
448
582
  const attempt = () => haven.fetch(merchantUrl, init, { idempotencyKey });
449
583
  let response = await attempt();
450
584
  if (!response.ok) {
@@ -478,7 +612,7 @@ function createToolHandlers(haven) {
478
612
  if ("success" in pf) return pf;
479
613
  const { args, warnings } = pf;
480
614
  try {
481
- const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.idempotencyKey });
615
+ const data = await haven.quoteX402(args.url, requestInit(args), { idempotencyKey: args.resolvedIdempotencyKey });
482
616
  return warnings.length > 0 ? { success: true, data, warnings } : { success: true, data };
483
617
  } catch (err) {
484
618
  return normalizeError(err);
@@ -505,8 +639,9 @@ function createToolHandlers(haven) {
505
639
  }
506
640
  return runTool(async () => {
507
641
  const response = await haven.payX402Quote(args.quote, {
508
- idempotencyKey: args.idempotencyKey,
509
- ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {}
642
+ idempotencyKey: args.resolvedIdempotencyKey,
643
+ ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
644
+ ...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
510
645
  });
511
646
  return responsePayload(response);
512
647
  }, warnings);
@@ -517,8 +652,9 @@ function createToolHandlers(haven) {
517
652
  const { args, warnings } = pf;
518
653
  return runTool(async () => {
519
654
  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 } : {}
655
+ idempotencyKey: args.resolvedIdempotencyKey,
656
+ ...typeof args.task_budget_id === "string" ? { taskBudgetId: args.task_budget_id } : {},
657
+ ...typeof args.sub_budget_id === "string" ? { subBudgetId: args.sub_budget_id } : {}
522
658
  });
523
659
  return responsePayload(response);
524
660
  }, warnings);
@@ -539,7 +675,17 @@ function createToolHandlers(haven) {
539
675
  const args = objectInput("haven_get_resume_state", input);
540
676
  return runTool(async () => haven.getResumeState(args.payment_id));
541
677
  },
542
- haven_get_agent: async () => runTool(async () => haven.getAgentSummary()),
678
+ haven_get_agent: async () => runTool(async () => {
679
+ const summary = await haven.getAgentSummary();
680
+ if (!Array.isArray(summary.pendingSubBudgetSignatures)) return summary;
681
+ return {
682
+ ...summary,
683
+ pendingSubBudgetSignatures: summary.pendingSubBudgetSignatures.map((row) => ({
684
+ ...row,
685
+ ...subBudgetSignStep(row.subBudgetId)
686
+ }))
687
+ };
688
+ }),
543
689
  haven_get_allowances: async () => runTool(async () => haven.getAllowances()),
544
690
  haven_sweep_delegate: async () => runTool(async () => haven.sweepDelegate()),
545
691
  haven_discover_tools: async (input) => {
@@ -615,7 +761,9 @@ function createToolHandlers(haven) {
615
761
  },
616
762
  haven_list_receipts: async (input) => {
617
763
  const args = objectInput("haven_list_receipts", input);
618
- return runTool(async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor }));
764
+ return runTool(
765
+ async () => haven.listReceiptsPage({ limit: args.limit, cursor: args.cursor, compact: args.compact === true })
766
+ );
619
767
  },
620
768
  haven_verify_receipt: async (input) => {
621
769
  const args = objectInput("haven_verify_receipt", input);
@@ -643,7 +791,8 @@ function createToolHandlers(haven) {
643
791
  task_budget: result.taskBudget,
644
792
  next_action: "sign",
645
793
  next_tool: "mcp__haven-signer__haven_sign",
646
- next_arguments: { task_budget_id: result.taskBudget.id }
794
+ next_arguments: { task_budget_id: result.taskBudget.id },
795
+ signer_compatibility: taskSignerCompatibilityNotice()
647
796
  };
648
797
  });
649
798
  },
@@ -658,21 +807,37 @@ function createToolHandlers(haven) {
658
807
  task_budget: result.taskBudget,
659
808
  next_action: "sign",
660
809
  next_tool: "mcp__haven-signer__haven_sign",
661
- next_arguments: { task_budget_id: args.task_budget_id }
810
+ next_arguments: { task_budget_id: args.task_budget_id },
811
+ signer_compatibility: taskSignerCompatibilityNotice()
662
812
  };
663
813
  });
664
814
  },
815
+ // #3518: read ONE task budget by id, any status — the status check a
816
+ // close/submit refusal's "re-check the budget's status" points at. The
817
+ // SDK relays `GET /task-budgets/:id` (the backend route answers any
818
+ // status) and a 404 carries the backend's own message through runTool.
819
+ haven_get_task_budget: async (input) => {
820
+ const args = objectInput("haven_get_task_budget", input);
821
+ return runTool(async () => {
822
+ const taskBudget = await haven.getTaskBudget(args.task_budget_id);
823
+ return { task_budget: taskBudget };
824
+ });
825
+ },
665
826
  haven_submit: async (input) => {
666
827
  const args = objectInput("haven_submit", input);
667
828
  const hasTaskBudget = typeof args.task_budget_id === "string" && args.task_budget_id.length > 0;
829
+ const hasSubBudget = typeof args.sub_budget_id === "string" && args.sub_budget_id.length > 0;
668
830
  const hasPayment = typeof args.payment_id === "string" && args.payment_id.length > 0;
669
- if (hasTaskBudget === hasPayment) {
831
+ if ([hasTaskBudget, hasSubBudget, hasPayment].filter(Boolean).length !== 1) {
670
832
  return {
671
833
  success: false,
672
834
  code: "INVALID_INPUT",
673
- message: "haven_submit takes exactly one of task_budget_id or payment_id, never both or neither. Nothing was relayed."
835
+ message: "haven_submit takes exactly one of payment_id, task_budget_id or sub_budget_id, never two or none. Nothing was relayed."
674
836
  };
675
837
  }
838
+ if (hasSubBudget) {
839
+ return submitSubBudgetTool(haven, args.sub_budget_id, args.signature);
840
+ }
676
841
  return runTool(async () => {
677
842
  if (hasTaskBudget) {
678
843
  const result = await haven.submitTaskBudget(args.task_budget_id, args.signature);
@@ -683,7 +848,7 @@ function createToolHandlers(haven) {
683
848
  };
684
849
  }
685
850
  throw new sdk.HavenApiError(
686
- "haven_submit with payment_id is not supported on the local MCP surface: haven_send signs and submits a direct payment in one call, so there is no separate relay step. Use haven_submit with task_budget_id to relay a task-budget open/close signature.",
851
+ "haven_submit with payment_id is not supported on the local MCP surface: haven_send signs and submits a direct payment in one call, so there is no separate relay step. Use haven_submit with task_budget_id or sub_budget_id to relay a budget open/close signature.",
687
852
  400
688
853
  );
689
854
  });
@@ -715,28 +880,20 @@ function preflight(name, input) {
715
880
  }
716
881
  const idem = resolveIdempotencyKey(args);
717
882
  if ("success" in idem) return idem;
718
- if (idem.key !== void 0) args = { ...args, idempotencyKey: idem.key };
883
+ if (idem.key !== void 0) args = { ...args, resolvedIdempotencyKey: idem.key };
719
884
  return { args, warnings: idem.warnings };
720
885
  }
721
886
  function resolveIdempotencyKey(args) {
722
887
  const modern = typeof args.idempotency_key === "string" ? args.idempotency_key : void 0;
723
888
  const legacy = typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0;
724
- if (modern !== void 0 && legacy !== void 0 && modern !== legacy) {
889
+ if (legacy !== void 0) {
725
890
  return {
726
891
  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."
892
+ code: "IDEMPOTENCY_KEY_RENAMED",
893
+ message: "idempotencyKey has been renamed to idempotency_key and is no longer accepted. Send idempotency_key instead. Nothing was contacted or spent."
729
894
  };
730
895
  }
731
896
  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
897
  return { warnings: [] };
741
898
  }
742
899
  function buildMcpToolsCallEnvelope(toolName, args) {
@@ -967,7 +1124,7 @@ function renderConsentBlock(input, hash) {
967
1124
  lines.push("signed delegation, not by Haven):");
968
1125
  for (const a of input.allowanceSummary) {
969
1126
  const reset = a.resetMinutes ? ` per ${a.resetMinutes} min` : " (no reset)";
970
- lines.push(` \u2022 up to ${a.amount} ${a.token}${reset}`);
1127
+ lines.push(` \u2022 up to ${describeBudgetAmount(a, input.chainId)}${reset}`);
971
1128
  }
972
1129
  }
973
1130
  lines.push("");
@@ -1064,7 +1221,14 @@ async function consentInputFromClient(haven, seed, toolNames) {
1064
1221
  }
1065
1222
  const liveAllowanceSummary = list.map((a) => ({
1066
1223
  token: a.tokenSymbol ?? "UNKNOWN",
1067
- amount: a.onchain?.amount ?? a.configuredAmount ?? "0",
1224
+ // #3410: `onchain.amount` is the atomic budget (`budget_atomic` on the
1225
+ // backend). The old `a.configuredAmount` fallback here was unreachable
1226
+ // — `onchain` is required in the SDK types and dereferenced
1227
+ // unconditionally, so a response without it throws into the seed path —
1228
+ // and it was the one HUMAN-decimal value in this chain; a human number
1229
+ // must never reach an atomic field. '0' keeps the (defensive) shape for
1230
+ // a malformed onchain object without inventing a unit.
1231
+ amount: a.onchain?.amount ?? "0",
1068
1232
  resetMinutes: typeof a.onchain?.resetTimeMin === "number" ? a.onchain.resetTimeMin : typeof a.resetPeriodMin === "number" ? a.resetPeriodMin : null
1069
1233
  }));
1070
1234
  allowanceSummary = liveAllowanceSummary;
@@ -1087,6 +1251,11 @@ function isAllowanceSummary(value) {
1087
1251
  function derivePrefix(apiKey) {
1088
1252
  return apiKey.slice(0, 12);
1089
1253
  }
1254
+ function describeBudgetAmount(a, chainId) {
1255
+ const token = chainId === void 0 ? null : sdk.resolveTokenBySymbol(chainId, a.token);
1256
+ if (!token) return `${a.amount} ${a.token} (atomic units)`;
1257
+ return `${sdk.formatTokenAmount(a.amount, token.decimals)} ${a.token}`;
1258
+ }
1090
1259
  function registeredToolNames() {
1091
1260
  return Object.keys(toolSchemas);
1092
1261
  }
@@ -1119,7 +1288,7 @@ async function createHavenMcpServer(options = {}) {
1119
1288
  return buildMcpServer(haven);
1120
1289
  }
1121
1290
  var MCP_NAME = "@haven_ai/mcp";
1122
- var MCP_VERSION = "0.6.0-alpha.0";
1291
+ var MCP_VERSION = "0.8.0-alpha.0";
1123
1292
  var MCP_INSTRUCTIONS = [
1124
1293
  "Haven local MCP server: signs in-process with the delegate key it holds on",
1125
1294
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",
@@ -1224,6 +1393,8 @@ function toMcpResult(payload) {
1224
1393
 
1225
1394
  exports.MCP_NAME = MCP_NAME;
1226
1395
  exports.MCP_VERSION = MCP_VERSION;
1396
+ exports.SIGNER_CAPABILITY_KEY = SIGNER_CAPABILITY_KEY;
1397
+ exports.TASK_BUDGET_MIN_SIGNER_VERSION = TASK_BUDGET_MIN_SIGNER_VERSION;
1227
1398
  exports.assertSupportedNodeVersion = assertSupportedNodeVersion;
1228
1399
  exports.computeConsentHash = computeConsentHash;
1229
1400
  exports.consentInputFromClient = consentInputFromClient;
@@ -1236,6 +1407,7 @@ exports.registeredToolNames = registeredToolNames;
1236
1407
  exports.renderConsentBlock = renderConsentBlock;
1237
1408
  exports.resolveHavenClient = resolveHavenClient;
1238
1409
  exports.runStdioServer = runStdioServer;
1410
+ exports.taskSignerCompatibilityNotice = taskSignerCompatibilityNotice;
1239
1411
  exports.toolDescriptions = toolDescriptions;
1240
1412
  exports.toolSchemas = toolSchemas;
1241
1413
  //# sourceMappingURL=index.cjs.map