@haven_ai/mcp 0.8.1-alpha.0 → 0.10.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/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, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, resolveTokenBySymbol, formatTokenAmount, HavenApiError, HavenPaymentStateError, HavenSigningError, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, verifyPaymentReceipt, discoverMerchantMcpUrl, sameUrl, resolveTokenFromAddress, TASK_SIGN_CONTEXT_VERSION, connectorUpgradeCommand } from '@haven_ai/sdk';
4
+ import { composeDescription, toolDescriptions as toolDescriptions$1, HavenClient, havenClientIdentity, isSupportedNodeVersion, unsupportedNodeVersionMessage, resolveTokenBySymbol, formatTokenAmount, HavenApiError, HavenPaymentStateError, HavenSigningError, AgentPaymentNextAction, HavenError, AgentPaymentFailureCode, verifyPaymentReceipt, catalogFundingLegExpected, discoverMerchantMcpUrl, sameUrl, resolveTokenFromAddress, TASK_SIGN_CONTEXT_VERSION, connectorUpgradeCommand } 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';
@@ -336,7 +336,21 @@ var toolSchemas = {
336
336
  compact: z.boolean().optional().describe("true drops challengePayload, selectedPayment and protocolReceiptPayload from each row; omit for the full row.")
337
337
  },
338
338
  haven_verify_receipt: {
339
- receipt: z.unknown()
339
+ receipt: z.unknown(),
340
+ // #3723: the spread form — the endpoint's `{ receipt, verification }`
341
+ // spread as top-level arguments — verifies: `verification` is declared
342
+ // ACCEPTED-AND-IGNORED (Haven's own self-check, never read). The stdio
343
+ // z.object would strip it silently anyway; declaring it keeps the schema
344
+ // identical to the hosted runtime's, where the key must be declared to
345
+ // pass its strict refusal.
346
+ verification: z.unknown().optional().describe("accepted and ignored \u2014 verification reads the receipt itself")
347
+ },
348
+ // #3723: the signed bundle's read — `{ receipt }` only, settled payments
349
+ // only. The endpoint's server-side `verification` is deliberately NOT
350
+ // returned: it is computed on Haven's server, not offline, and an agent
351
+ // that trusted it would skip haven_verify_receipt.
352
+ haven_get_receipt: {
353
+ payment_id: z.string().min(1)
340
354
  },
341
355
  // #3329: a budget for one task that ends by itself — a short-lived child of
342
356
  // the agent's own budget, capped and time-boxed independently of the period
