@muhaven/mcp 0.4.1 → 0.4.3
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 +36 -0
- package/dist/broker.cjs +31 -4
- package/dist/broker.js +31 -4
- package/dist/index.cjs +16 -1
- package/dist/index.js +16 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.3] — 2026-05-24
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **`muhaven-broker setup` no longer silently uses an opaque env key.** When
|
|
15
|
+
`MUHAVEN_BROKER_SESSION_KEY` is set (its "scriptable" precedence over the
|
|
16
|
+
interactive prompt), setup now (a) **validates the key's shape up front**
|
|
17
|
+
— a malformed value fails with a clear error + an unset hint instead of a
|
|
18
|
+
confusing daemon crash later — and (b) **prints the derived signer
|
|
19
|
+
address**: `Session key: using MUHAVEN_BROKER_SESSION_KEY from env (signer
|
|
20
|
+
0x…)`, plus a one-line "unset it to paste a dashboard key instead" hint. So
|
|
21
|
+
a STALE/unintended lingering env key is obvious rather than silently
|
|
22
|
+
adopted. The pasted-key and self-mint paths print the signer too, matching
|
|
23
|
+
`start`/`update`'s `Broker signer: 0x…`. New exported `deriveSignerAddress`
|
|
24
|
+
helper (display-only; never logs the private key). Operator-found on 0.4.2.
|
|
25
|
+
|
|
26
|
+
## [0.4.2] — 2026-05-24
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **Revoke kill-switch — MCP-side gate (defense-in-depth).** A revoked
|
|
31
|
+
Scoped session let an already-running broker keep buying autonomously
|
|
32
|
+
(the broker signs from a local snapshot with no "revoked" concept and the
|
|
33
|
+
on-chain validator stays installed). `position.buy` Path D now hard-gates
|
|
34
|
+
on the backend mirror: when the broker hands over an active snapshot but
|
|
35
|
+
`GET /agent/policy/scoped-session` reports NO active session (= revoked or
|
|
36
|
+
expired on the dashboard), the buy refuses with the new
|
|
37
|
+
`pathDFallbackReason: 'session_revoked'`, best-effort calls the broker's
|
|
38
|
+
`clear_policy_snapshot` to purge the dormant key-backed snapshot, and
|
|
39
|
+
falls back to the Path C deep-link. A transient mirror-fetch ERROR is
|
|
40
|
+
still treated as best-effort (not "revoked") so a backend blip doesn't
|
|
41
|
+
break Path D. The authoritative enforcement is the server-side
|
|
42
|
+
`encrypt-shares` gate (backend change, same release window); this MCP
|
|
43
|
+
check is the fast, clear fail before the encrypt round-trip. SecEng
|
|
44
|
+
investigation 2026-05-24.
|
|
45
|
+
|
|
10
46
|
## [0.4.1] — 2026-05-24
|
|
11
47
|
|
|
12
48
|
### Added
|
package/dist/broker.cjs
CHANGED
|
@@ -2457,6 +2457,13 @@ function applyEnvDefaults(input) {
|
|
|
2457
2457
|
function mintSessionKey() {
|
|
2458
2458
|
return accounts.generatePrivateKey();
|
|
2459
2459
|
}
|
|
2460
|
+
function deriveSignerAddress(privateKeyHex) {
|
|
2461
|
+
try {
|
|
2462
|
+
return accounts.privateKeyToAccount(privateKeyHex).address;
|
|
2463
|
+
} catch {
|
|
2464
|
+
return null;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2460
2467
|
function decideSetupAction(input) {
|
|
2461
2468
|
if (input.hello === null) return "spawn_and_login";
|
|
2462
2469
|
if (!input.hello.hasJwt) return "login_only";
|
|
@@ -2793,7 +2800,21 @@ async function runSetup(argv, deps) {
|
|
|
2793
2800
|
let sessionKey = effectiveEnv.MUHAVEN_BROKER_SESSION_KEY;
|
|
2794
2801
|
let mintedKey = false;
|
|
2795
2802
|
if (sessionKey && sessionKey.length > 0) {
|
|
2796
|
-
|
|
2803
|
+
const shapeErr = validateSessionKeyShape(sessionKey);
|
|
2804
|
+
if (shapeErr) {
|
|
2805
|
+
deps.printErr(`error: MUHAVEN_BROKER_SESSION_KEY is set but invalid \u2014 ${shapeErr}`);
|
|
2806
|
+
deps.printErr(
|
|
2807
|
+
" Unset MUHAVEN_BROKER_SESSION_KEY to paste a dashboard key (or fix the value), then re-run setup."
|
|
2808
|
+
);
|
|
2809
|
+
return 2;
|
|
2810
|
+
}
|
|
2811
|
+
const signer = deriveSignerAddress(sessionKey);
|
|
2812
|
+
deps.print(
|
|
2813
|
+
`Session key: using MUHAVEN_BROKER_SESSION_KEY from env${signer ? ` (signer ${signer})` : ""}.`
|
|
2814
|
+
);
|
|
2815
|
+
deps.print(
|
|
2816
|
+
" To paste a dashboard-minted key instead, unset MUHAVEN_BROKER_SESSION_KEY first."
|
|
2817
|
+
);
|
|
2797
2818
|
} else {
|
|
2798
2819
|
const sessionInput = deps.sessionInput ?? {
|
|
2799
2820
|
isTty: false,
|
|
@@ -2812,11 +2833,17 @@ async function runSetup(argv, deps) {
|
|
|
2812
2833
|
}
|
|
2813
2834
|
if (resolution.kind === "key") {
|
|
2814
2835
|
sessionKey = resolution.key;
|
|
2815
|
-
|
|
2836
|
+
const signer = deriveSignerAddress(sessionKey);
|
|
2837
|
+
deps.print(
|
|
2838
|
+
`Session key: using the pasted dashboard key${signer ? ` (signer ${signer})` : ""}.`
|
|
2839
|
+
);
|
|
2816
2840
|
} else {
|
|
2817
2841
|
sessionKey = deps.mintSessionKey();
|
|
2818
2842
|
mintedKey = true;
|
|
2819
|
-
|
|
2843
|
+
const signer = deriveSignerAddress(sessionKey);
|
|
2844
|
+
deps.print(
|
|
2845
|
+
`Session key: minted fresh (secp256k1, ephemeral to this daemon)${signer ? ` \u2014 signer ${signer}` : ""}.`
|
|
2846
|
+
);
|
|
2820
2847
|
}
|
|
2821
2848
|
}
|
|
2822
2849
|
effectiveEnv.MUHAVEN_BROKER_SESSION_KEY = sessionKey;
|
|
@@ -3591,7 +3618,7 @@ function printUsage() {
|
|
|
3591
3618
|
}
|
|
3592
3619
|
function getBrokerPackageVersion() {
|
|
3593
3620
|
{
|
|
3594
|
-
return "0.4.
|
|
3621
|
+
return "0.4.3";
|
|
3595
3622
|
}
|
|
3596
3623
|
}
|
|
3597
3624
|
function printVersion() {
|
package/dist/broker.js
CHANGED
|
@@ -2459,6 +2459,13 @@ function applyEnvDefaults(input) {
|
|
|
2459
2459
|
function mintSessionKey() {
|
|
2460
2460
|
return generatePrivateKey();
|
|
2461
2461
|
}
|
|
2462
|
+
function deriveSignerAddress(privateKeyHex) {
|
|
2463
|
+
try {
|
|
2464
|
+
return privateKeyToAccount(privateKeyHex).address;
|
|
2465
|
+
} catch {
|
|
2466
|
+
return null;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2462
2469
|
function decideSetupAction(input) {
|
|
2463
2470
|
if (input.hello === null) return "spawn_and_login";
|
|
2464
2471
|
if (!input.hello.hasJwt) return "login_only";
|
|
@@ -2795,7 +2802,21 @@ async function runSetup(argv, deps) {
|
|
|
2795
2802
|
let sessionKey = effectiveEnv.MUHAVEN_BROKER_SESSION_KEY;
|
|
2796
2803
|
let mintedKey = false;
|
|
2797
2804
|
if (sessionKey && sessionKey.length > 0) {
|
|
2798
|
-
|
|
2805
|
+
const shapeErr = validateSessionKeyShape(sessionKey);
|
|
2806
|
+
if (shapeErr) {
|
|
2807
|
+
deps.printErr(`error: MUHAVEN_BROKER_SESSION_KEY is set but invalid \u2014 ${shapeErr}`);
|
|
2808
|
+
deps.printErr(
|
|
2809
|
+
" Unset MUHAVEN_BROKER_SESSION_KEY to paste a dashboard key (or fix the value), then re-run setup."
|
|
2810
|
+
);
|
|
2811
|
+
return 2;
|
|
2812
|
+
}
|
|
2813
|
+
const signer = deriveSignerAddress(sessionKey);
|
|
2814
|
+
deps.print(
|
|
2815
|
+
`Session key: using MUHAVEN_BROKER_SESSION_KEY from env${signer ? ` (signer ${signer})` : ""}.`
|
|
2816
|
+
);
|
|
2817
|
+
deps.print(
|
|
2818
|
+
" To paste a dashboard-minted key instead, unset MUHAVEN_BROKER_SESSION_KEY first."
|
|
2819
|
+
);
|
|
2799
2820
|
} else {
|
|
2800
2821
|
const sessionInput = deps.sessionInput ?? {
|
|
2801
2822
|
isTty: false,
|
|
@@ -2814,11 +2835,17 @@ async function runSetup(argv, deps) {
|
|
|
2814
2835
|
}
|
|
2815
2836
|
if (resolution.kind === "key") {
|
|
2816
2837
|
sessionKey = resolution.key;
|
|
2817
|
-
|
|
2838
|
+
const signer = deriveSignerAddress(sessionKey);
|
|
2839
|
+
deps.print(
|
|
2840
|
+
`Session key: using the pasted dashboard key${signer ? ` (signer ${signer})` : ""}.`
|
|
2841
|
+
);
|
|
2818
2842
|
} else {
|
|
2819
2843
|
sessionKey = deps.mintSessionKey();
|
|
2820
2844
|
mintedKey = true;
|
|
2821
|
-
|
|
2845
|
+
const signer = deriveSignerAddress(sessionKey);
|
|
2846
|
+
deps.print(
|
|
2847
|
+
`Session key: minted fresh (secp256k1, ephemeral to this daemon)${signer ? ` \u2014 signer ${signer}` : ""}.`
|
|
2848
|
+
);
|
|
2822
2849
|
}
|
|
2823
2850
|
}
|
|
2824
2851
|
effectiveEnv.MUHAVEN_BROKER_SESSION_KEY = sessionKey;
|
|
@@ -3593,7 +3620,7 @@ function printUsage() {
|
|
|
3593
3620
|
}
|
|
3594
3621
|
function getBrokerPackageVersion() {
|
|
3595
3622
|
{
|
|
3596
|
-
return "0.4.
|
|
3623
|
+
return "0.4.3";
|
|
3597
3624
|
}
|
|
3598
3625
|
}
|
|
3599
3626
|
function printVersion() {
|
package/dist/index.cjs
CHANGED
|
@@ -2799,18 +2799,33 @@ async function attemptPathD(args, deps) {
|
|
|
2799
2799
|
message: `backend /agent/policy/state lookup failed: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
2800
2800
|
};
|
|
2801
2801
|
}
|
|
2802
|
+
let mirrorFetchOk = false;
|
|
2803
|
+
let mirrorHadActiveSession = false;
|
|
2802
2804
|
try {
|
|
2803
2805
|
const mirror = await deps.backend.get(
|
|
2804
2806
|
"/api/v1/agent/policy/scoped-session",
|
|
2805
2807
|
{ surface: "mcp" }
|
|
2806
2808
|
);
|
|
2809
|
+
mirrorFetchOk = true;
|
|
2807
2810
|
if (mirror?.session) {
|
|
2811
|
+
mirrorHadActiveSession = true;
|
|
2808
2812
|
mirrorSessionRow = mirror.session;
|
|
2809
2813
|
mirrorEnableStatus = mirror.session.enableStatus ?? null;
|
|
2810
2814
|
mirrorValidatorNonce = mirror.session.validatorNonce ?? null;
|
|
2811
2815
|
}
|
|
2812
2816
|
} catch (err2) {
|
|
2813
2817
|
}
|
|
2818
|
+
if (mirrorFetchOk && !mirrorHadActiveSession) {
|
|
2819
|
+
try {
|
|
2820
|
+
await deps.broker.clearPolicySnapshot(activeId);
|
|
2821
|
+
} catch {
|
|
2822
|
+
}
|
|
2823
|
+
return {
|
|
2824
|
+
kind: "fallback",
|
|
2825
|
+
reason: "session_revoked",
|
|
2826
|
+
message: "the Scoped session was revoked (or expired) on the dashboard \u2014 the broker snapshot is stale; purged it and falling back to Path C. Re-mint a Scoped session to resume autonomous buys."
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2814
2829
|
if (!snapshot.permissionId) {
|
|
2815
2830
|
return {
|
|
2816
2831
|
kind: "fallback",
|
|
@@ -3680,7 +3695,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
3680
3695
|
var SERVER_VERSION = resolveServerVersion();
|
|
3681
3696
|
function resolveServerVersion() {
|
|
3682
3697
|
{
|
|
3683
|
-
return "0.4.
|
|
3698
|
+
return "0.4.3";
|
|
3684
3699
|
}
|
|
3685
3700
|
}
|
|
3686
3701
|
function toJsonInputSchema(schema) {
|
package/dist/index.js
CHANGED
|
@@ -2795,18 +2795,33 @@ async function attemptPathD(args, deps) {
|
|
|
2795
2795
|
message: `backend /agent/policy/state lookup failed: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
2796
2796
|
};
|
|
2797
2797
|
}
|
|
2798
|
+
let mirrorFetchOk = false;
|
|
2799
|
+
let mirrorHadActiveSession = false;
|
|
2798
2800
|
try {
|
|
2799
2801
|
const mirror = await deps.backend.get(
|
|
2800
2802
|
"/api/v1/agent/policy/scoped-session",
|
|
2801
2803
|
{ surface: "mcp" }
|
|
2802
2804
|
);
|
|
2805
|
+
mirrorFetchOk = true;
|
|
2803
2806
|
if (mirror?.session) {
|
|
2807
|
+
mirrorHadActiveSession = true;
|
|
2804
2808
|
mirrorSessionRow = mirror.session;
|
|
2805
2809
|
mirrorEnableStatus = mirror.session.enableStatus ?? null;
|
|
2806
2810
|
mirrorValidatorNonce = mirror.session.validatorNonce ?? null;
|
|
2807
2811
|
}
|
|
2808
2812
|
} catch (err2) {
|
|
2809
2813
|
}
|
|
2814
|
+
if (mirrorFetchOk && !mirrorHadActiveSession) {
|
|
2815
|
+
try {
|
|
2816
|
+
await deps.broker.clearPolicySnapshot(activeId);
|
|
2817
|
+
} catch {
|
|
2818
|
+
}
|
|
2819
|
+
return {
|
|
2820
|
+
kind: "fallback",
|
|
2821
|
+
reason: "session_revoked",
|
|
2822
|
+
message: "the Scoped session was revoked (or expired) on the dashboard \u2014 the broker snapshot is stale; purged it and falling back to Path C. Re-mint a Scoped session to resume autonomous buys."
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2810
2825
|
if (!snapshot.permissionId) {
|
|
2811
2826
|
return {
|
|
2812
2827
|
kind: "fallback",
|
|
@@ -3676,7 +3691,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
3676
3691
|
var SERVER_VERSION = resolveServerVersion();
|
|
3677
3692
|
function resolveServerVersion() {
|
|
3678
3693
|
{
|
|
3679
|
-
return "0.4.
|
|
3694
|
+
return "0.4.3";
|
|
3680
3695
|
}
|
|
3681
3696
|
}
|
|
3682
3697
|
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.4.
|
|
6
|
+
"version": "0.4.3",
|
|
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.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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": {
|