@muhaven/mcp 0.2.5 → 0.2.7
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 +71 -0
- package/dist/broker.cjs +1 -1
- package/dist/broker.js +1 -1
- package/dist/index.cjs +44 -3
- package/dist/index.js +44 -3
- package/manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.7] — 2026-05-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Startup banner (always on).** `runMcpStdioCli` writes one stderr
|
|
15
|
+
line at boot with the running version + verbose mode:
|
|
16
|
+
|
|
17
|
+
[muhaven-mcp] starting @muhaven/mcp@0.2.7 (verbose=off)
|
|
18
|
+
|
|
19
|
+
Multiple smoke iterations have been ambiguous about whether
|
|
20
|
+
`npm i -g @muhaven/mcp@<v>` actually updated the global binary
|
|
21
|
+
picked up by Claude Code (the subprocess only re-spawns on FULL
|
|
22
|
+
Claude Code restart, not `/mcp reconnect`). One stderr line at boot
|
|
23
|
+
makes the version mismatch impossible to miss.
|
|
24
|
+
|
|
25
|
+
- **Verbose paymaster/bundler logging (`MUHAVEN_MCP_VERBOSE=1`).**
|
|
26
|
+
Gated env var that emits two stderr lines per bundler RPC:
|
|
27
|
+
|
|
28
|
+
[muhaven-mcp] [bundler→] zd_sponsorUserOperation id=N body={...}
|
|
29
|
+
[muhaven-mcp] [bundler←] zd_sponsorUserOperation id=N resp={...}
|
|
30
|
+
|
|
31
|
+
Bodies truncated at 2KB; covers happy-path, http_error, timeout,
|
|
32
|
+
non-JSON, network failures. Add `"MUHAVEN_MCP_VERBOSE": "1"` to the
|
|
33
|
+
`.mcp.json` env block when triaging a `pathDFallbackReason` —
|
|
34
|
+
removes the need for curl repro entirely.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **Stale `pm_sponsorUserOperation` label in the
|
|
39
|
+
`paymaster_rejected` fallback message** (0.2.4 leftover). The
|
|
40
|
+
actual method call has been `zd_sponsorUserOperation` since 0.2.4,
|
|
41
|
+
but the error message label still said `pm_*`. No functional
|
|
42
|
+
impact — just a confusing log string when the gate fires.
|
|
43
|
+
|
|
44
|
+
## [0.2.6] — 2026-05-23
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`PLACEHOLDER_SIGNATURE` uses exact `@zerodev/sdk::DUMMY_ECDSA_SIG`
|
|
49
|
+
bytes** — NOT random `0xfe`-filled high-entropy bytes (the 0.2.5
|
|
50
|
+
regression). Per `@zerodev/permissions::toPermissionValidator.js`,
|
|
51
|
+
the canonical stub signature for paymaster simulation is:
|
|
52
|
+
|
|
53
|
+
concat(["0xff", signer.getDummySignature()])
|
|
54
|
+
↓ ↓
|
|
55
|
+
"use root permission" DUMMY_ECDSA_SIG = "0xfffffff...7aa...aaa...1c"
|
|
56
|
+
|
|
57
|
+
The DUMMY_ECDSA_SIG is a CRAFTED 65-byte pattern (r is high-end of
|
|
58
|
+
secp256k1's field, s is `7aa...aaa`, v is `0x1c`) that the
|
|
59
|
+
PermissionValidator's `validateUserOp` simulation path recognizes
|
|
60
|
+
as a stub and skips real ecrecover. 0.2.5 had the right length (66
|
|
61
|
+
bytes) and the right `0xff` prefix, but filled the trailing 65
|
|
62
|
+
bytes with random `0xfe` — the validator ecrecovers them as if
|
|
63
|
+
real, gets a garbage address that doesn't match the bound session-
|
|
64
|
+
key, reverts with `AA23` → paymaster returns rpc_error → MCP maps
|
|
65
|
+
to `paymaster_rejected`.
|
|
66
|
+
|
|
67
|
+
The new `pathDFallbackDetail` echo (0.2.5) made this trivially
|
|
68
|
+
diagnosable on the very next smoke iteration — the surfaced
|
|
69
|
+
message was `zd_sponsorUserOperation → HTTP 400 → AA23 reverted`
|
|
70
|
+
which pinned the validator-revert layer.
|
|
71
|
+
|
|
72
|
+
Verified 2026-05-23 against `@zerodev/sdk@5.5.10`'s
|
|
73
|
+
`_cjs/constants.js::DUMMY_ECDSA_SIG` and
|
|
74
|
+
`@zerodev/permissions/_cjs/toPermissionValidator.js::getStubSignature`.
|
|
75
|
+
|
|
76
|
+
Regression tests pin: byte length (66), `0xff` prefix, trailing
|
|
77
|
+
65-byte byte-for-byte match against DUMMY_ECDSA_SIG, v=0x1c,
|
|
78
|
+
s-component magic pattern (rejecting the 0.2.5 `0xfe`-filled
|
|
79
|
+
shape).
|
|
80
|
+
|
|
10
81
|
## [0.2.5] — 2026-05-23
|
|
11
82
|
|
|
12
83
|
### Fixed
|
package/dist/broker.cjs
CHANGED
package/dist/broker.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1211,6 +1211,14 @@ var BundlerClient = class {
|
|
|
1211
1211
|
async rpc(method, params) {
|
|
1212
1212
|
const id = this.nextRpcId++;
|
|
1213
1213
|
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
1214
|
+
const verbose = process.env.MUHAVEN_MCP_VERBOSE === "1";
|
|
1215
|
+
if (verbose) {
|
|
1216
|
+
const bodyDump = body.length > 2048 ? body.slice(0, 2048) + "\u2026(truncated)" : body;
|
|
1217
|
+
process.stderr.write(
|
|
1218
|
+
`[muhaven-mcp] [bundler\u2192] ${method} id=${id} body=${bodyDump}
|
|
1219
|
+
`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1214
1222
|
const ctrl = new AbortController();
|
|
1215
1223
|
const timer = setTimeout(() => ctrl.abort(), this.options.requestTimeoutMs);
|
|
1216
1224
|
let res;
|
|
@@ -1231,8 +1239,18 @@ var BundlerClient = class {
|
|
|
1231
1239
|
} catch (err2) {
|
|
1232
1240
|
clearTimeout(timer);
|
|
1233
1241
|
if (err2.name === "AbortError") {
|
|
1242
|
+
if (verbose) {
|
|
1243
|
+
process.stderr.write(`[muhaven-mcp] [bundler\u2717] ${method} id=${id} timeout
|
|
1244
|
+
`);
|
|
1245
|
+
}
|
|
1234
1246
|
throw new BundlerClientError("timeout", `bundler ${method} timed out`);
|
|
1235
1247
|
}
|
|
1248
|
+
if (verbose) {
|
|
1249
|
+
process.stderr.write(
|
|
1250
|
+
`[muhaven-mcp] [bundler\u2717] ${method} id=${id} network err=${err2 instanceof Error ? err2.message : String(err2)}
|
|
1251
|
+
`
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1236
1254
|
throw new BundlerClientError(
|
|
1237
1255
|
"network",
|
|
1238
1256
|
`bundler ${method} network error: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
@@ -1247,6 +1265,12 @@ var BundlerClient = class {
|
|
|
1247
1265
|
text = (await res.text()).slice(0, 256);
|
|
1248
1266
|
} catch {
|
|
1249
1267
|
}
|
|
1268
|
+
if (verbose) {
|
|
1269
|
+
process.stderr.write(
|
|
1270
|
+
`[muhaven-mcp] [bundler\u2717] ${method} id=${id} HTTP ${res.status} body=${text}
|
|
1271
|
+
`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1250
1274
|
throw new BundlerClientError(
|
|
1251
1275
|
"http_error",
|
|
1252
1276
|
`bundler ${method} \u2192 HTTP ${res.status}: ${text}`
|
|
@@ -1256,11 +1280,23 @@ var BundlerClient = class {
|
|
|
1256
1280
|
try {
|
|
1257
1281
|
parsed = await res.json();
|
|
1258
1282
|
} catch (err2) {
|
|
1283
|
+
if (verbose) {
|
|
1284
|
+
process.stderr.write(
|
|
1285
|
+
`[muhaven-mcp] [bundler\u2717] ${method} id=${id} non-JSON err=${err2 instanceof Error ? err2.message : String(err2)}
|
|
1286
|
+
`
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1259
1289
|
throw new BundlerClientError(
|
|
1260
1290
|
"invalid_response",
|
|
1261
1291
|
`bundler ${method} returned non-JSON: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
1262
1292
|
);
|
|
1263
1293
|
}
|
|
1294
|
+
if (verbose) {
|
|
1295
|
+
const respDump = JSON.stringify(parsed);
|
|
1296
|
+
const trunc = respDump.length > 2048 ? respDump.slice(0, 2048) + "\u2026(truncated)" : respDump;
|
|
1297
|
+
process.stderr.write(`[muhaven-mcp] [bundler\u2190] ${method} id=${id} resp=${trunc}
|
|
1298
|
+
`);
|
|
1299
|
+
}
|
|
1264
1300
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1265
1301
|
throw new BundlerClientError(
|
|
1266
1302
|
"invalid_response",
|
|
@@ -1923,7 +1959,8 @@ var SUBSCRIPTION_PURCHASE_SELECTOR = viem.toFunctionSelector(
|
|
|
1923
1959
|
var SUBSCRIPTION_PURCHASE_ABI = viem.parseAbi([
|
|
1924
1960
|
"function purchase(address token, (uint256 ctHash, uint8 securityZone, uint8 utype, bytes signature) encShares, uint128 maxSharesHint, address ephemeralEOA)"
|
|
1925
1961
|
]);
|
|
1926
|
-
var
|
|
1962
|
+
var ZERODEV_DUMMY_ECDSA_SIG = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";
|
|
1963
|
+
var PLACEHOLDER_SIGNATURE = `0xff${ZERODEV_DUMMY_ECDSA_SIG.slice(2)}`;
|
|
1927
1964
|
function ok(data) {
|
|
1928
1965
|
return { ok: true, data };
|
|
1929
1966
|
}
|
|
@@ -2461,7 +2498,7 @@ async function attemptPathD(args, deps) {
|
|
|
2461
2498
|
return {
|
|
2462
2499
|
kind: "fallback",
|
|
2463
2500
|
reason: "paymaster_rejected",
|
|
2464
|
-
message: `
|
|
2501
|
+
message: `zd_sponsorUserOperation rejected${detail}: ${safeMsg}`
|
|
2465
2502
|
};
|
|
2466
2503
|
}
|
|
2467
2504
|
const userOpForHash = {
|
|
@@ -3056,7 +3093,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
3056
3093
|
var SERVER_VERSION = resolveServerVersion();
|
|
3057
3094
|
function resolveServerVersion() {
|
|
3058
3095
|
{
|
|
3059
|
-
return "0.2.
|
|
3096
|
+
return "0.2.7";
|
|
3060
3097
|
}
|
|
3061
3098
|
}
|
|
3062
3099
|
function toJsonInputSchema(schema) {
|
|
@@ -3175,6 +3212,10 @@ function toolJsonResponse(payload) {
|
|
|
3175
3212
|
}
|
|
3176
3213
|
async function runMcpStdioCli(opts = {}) {
|
|
3177
3214
|
const config = loadMcpConfig();
|
|
3215
|
+
process.stderr.write(
|
|
3216
|
+
`[muhaven-mcp] starting @muhaven/mcp@${SERVER_VERSION} (verbose=${process.env.MUHAVEN_MCP_VERBOSE === "1" ? "on" : "off"})
|
|
3217
|
+
`
|
|
3218
|
+
);
|
|
3178
3219
|
const pinned = await loadPinnedToolHashes();
|
|
3179
3220
|
const verify = verifyToolHashes(pinned);
|
|
3180
3221
|
if (!verify.ok) {
|
package/dist/index.js
CHANGED
|
@@ -1207,6 +1207,14 @@ var BundlerClient = class {
|
|
|
1207
1207
|
async rpc(method, params) {
|
|
1208
1208
|
const id = this.nextRpcId++;
|
|
1209
1209
|
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
1210
|
+
const verbose = process.env.MUHAVEN_MCP_VERBOSE === "1";
|
|
1211
|
+
if (verbose) {
|
|
1212
|
+
const bodyDump = body.length > 2048 ? body.slice(0, 2048) + "\u2026(truncated)" : body;
|
|
1213
|
+
process.stderr.write(
|
|
1214
|
+
`[muhaven-mcp] [bundler\u2192] ${method} id=${id} body=${bodyDump}
|
|
1215
|
+
`
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1210
1218
|
const ctrl = new AbortController();
|
|
1211
1219
|
const timer = setTimeout(() => ctrl.abort(), this.options.requestTimeoutMs);
|
|
1212
1220
|
let res;
|
|
@@ -1227,8 +1235,18 @@ var BundlerClient = class {
|
|
|
1227
1235
|
} catch (err2) {
|
|
1228
1236
|
clearTimeout(timer);
|
|
1229
1237
|
if (err2.name === "AbortError") {
|
|
1238
|
+
if (verbose) {
|
|
1239
|
+
process.stderr.write(`[muhaven-mcp] [bundler\u2717] ${method} id=${id} timeout
|
|
1240
|
+
`);
|
|
1241
|
+
}
|
|
1230
1242
|
throw new BundlerClientError("timeout", `bundler ${method} timed out`);
|
|
1231
1243
|
}
|
|
1244
|
+
if (verbose) {
|
|
1245
|
+
process.stderr.write(
|
|
1246
|
+
`[muhaven-mcp] [bundler\u2717] ${method} id=${id} network err=${err2 instanceof Error ? err2.message : String(err2)}
|
|
1247
|
+
`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1232
1250
|
throw new BundlerClientError(
|
|
1233
1251
|
"network",
|
|
1234
1252
|
`bundler ${method} network error: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
@@ -1243,6 +1261,12 @@ var BundlerClient = class {
|
|
|
1243
1261
|
text = (await res.text()).slice(0, 256);
|
|
1244
1262
|
} catch {
|
|
1245
1263
|
}
|
|
1264
|
+
if (verbose) {
|
|
1265
|
+
process.stderr.write(
|
|
1266
|
+
`[muhaven-mcp] [bundler\u2717] ${method} id=${id} HTTP ${res.status} body=${text}
|
|
1267
|
+
`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1246
1270
|
throw new BundlerClientError(
|
|
1247
1271
|
"http_error",
|
|
1248
1272
|
`bundler ${method} \u2192 HTTP ${res.status}: ${text}`
|
|
@@ -1252,11 +1276,23 @@ var BundlerClient = class {
|
|
|
1252
1276
|
try {
|
|
1253
1277
|
parsed = await res.json();
|
|
1254
1278
|
} catch (err2) {
|
|
1279
|
+
if (verbose) {
|
|
1280
|
+
process.stderr.write(
|
|
1281
|
+
`[muhaven-mcp] [bundler\u2717] ${method} id=${id} non-JSON err=${err2 instanceof Error ? err2.message : String(err2)}
|
|
1282
|
+
`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1255
1285
|
throw new BundlerClientError(
|
|
1256
1286
|
"invalid_response",
|
|
1257
1287
|
`bundler ${method} returned non-JSON: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
1258
1288
|
);
|
|
1259
1289
|
}
|
|
1290
|
+
if (verbose) {
|
|
1291
|
+
const respDump = JSON.stringify(parsed);
|
|
1292
|
+
const trunc = respDump.length > 2048 ? respDump.slice(0, 2048) + "\u2026(truncated)" : respDump;
|
|
1293
|
+
process.stderr.write(`[muhaven-mcp] [bundler\u2190] ${method} id=${id} resp=${trunc}
|
|
1294
|
+
`);
|
|
1295
|
+
}
|
|
1260
1296
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1261
1297
|
throw new BundlerClientError(
|
|
1262
1298
|
"invalid_response",
|
|
@@ -1919,7 +1955,8 @@ var SUBSCRIPTION_PURCHASE_SELECTOR = toFunctionSelector(
|
|
|
1919
1955
|
var SUBSCRIPTION_PURCHASE_ABI = parseAbi([
|
|
1920
1956
|
"function purchase(address token, (uint256 ctHash, uint8 securityZone, uint8 utype, bytes signature) encShares, uint128 maxSharesHint, address ephemeralEOA)"
|
|
1921
1957
|
]);
|
|
1922
|
-
var
|
|
1958
|
+
var ZERODEV_DUMMY_ECDSA_SIG = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";
|
|
1959
|
+
var PLACEHOLDER_SIGNATURE = `0xff${ZERODEV_DUMMY_ECDSA_SIG.slice(2)}`;
|
|
1923
1960
|
function ok(data) {
|
|
1924
1961
|
return { ok: true, data };
|
|
1925
1962
|
}
|
|
@@ -2457,7 +2494,7 @@ async function attemptPathD(args, deps) {
|
|
|
2457
2494
|
return {
|
|
2458
2495
|
kind: "fallback",
|
|
2459
2496
|
reason: "paymaster_rejected",
|
|
2460
|
-
message: `
|
|
2497
|
+
message: `zd_sponsorUserOperation rejected${detail}: ${safeMsg}`
|
|
2461
2498
|
};
|
|
2462
2499
|
}
|
|
2463
2500
|
const userOpForHash = {
|
|
@@ -3052,7 +3089,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
3052
3089
|
var SERVER_VERSION = resolveServerVersion();
|
|
3053
3090
|
function resolveServerVersion() {
|
|
3054
3091
|
{
|
|
3055
|
-
return "0.2.
|
|
3092
|
+
return "0.2.7";
|
|
3056
3093
|
}
|
|
3057
3094
|
}
|
|
3058
3095
|
function toJsonInputSchema(schema) {
|
|
@@ -3171,6 +3208,10 @@ function toolJsonResponse(payload) {
|
|
|
3171
3208
|
}
|
|
3172
3209
|
async function runMcpStdioCli(opts = {}) {
|
|
3173
3210
|
const config = loadMcpConfig();
|
|
3211
|
+
process.stderr.write(
|
|
3212
|
+
`[muhaven-mcp] starting @muhaven/mcp@${SERVER_VERSION} (verbose=${process.env.MUHAVEN_MCP_VERBOSE === "1" ? "on" : "off"})
|
|
3213
|
+
`
|
|
3214
|
+
);
|
|
3174
3215
|
const pinned = await loadPinnedToolHashes();
|
|
3175
3216
|
const verify = verifyToolHashes(pinned);
|
|
3176
3217
|
if (!verify.ok) {
|
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.7",
|
|
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.7",
|
|
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": {
|