@@ -421,6 +435,8 @@ var toolDescriptions = {
421
435
  haven_submit_catalog_entry: composeDescription(toolDescriptions$1.submitCatalogEntry),
422
436
  haven_list_receipts: composeDescription(toolDescriptions$1.listReceipts),
423
437
  haven_verify_receipt: composeDescription(toolDescriptions$1.verifyReceipt),
438
+ // #3723: the signed bundle's read, described from the shared fragment.
439
+ haven_get_receipt: composeDescription(toolDescriptions$1.getReceipt),
424
440
  haven_open_task_budget: OPEN_TASK_BUDGET_DESCRIPTION,
425
441
  haven_close_task_budget: CLOSE_TASK_BUDGET_DESCRIPTION,
426
442
  haven_get_task_budget: GET_TASK_BUDGET_DESCRIPTION,
@@ -709,6 +725,10 @@ function createToolHandlers(haven) {
709
725
  price_atomic: entry.priceAtomic,
710
726
  asset: entry.asset,
711
727
  network: entry.network,
728
+ // #3839: same hint the hosted surface emits — a catalog hint, the
729
+ // live quote stays authoritative; "unknown" when nothing is recorded.
730
+ asset_transfer_methods: entry.assetTransferMethods ?? null,
731
+ funding_leg_expected: catalogFundingLegExpected(entry.assetTransferMethods),
712
732
  status: entry.status,
713
733
  verified_at: entry.verifiedAt,
714
734
  source: entry.source,
@@ -767,6 +787,17 @@ function createToolHandlers(haven) {
767
787
  const args = objectInput("haven_verify_receipt", input);
768
788
  return runTool(async () => verifyPaymentReceipt(args.receipt));
769
789
  },
790
+ // #3723: the signed bundle's home on the local surface. Parsed INSIDE
791
+ // runTool, so a missing payment_id is a structured INVALID_INPUT failure
792
+ // rather than a thrown ZodError (the hosted runtime's parseStrict already
793
+ // answers that way). Returns `{ receipt }` only — the endpoint's
794
+ // server-side `verification` is not offline evidence, and the agent
795
+ // verifies with haven_verify_receipt, which reads the bundle unchanged.
796
+ haven_get_receipt: async (input) => runTool(async () => {
797
+ const args = objectInput("haven_get_receipt", input);
798
+ const { receipt } = await haven.getReceipt(args.payment_id);
799
+ return { receipt };
800
+ }),
770
801
  haven_open_task_budget: async (input) => {
771
802
  const args = objectInput("haven_open_task_budget", input);
772
803
  return runTool(async () => {
@@ -963,17 +994,26 @@ function parseMaybeJson(text) {
963
994
  return text;
964
995
  }
965
996
  }
997
+ var MERCHANT_OUT_OF_GAS_REASON = "settlement_wallet_out_of_gas";
998
+ function merchantOutOfGasMessage(settlementsRemaining, failFloor) {
999
+ const remaining = typeof settlementsRemaining === "number" ? settlementsRemaining : null;
1000
+ const floor = typeof failFloor === "number" ? failFloor : null;
1001
+ const figures = remaining !== null && floor !== null ? ` It has gas for ${remaining} more settlement${remaining === 1 ? "" : "s"} and refuses new payments below ${floor} (settlements_remaining: ${remaining}, fail_floor: ${floor}).` : remaining !== null ? ` settlements_remaining: ${remaining}.` : "";
1002
+ return `The merchant refused this call: its settlement wallet is out of gas (reason_code: ${MERCHANT_OUT_OF_GAS_REASON}).` + figures + " No payment was created. The merchant's operator must top up its settlement wallet; until then every retry is refused again. Tell the user, and re-quote once the merchant has been topped up.";
1003
+ }
966
1004
  var MerchantNotReadyError = class extends Error {
1005
+ constructor(message, nextToolOmittedReason = "the merchant needs to recover first; re-quote after the retry_after_s in the message") {
1006
+ super(message);
1007
+ this.nextToolOmittedReason = nextToolOmittedReason;
1008
+ this.name = "MerchantNotReadyError";
1009
+ }
1010
+ nextToolOmittedReason;
967
1011
  code = AgentPaymentFailureCode.MerchantNotReady;
968
1012
  statusCode = 503;
969
1013
  nextAction = AgentPaymentNextAction.StopAndTellUser;
970
1014
  // Genuinely retryable — unlike a rejection, nothing about THIS call was
971
1015
  // wrong; the merchant's own wallet needs to recover first.
972
1016
  retryWithNewQuote = true;
973
- constructor(message) {
974
- super(message);
975
- this.name = "MerchantNotReadyError";
976
- }
977
1017
  };
978
1018
  async function merchantNotReadyErrorFor(response) {
979
1019
  if (response.status !== 503) return null;
@@ -986,7 +1026,14 @@ async function merchantNotReadyErrorFor(response) {
986
1026
  if (!body || typeof body !== "object" || body.error !== "merchant_not_ready") {
987
1027
  return null;
988
1028
  }
989
- const { reason_code, settlements_remaining, retry_after_s } = body;
1029
+ const { reason_code, settlements_remaining, fail_floor, retry_after_s } = body;
1030
+ if (reason_code === MERCHANT_OUT_OF_GAS_REASON) {
1031
+ return new MerchantNotReadyError(
1032
+ merchantOutOfGasMessage(settlements_remaining, fail_floor),
1033
+ // Same reason the hosted runtime returns (support/mcp-context.ts).
1034
+ "the merchant's operator must top up its settlement wallet first; re-quote after that"
1035
+ );
1036
+ }
990
1037
  return new MerchantNotReadyError(
991
1038
  "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.")
992
1039
  );
@@ -1007,8 +1054,10 @@ function normalizeError(err) {
1007
1054
  next_action: err.nextAction,
1008
1055
  retry_with_new_quote: err.retryWithNewQuote,
1009
1056
  // #3103: the local runtime's one decision site — no tool can act until
1010
- // the merchant recovers; the message carries retry_after_s.
1011
- next_tool_omitted_reason: "the merchant needs to recover first; re-quote after the retry_after_s in the message"
1057
+ // the merchant recovers. The reason rides the error (#3834): the
1058
+ // generic one points at the message's retry_after_s, the out-of-gas
1059
+ // one at the operator top-up.
1060
+ next_tool_omitted_reason: err.nextToolOmittedReason
1012
1061
  };
1013
1062
  }
1014
1063
  if (err instanceof HavenPaymentStateError) {
@@ -1050,6 +1099,14 @@ function normalizeError(err) {
1050
1099
  body: err.body
1051
1100
  };
1052
1101
  }
1102
+ if (err instanceof z.ZodError) {
1103
+ return {
1104
+ success: false,
1105
+ code: "INVALID_INPUT",
1106
+ message: err.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join("; "),
1107
+ statusCode: 400
1108
+ };
1109
+ }
1053
1110
  if (err instanceof HavenError) {
1054
1111
  return {
1055
1112
  success: false,
@@ -1278,7 +1335,7 @@ async function resolveHavenClient(options = {}) {
1278
1335
  return { client, credentials };
1279
1336
  }
1280
1337
  var MCP_NAME = "@haven_ai/mcp";
1281
- var MCP_VERSION = "0.8.1-alpha.0";
1338
+ var MCP_VERSION = "0.10.0-alpha.0";
1282
1339
  var MCP_INSTRUCTIONS = [
1283
1340
  "Haven local MCP server: signs in-process with the delegate key it holds on",
1284
1341
  "this machine \u2014 the key never leaves this process. Call haven_get_agent",