@muhaven/mcp 0.2.3 → 0.2.5

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/CHANGELOG.md CHANGED
@@ -7,6 +7,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.5] — 2026-05-23
11
+
12
+ ### Fixed
13
+
14
+ - **`PLACEHOLDER_SIGNATURE` size 86 bytes → 66 bytes** to match the
15
+ real Kernel v3.1 PermissionValidator signature shape that
16
+ `buildKernelSessionKeySignature` produces:
17
+
18
+ byte 0 — 0xff (PermissionValidator "use root permission" sentinel)
19
+ bytes 1..65 — 65-byte ECDSA
20
+ = 66 bytes total
21
+
22
+ Pre-0.2.5 the placeholder was 86 bytes — the OLD enable-mode shape
23
+ (1 byte prefix + 20 bytes validator + 65 bytes ECDSA). The paymaster
24
+ simulated the validator with the wrong-length signature, the
25
+ validator reverted with `AA23 reverted`, and
26
+ `zd_sponsorUserOperation` returned rpc_error → MCP mapped to
27
+ `paymaster_rejected`. This is the load-bearing piece that 0.2.4 did
28
+ NOT close.
29
+
30
+ ### Added
31
+
32
+ - **`pathDFallbackDetail` in the `muhaven.position.buy` echo.** Every
33
+ Path D fallback (`bundler_setup_failed`, `paymaster_rejected`,
34
+ `encrypt_shares_server_error`, etc.) now carries the underlying
35
+ error message in addition to the structured reason code. Pre-0.2.5
36
+ the message was dropped → every new gate required curl repro to
37
+ find the actual error class (cost ~2 publish cycles during the
38
+ 2026-05-23 smoke). Future fallback iterations are self-diagnosing.
39
+ Untrusted-network input (bundler RPC error messages) is sanitized
40
+ server-side before crossing into the echo (existing
41
+ `sanitizeRpcMessageForLlmContext` boundary).
42
+
43
+ ### Changed
44
+
45
+ - **`DEFAULT_REQUEST_TIMEOUT_MS` 15s → 75s.** The cold-start FHE
46
+ encrypt at `/api/v1/agent/path-d/encrypt-shares` costs ~25s on
47
+ first call after fhe-worker container boot (CoFHE verifier-
48
+ signature handshake). The 15s default cut the MCP-side fetch
49
+ before the backend's reply arrived → spurious
50
+ `encrypt_shares_server_error`. Operators on warm setups can
51
+ tighten via `MUHAVEN_REQUEST_TIMEOUT_MS`. Subsequent encrypts
52
+ after warm-up are sub-second; the 75s ceiling is defensive
53
+ headroom, not steady-state latency.
54
+
55
+ ## [0.2.4] — 2026-05-23
56
+
57
+ ### Fixed
58
+
59
+ - **Paymaster RPC method + param shape**:
60
+ `pm_sponsorUserOperation` → `zd_sponsorUserOperation`. ZeroDev v3
61
+ endpoints route paymaster RPCs through Alchemy infrastructure, which
62
+ exposes only ERC-7677 (`pm_getPaymasterStubData` / `pm_getPaymasterData`)
63
+ and ZeroDev-prefixed (`zd_sponsorUserOperation`) — the legacy
64
+ `pm_sponsorUserOperation` returns `"Unsupported method"` from
65
+ Alchemy. The MCP server's `attemptPathD` failed at
66
+ `pathDFallbackReason: paymaster_rejected` on every Path D autonomous
67
+ buy. Fix: switch method name + change request shape from positional
68
+ `[userOp, entryPoint]` to wrapped
69
+ `[{chainId, userOp, entryPointAddress, shouldOverrideFee, shouldConsume}]`.
70
+ Matches `@zerodev/sdk@5.5.10`'s `paymaster/sponsorUserOperation.js`
71
+ byte-for-byte. Verified 2026-05-23 via direct curl reproduction
72
+ against the prod bundler URL (bare → "Unsupported method"; correct
73
+ shape → simulation result with AA23 from synthetic UserOp, proving
74
+ the method works).
75
+
76
+ `sponsorUserOp` now requires `expectedChainId` in `BundlerClientOptions`
77
+ (throws `BundlerClientError(config)` when missing) — the chainId is
78
+ part of the request envelope. Defaults conservative placeholder gas
79
+ limits on the userOp when the caller omits them (ZeroDev's Zod
80
+ validator requires the gas fields in the request even though
81
+ simulation recomputes them).
82
+
83
+ Added 3 regression tests pinning the new method name, the wrapped
84
+ envelope, the gas-default behaviour + a `config`-error case when
85
+ expectedChainId is missing.
86
+
10
87
  ## [0.2.3] — 2026-05-23
