@muhaven/mcp 0.2.2 → 0.2.4
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 +56 -0
- package/dist/broker.cjs +1 -1
- package/dist/broker.js +1 -1
- package/dist/index.cjs +38 -4
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +38 -4
- package/manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.4] — 2026-05-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Paymaster RPC method + param shape**:
|
|
15
|
+
`pm_sponsorUserOperation` → `zd_sponsorUserOperation`. ZeroDev v3
|
|
16
|
+
endpoints route paymaster RPCs through Alchemy infrastructure, which
|
|
17
|
+
exposes only ERC-7677 (`pm_getPaymasterStubData` / `pm_getPaymasterData`)
|
|
18
|
+
and ZeroDev-prefixed (`zd_sponsorUserOperation`) — the legacy
|
|
19
|
+
`pm_sponsorUserOperation` returns `"Unsupported method"` from
|
|
20
|
+
Alchemy. The MCP server's `attemptPathD` failed at
|
|
21
|
+
`pathDFallbackReason: paymaster_rejected` on every Path D autonomous
|
|
22
|
+
buy. Fix: switch method name + change request shape from positional
|
|
23
|
+
`[userOp, entryPoint]` to wrapped
|
|
24
|
+
`[{chainId, userOp, entryPointAddress, shouldOverrideFee, shouldConsume}]`.
|
|
25
|
+
Matches `@zerodev/sdk@5.5.10`'s `paymaster/sponsorUserOperation.js`
|
|
26
|
+
byte-for-byte. Verified 2026-05-23 via direct curl reproduction
|
|
27
|
+
against the prod bundler URL (bare → "Unsupported method"; correct
|
|
28
|
+
shape → simulation result with AA23 from synthetic UserOp, proving
|
|
29
|
+
the method works).
|
|
30
|
+
|
|
31
|
+
`sponsorUserOp` now requires `expectedChainId` in `BundlerClientOptions`
|
|
32
|
+
(throws `BundlerClientError(config)` when missing) — the chainId is
|
|
33
|
+
part of the request envelope. Defaults conservative placeholder gas
|
|
34
|
+
limits on the userOp when the caller omits them (ZeroDev's Zod
|
|
35
|
+
validator requires the gas fields in the request even though
|
|
36
|
+
simulation recomputes them).
|
|
37
|
+
|
|
38
|
+
Added 3 regression tests pinning the new method name, the wrapped
|
|
39
|
+
envelope, the gas-default behaviour + a `config`-error case when
|
|
40
|
+
expectedChainId is missing.
|
|
41
|
+
|
|
42
|
+
## [0.2.3] — 2026-05-23
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **`BundlerClient` now sends an `Origin` header on every RPC.** ZeroDev
|
|
47
|
+
bundler URLs gate access via an IP+domain allowlist; browser requests
|
|
48
|
+
from `https://muhaven.app` pass because the project's allowlist
|
|
49
|
+
accepts that domain, but Node `fetch` (the MCP server's transport)
|
|
50
|
+
sends no `Origin` by default and so hit a `403 "Neither IP nor domain
|
|
51
|
+
is on the allowlist"` on every `eth_call` / `eth_gasPrice`. The MCP
|
|
52
|
+
server then surfaced this as `pathDFallbackReason:
|
|
53
|
+
bundler_setup_failed` and degraded to Path C. Fix: stamp `Origin:
|
|
54
|
+
<MUHAVEN_DASHBOARD_URL>` on every bundler RPC (defaults to
|
|
55
|
+
`https://muhaven.app`, threaded through from
|
|
56
|
+
`buildMcpServer({ dashboardBaseUrl })`). Mirrors how ethers.js + viem
|
|
57
|
+
stamp default Origins against EVM RPC providers; surfaces a single
|
|
58
|
+
knob (`MUHAVEN_DASHBOARD_URL`) for operators on a custom domain.
|
|
59
|
+
Diagnosed 2026-05-23 via direct curl reproduction (bare → 403; with
|
|
60
|
+
`Origin: https://muhaven.app` → `result: 0x66eee`).
|
|
61
|
+
|
|
62
|
+
Added 3 regression tests pinning the contract (Origin sent when set;
|
|
63
|
+
omitted when undefined; omitted when explicitly empty for test
|
|
64
|
+
injection).
|
|
65
|
+
|
|
10
66
|
## [0.2.2] — 2026-05-23
|
|
11
67
|
|
|
12
68
|
### Fixed
|
package/dist/broker.cjs
CHANGED
package/dist/broker.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
|
@@ -1194,9 +1215,16 @@ var BundlerClient = class {
|
|
|
1194
1215
|
const timer = setTimeout(() => ctrl.abort(), this.options.requestTimeoutMs);
|
|
1195
1216
|
let res;
|
|
1196
1217
|
try {
|
|
1218
|
+
const headers = {
|
|
1219
|
+
"content-type": "application/json",
|
|
1220
|
+
accept: "application/json"
|
|
1221
|
+
};
|
|
1222
|
+
if (this.options.originHeader) {
|
|
1223
|
+
headers["origin"] = this.options.originHeader;
|
|
1224
|
+
}
|
|
1197
1225
|
res = await this.fetchImpl(this.options.endpoint, {
|
|
1198
1226
|
method: "POST",
|
|
1199
|
-
headers
|
|
1227
|
+
headers,
|
|
1200
1228
|
body,
|
|
1201
1229
|
signal: ctrl.signal
|
|
1202
1230
|
});
|
|
@@ -3025,7 +3053,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
3025
3053
|
var SERVER_VERSION = resolveServerVersion();
|
|
3026
3054
|
function resolveServerVersion() {
|
|
3027
3055
|
{
|
|
3028
|
-
return "0.2.
|
|
3056
|
+
return "0.2.4";
|
|
3029
3057
|
}
|
|
3030
3058
|
}
|
|
3031
3059
|
function toJsonInputSchema(schema) {
|
|
@@ -3166,7 +3194,13 @@ async function runMcpStdioCli(opts = {}) {
|
|
|
3166
3194
|
const bundler = config.bundlerUrl ? new BundlerClient({
|
|
3167
3195
|
endpoint: config.bundlerUrl,
|
|
3168
3196
|
requestTimeoutMs: config.bundlerTimeoutMs,
|
|
3169
|
-
expectedChainId: config.chainId
|
|
3197
|
+
expectedChainId: config.chainId,
|
|
3198
|
+
// Wave 5 Path D 0.2.3 — Origin defaults to the dashboard URL
|
|
3199
|
+
// so ZeroDev's domain-allowlist accepts the MCP server's RPC
|
|
3200
|
+
// traffic. The dashboard URL is the natural match because it's
|
|
3201
|
+
// also the SIWE / passkey origin the project already trusts
|
|
3202
|
+
// for browser-side traffic.
|
|
3203
|
+
originHeader: config.dashboardBaseUrl
|
|
3170
3204
|
}) : void 0;
|
|
3171
3205
|
const baseRegistry = selectRegistry(config.readOnly);
|
|
3172
3206
|
const registry = opts.filterRegistry ? opts.filterRegistry(baseRegistry) : baseRegistry;
|
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}`;
|
|
@@ -753,6 +764,24 @@ interface BundlerClientOptions {
|
|
|
753
764
|
/** Expected chain id (Arb Sepolia = 421614). When set, `assertChainId()`
|
|
754
765
|
* refuses to proceed if the bundler reports a different chain. */
|
|
755
766
|
readonly expectedChainId?: number;
|
|
767
|
+
/**
|
|
768
|
+
* Wave 5 Path D 0.2.3 — `Origin` header sent on every bundler RPC.
|
|
769
|
+
*
|
|
770
|
+
* Why: ZeroDev's bundler URLs gate access via an IP+domain allowlist.
|
|
771
|
+
* Browser requests from `https://muhaven.app` pass because the
|
|
772
|
+
* project's allowlist includes that domain; Node `fetch` (the MCP
|
|
773
|
+
* server's transport) sends no `Origin` header by default and so
|
|
774
|
+
* hits a 403 "Neither IP nor domain is on the allowlist". Sending
|
|
775
|
+
* an `Origin` matching the project's allowlisted domain unblocks
|
|
776
|
+
* the MCP server without requiring an operator-side ZeroDev
|
|
777
|
+
* dashboard edit. Mirrors how ethers.js + viem's HTTP transports
|
|
778
|
+
* stamp a default `Origin` against EVM RPC providers.
|
|
779
|
+
*
|
|
780
|
+
* Defaults to `https://muhaven.app` at the call site
|
|
781
|
+
* (`server.ts::buildMcpServer`) — operators on a custom dashboard
|
|
782
|
+
* URL override via `MUHAVEN_DASHBOARD_URL`.
|
|
783
|
+
*/
|
|
784
|
+
readonly originHeader?: string;
|
|
756
785
|
/** Inject for tests. */
|
|
757
786
|
readonly fetchImpl?: typeof fetch;
|
|
758
787
|
}
|
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}`;
|
|
@@ -753,6 +764,24 @@ interface BundlerClientOptions {
|
|
|
753
764
|
/** Expected chain id (Arb Sepolia = 421614). When set, `assertChainId()`
|
|
754
765
|
* refuses to proceed if the bundler reports a different chain. */
|
|
755
766
|
readonly expectedChainId?: number;
|
|
767
|
+
/**
|
|
768
|
+
* Wave 5 Path D 0.2.3 — `Origin` header sent on every bundler RPC.
|
|
769
|
+
*
|
|
770
|
+
* Why: ZeroDev's bundler URLs gate access via an IP+domain allowlist.
|
|
771
|
+
* Browser requests from `https://muhaven.app` pass because the
|
|
772
|
+
* project's allowlist includes that domain; Node `fetch` (the MCP
|
|
773
|
+
* server's transport) sends no `Origin` header by default and so
|
|
774
|
+
* hits a 403 "Neither IP nor domain is on the allowlist". Sending
|
|
775
|
+
* an `Origin` matching the project's allowlisted domain unblocks
|
|
776
|
+
* the MCP server without requiring an operator-side ZeroDev
|
|
777
|
+
* dashboard edit. Mirrors how ethers.js + viem's HTTP transports
|
|
778
|
+
* stamp a default `Origin` against EVM RPC providers.
|
|
779
|
+
*
|
|
780
|
+
* Defaults to `https://muhaven.app` at the call site
|
|
781
|
+
* (`server.ts::buildMcpServer`) — operators on a custom dashboard
|
|
782
|
+
* URL override via `MUHAVEN_DASHBOARD_URL`.
|
|
783
|
+
*/
|
|
784
|
+
readonly originHeader?: string;
|
|
756
785
|
/** Inject for tests. */
|
|
757
786
|
readonly fetchImpl?: typeof fetch;
|
|
758
787
|
}
|
package/dist/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
|
@@ -1190,9 +1211,16 @@ var BundlerClient = class {
|
|
|
1190
1211
|
const timer = setTimeout(() => ctrl.abort(), this.options.requestTimeoutMs);
|
|
1191
1212
|
let res;
|
|
1192
1213
|
try {
|
|
1214
|
+
const headers = {
|
|
1215
|
+
"content-type": "application/json",
|
|
1216
|
+
accept: "application/json"
|
|
1217
|
+
};
|
|
1218
|
+
if (this.options.originHeader) {
|
|
1219
|
+
headers["origin"] = this.options.originHeader;
|
|
1220
|
+
}
|
|
1193
1221
|
res = await this.fetchImpl(this.options.endpoint, {
|
|
1194
1222
|
method: "POST",
|
|
1195
|
-
headers
|
|
1223
|
+
headers,
|
|
1196
1224
|
body,
|
|
1197
1225
|
signal: ctrl.signal
|
|
1198
1226
|
});
|
|
@@ -3021,7 +3049,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
3021
3049
|
var SERVER_VERSION = resolveServerVersion();
|
|
3022
3050
|
function resolveServerVersion() {
|
|
3023
3051
|
{
|
|
3024
|
-
return "0.2.
|
|
3052
|
+
return "0.2.4";
|
|
3025
3053
|
}
|
|
3026
3054
|
}
|
|
3027
3055
|
function toJsonInputSchema(schema) {
|
|
@@ -3162,7 +3190,13 @@ async function runMcpStdioCli(opts = {}) {
|
|
|
3162
3190
|
const bundler = config.bundlerUrl ? new BundlerClient({
|
|
3163
3191
|
endpoint: config.bundlerUrl,
|
|
3164
3192
|
requestTimeoutMs: config.bundlerTimeoutMs,
|
|
3165
|
-
expectedChainId: config.chainId
|
|
3193
|
+
expectedChainId: config.chainId,
|
|
3194
|
+
// Wave 5 Path D 0.2.3 — Origin defaults to the dashboard URL
|
|
3195
|
+
// so ZeroDev's domain-allowlist accepts the MCP server's RPC
|
|
3196
|
+
// traffic. The dashboard URL is the natural match because it's
|
|
3197
|
+
// also the SIWE / passkey origin the project already trusts
|
|
3198
|
+
// for browser-side traffic.
|
|
3199
|
+
originHeader: config.dashboardBaseUrl
|
|
3166
3200
|
}) : void 0;
|
|
3167
3201
|
const baseRegistry = selectRegistry(config.readOnly);
|
|
3168
3202
|
const registry = opts.filterRegistry ? opts.filterRegistry(baseRegistry) : baseRegistry;
|
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.
|
|
6
|
+
"version": "0.2.4",
|
|
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
|
+
"version": "0.2.4",
|
|
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": {
|