11
88
 
12
89
  ### Fixed
package/dist/broker.cjs CHANGED
@@ -10,7 +10,7 @@ var crypto = require('crypto');
10
10
 
11
11
  var DEFAULT_BACKEND_URL = "https://api.muhaven.app";
12
12
  var DEFAULT_DASHBOARD_URL = "https://muhaven.app";
13
- var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
13
+ var DEFAULT_REQUEST_TIMEOUT_MS = 75e3;
14
14
  var DEFAULT_BROKER_TIMEOUT_MS = 5e3;
15
15
  var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
16
16
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
@@ -2783,7 +2783,7 @@ function printUsage() {
2783
2783
  }
2784
2784
  function getBrokerPackageVersion() {
2785
2785
  {
2786
- return "0.2.3";
2786
+ return "0.2.5";
2787
2787
  }
2788
2788
  }
2789
2789
  function printVersion() {
package/dist/broker.js CHANGED
@@ -12,7 +12,7 @@ var getDirname = () => path.dirname(getFilename());
12
12
  var __dirname$1 = /* @__PURE__ */ getDirname();
13
13
  var DEFAULT_BACKEND_URL = "https://api.muhaven.app";
14
14
  var DEFAULT_DASHBOARD_URL = "https://muhaven.app";
15
- var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
15
+ var DEFAULT_REQUEST_TIMEOUT_MS = 75e3;
16
16
  var DEFAULT_BROKER_TIMEOUT_MS = 5e3;
17
17
  var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
18
18
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
@@ -2785,7 +2785,7 @@ function printUsage() {
2785
2785
  }
2786
2786
  function getBrokerPackageVersion() {
2787
2787
  {
2788
- return "0.2.3";
2788
+ return "0.2.5";
2789
2789
  }
2790
2790
  }
2791
2791
  function printVersion() {
package/dist/index.cjs CHANGED
@@ -22,7 +22,7 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
22
22
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
23
23
  var DEFAULT_BACKEND_URL = "https://api.muhaven.app";
24
24
  var DEFAULT_DASHBOARD_URL = "https://muhaven.app";
25
- var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
25
+ var DEFAULT_REQUEST_TIMEOUT_MS = 75e3;
26
26
  var DEFAULT_BROKER_TIMEOUT_MS = 5e3;
27
27
  var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
28
28
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
@@ -1071,7 +1071,28 @@ var BundlerClient = class {
1071
1071
  * `estimateUserOpGas` round-trip on the happy path).
1072
1072
  */
1073
1073
  async sponsorUserOp(userOp, entryPoint) {
1074
- const result = await this.rpc("pm_sponsorUserOperation", [userOp, entryPoint]);
1074
+ if (this.options.expectedChainId === void 0) {
1075
+ throw new BundlerClientError(
1076
+ "config",
1077
+ "sponsorUserOp requires expectedChainId in BundlerClientOptions \u2014 ZeroDev paymaster needs it in the request envelope"
1078
+ );
1079
+ }
1080
+ const userOpWithGas = {
1081
+ ...userOp,
1082
+ callGasLimit: userOp.callGasLimit ?? "0x100000",
1083
+ // 1_048_576
1084
+ verificationGasLimit: userOp.verificationGasLimit ?? "0x100000",
1085
+ preVerificationGas: userOp.preVerificationGas ?? "0x100000"
1086
+ };
1087
+ const result = await this.rpc("zd_sponsorUserOperation", [
1088
+ {
1089
+ chainId: this.options.expectedChainId,
1090
+ userOp: userOpWithGas,
1091
+ entryPointAddress: entryPoint,
1092
+ shouldOverrideFee: false,
1093
+ shouldConsume: true
1094
+ }
1095
+ ]);
1075
1096
  return parseSponsoredFields(result);
1076
1097
  }
1077
1098
  /**
@@ -1902,7 +1923,7 @@ var SUBSCRIPTION_PURCHASE_SELECTOR = viem.toFunctionSelector(
1902
1923
  var SUBSCRIPTION_PURCHASE_ABI = viem.parseAbi([
1903
1924
  "function purchase(address token, (uint256 ctHash, uint8 securityZone, uint8 utype, bytes signature) encShares, uint128 maxSharesHint, address ephemeralEOA)"
1904
1925
  ]);
1905
- var PLACEHOLDER_SIGNATURE = "0x" + "fe".repeat(86);
1926
+ var PLACEHOLDER_SIGNATURE = "0xff" + "fe".repeat(65);
1906
1927
  function ok(data) {
1907
1928
  return { ok: true, data };
1908
1929
  }
@@ -2646,6 +2667,7 @@ async function positionBuy(input, deps) {
2646
2667
  const navDisplay = formatUsd6AsDecimal(navUsd6);
2647
2668
  const sharesStr = shares.toString();
2648
2669
  let pathDFallbackReason;
2670
+ let pathDFallbackDetail;
2649
2671
  let pathDSubmittedUserOpHash;
2650
2672
  const pathD = await attemptPathD(
2651
2673
  { shares, tokenAddress: token.address, tokenSymbol: token.symbol },
@@ -2656,6 +2678,7 @@ async function positionBuy(input, deps) {
2656
2678
  }
2657
2679
  if (pathD.kind === "fallback") {
2658
2680
  pathDFallbackReason = pathD.reason;
2681
+ pathDFallbackDetail = pathD.message;
2659
2682
  if (pathD.submittedUserOpHash) {
2660
2683
  pathDSubmittedUserOpHash = pathD.submittedUserOpHash;
2661
2684
  }
@@ -2681,6 +2704,7 @@ ${dashboardUrl}`,
2681
2704
  effectiveNotionalUsd6: effectiveNotionalUsd6.toString(),
2682
2705
  navUsd6: navUsd6.toString(),
2683
2706
  ...pathDFallbackReason ? { pathDFallbackReason } : {},
2707
+ ...pathDFallbackDetail ? { pathDFallbackDetail } : {},
2684
2708
  ...pathDSubmittedUserOpHash ? { pathDSubmittedUserOpHash } : {}
2685
2709
  }
2686
2710
  });
@@ -3032,7 +3056,7 @@ var SERVER_NAME = "@muhaven/mcp";
3032
3056
  var SERVER_VERSION = resolveServerVersion();
3033
3057
  function resolveServerVersion() {
3034
3058
  {
3035
- return "0.2.3";
3059
+ return "0.2.5";
3036
3060
  }
3037
3061
  }
3038
3062
  function toJsonInputSchema(schema) {
package/dist/index.d.cts CHANGED
@@ -708,6 +708,17 @@ interface PartialUserOpForSponsorship {
708
708
  readonly maxPriorityFeePerGas: `0x${string}`;
709
709
  /** Worst-case placeholder so paymaster simulates realistic gas. */
710
710
  readonly signature: `0x${string}`;
711
+ /**
712
+ * Optional pre-estimated gas fields. ZeroDev's `zd_sponsorUserOperation`
713
+ * does its own simulation + recomputes these in the response, so
714
+ * passing placeholders is OK — but the upstream Zod validator
715
+ * REQUIRES the fields to be present. `sponsorUserOp` defaults them
716
+ * to safe placeholders when omitted (the response carries the real
717
+ * values for the caller to use).
718
+ */
719
+ readonly callGasLimit?: `0x${string}`;
720
+ readonly verificationGasLimit?: `0x${string}`;
721
+ readonly preVerificationGas?: `0x${string}`;
711
722
  }
712
723
  interface SponsoredUserOpFields {
713
724
  readonly paymaster: `0x${string}`;
package/dist/index.d.ts CHANGED
@@ -708,6 +708,17 @@ interface PartialUserOpForSponsorship {
708
708
  readonly maxPriorityFeePerGas: `0x${string}`;
709
709
  /** Worst-case placeholder so paymaster simulates realistic gas. */
710
710
  readonly signature: `0x${string}`;
711
+ /**
712
+ * Optional pre-estimated gas fields. ZeroDev's `zd_sponsorUserOperation`
713
+ * does its own simulation + recomputes these in the response, so
714
+ * passing placeholders is OK — but the upstream Zod validator
715
+ * REQUIRES the fields to be present. `sponsorUserOp` defaults them
716
+ * to safe placeholders when omitted (the response carries the real
717
+ * values for the caller to use).
718
+ */
719
+ readonly callGasLimit?: `0x${string}`;
720
+ readonly verificationGasLimit?: `0x${string}`;
721
+ readonly preVerificationGas?: `0x${string}`;
711
722
  }
712
723
  interface SponsoredUserOpFields {
713
724
  readonly paymaster: `0x${string}`;
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import { privateKeyToAccount } from 'viem/accounts';
18
18
  // src/server.ts
19
19
  var DEFAULT_BACKEND_URL = "https://api.muhaven.app";
20
20
  var DEFAULT_DASHBOARD_URL = "https://muhaven.app";
21
- var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
21
+ var DEFAULT_REQUEST_TIMEOUT_MS = 75e3;
22
22
  var DEFAULT_BROKER_TIMEOUT_MS = 5e3;
23
23
  var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
24
24
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
@@ -1067,7 +1067,28 @@ var BundlerClient = class {
1067
1067
  * `estimateUserOpGas` round-trip on the happy path).
1068
1068
  */
1069
1069
  async sponsorUserOp(userOp, entryPoint) {
1070
- const result = await this.rpc("pm_sponsorUserOperation", [userOp, entryPoint]);
1070
+ if (this.options.expectedChainId === void 0) {
1071
+ throw new BundlerClientError(
1072
+ "config",
1073
+ "sponsorUserOp requires expectedChainId in BundlerClientOptions \u2014 ZeroDev paymaster needs it in the request envelope"
1074
+ );
1075
+ }
1076
+ const userOpWithGas = {
1077
+ ...userOp,
1078
+ callGasLimit: userOp.callGasLimit ?? "0x100000",
1079
+ // 1_048_576
1080
+ verificationGasLimit: userOp.verificationGasLimit ?? "0x100000",
1081
+ preVerificationGas: userOp.preVerificationGas ?? "0x100000"
1082
+ };
1083
+ const result = await this.rpc("zd_sponsorUserOperation", [
1084
+ {
1085
+ chainId: this.options.expectedChainId,
1086
+ userOp: userOpWithGas,
1087
+ entryPointAddress: entryPoint,
1088
+ shouldOverrideFee: false,
1089
+ shouldConsume: true
1090
+ }
1091
+ ]);
1071
1092
  return parseSponsoredFields(result);
1072
1093
  }
1073
1094
  /**
@@ -1898,7 +1919,7 @@ var SUBSCRIPTION_PURCHASE_SELECTOR = toFunctionSelector(
1898
1919
  var SUBSCRIPTION_PURCHASE_ABI = parseAbi([
1899
1920
  "function purchase(address token, (uint256 ctHash, uint8 securityZone, uint8 utype, bytes signature) encShares, uint128 maxSharesHint, address ephemeralEOA)"
1900
1921
  ]);
1901
- var PLACEHOLDER_SIGNATURE = "0x" + "fe".repeat(86);
1922
+ var PLACEHOLDER_SIGNATURE = "0xff" + "fe".repeat(65);
1902
1923
  function ok(data) {
1903
1924
  return { ok: true, data };
1904
1925
  }
@@ -2642,6 +2663,7 @@ async function positionBuy(input, deps) {
2642
2663
  const navDisplay = formatUsd6AsDecimal(navUsd6);
2643
2664
  const sharesStr = shares.toString();
2644
2665
  let pathDFallbackReason;
2666
+ let pathDFallbackDetail;
2645
2667
  let pathDSubmittedUserOpHash;
2646
2668
  const pathD = await attemptPathD(
2647
2669
  { shares, tokenAddress: token.address, tokenSymbol: token.symbol },
@@ -2652,6 +2674,7 @@ async function positionBuy(input, deps) {
2652
2674
  }
2653
2675
  if (pathD.kind === "fallback") {
2654
2676
  pathDFallbackReason = pathD.reason;
2677
+ pathDFallbackDetail = pathD.message;
2655
2678
  if (pathD.submittedUserOpHash) {
2656
2679
  pathDSubmittedUserOpHash = pathD.submittedUserOpHash;
2657
2680
  }
@@ -2677,6 +2700,7 @@ ${dashboardUrl}`,
2677
2700
  effectiveNotionalUsd6: effectiveNotionalUsd6.toString(),
2678
2701
  navUsd6: navUsd6.toString(),
2679
2702
  ...pathDFallbackReason ? { pathDFallbackReason } : {},
2703
+ ...pathDFallbackDetail ? { pathDFallbackDetail } : {},
2680
2704
  ...pathDSubmittedUserOpHash ? { pathDSubmittedUserOpHash } : {}
2681
2705
  }
2682
2706
  });
@@ -3028,7 +3052,7 @@ var SERVER_NAME = "@muhaven/mcp";
3028
3052
  var SERVER_VERSION = resolveServerVersion();
3029
3053
  function resolveServerVersion() {
3030
3054
  {
3031
- return "0.2.3";
3055
+ return "0.2.5";
3032
3056
  }
3033
3057
  }
3034
3058
  function toJsonInputSchema(schema) {
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.2",
4
4
  "name": "muhaven-mcp",
5
5
  "display_name": "MuHaven (RWA portfolio)",
6
- "version": "0.2.3",
6
+ "version": "0.2.5",
7
7
  "description": "Confidential RWA portfolio management on Fhenix CoFHE. Read your encrypted balances, propose yield claims and policy changes — all signing happens in a sibling broker daemon, the LLM never sees your private key.",
8
8
  "long_description": "MuHaven MCP exposes 24 tools across read.* / position.* / policy.* / issuer.* / governance.* groups for managing real-world asset (RWA) tokens with FHE-encrypted balances. Authentication uses a one-time device-code ceremony (run `muhaven-broker login`); subsequent tool calls fetch the JWT from the broker over a Unix socket. Position / governance tools deep-link to the dashboard for passkey signing — they NEVER auto-submit to a bundler. The companion `muhaven-broker` daemon must be running before tools can be invoked. See README for setup.",
9
9
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhaven/mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "MuHaven MCP server — read/position/policy toolsets bridging Claude Desktop / Cursor / Claude Code to the MuHaven backend, with a sibling muhaven-broker daemon holding the session-key private half over a local IPC socket",
5
5
  "type": "module",
6
6
  "repository": {