@muhaven/mcp 0.1.6 → 0.2.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/CHANGELOG.md +195 -0
- package/dist/broker.cjs +103 -13
- package/dist/broker.js +103 -13
- package/dist/index.cjs +162 -120
- package/dist/index.d.cts +1 -21
- package/dist/index.d.ts +1 -21
- package/dist/index.js +162 -120
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/tool-hashes.json +9 -5
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@ import { z, ZodError } from 'zod';
|
|
|
9
9
|
import { platform, homedir } from 'os';
|
|
10
10
|
import { connect, createServer } from 'net';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
|
-
import { keccak256, toBytes } from 'viem';
|
|
13
12
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
14
13
|
|
|
15
14
|
// src/server.ts
|
|
@@ -57,9 +56,38 @@ function deriveAllowedHosts(baseUrl) {
|
|
|
57
56
|
function trimTrailingSlash(s) {
|
|
58
57
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
59
58
|
}
|
|
59
|
+
function validatePublicUrlEnv(name, value) {
|
|
60
|
+
let parsed;
|
|
61
|
+
try {
|
|
62
|
+
parsed = new URL(value);
|
|
63
|
+
} catch {
|
|
64
|
+
return `${name} is not a valid URL: ${value}`;
|
|
65
|
+
}
|
|
66
|
+
if (parsed.protocol === "https:") return null;
|
|
67
|
+
if (parsed.protocol === "http:") {
|
|
68
|
+
const host = parsed.hostname;
|
|
69
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "[::1]") return null;
|
|
70
|
+
return `${name} must use https:// (got http:// to ${host} \u2014 refusing to route MCP deep-links over cleartext to a non-loopback host)`;
|
|
71
|
+
}
|
|
72
|
+
return `${name} must use https:// (got ${parsed.protocol})`;
|
|
73
|
+
}
|
|
74
|
+
function resolvePublicUrlEnv(name, rawValue, defaultValue) {
|
|
75
|
+
const value = rawValue ?? defaultValue;
|
|
76
|
+
const err2 = validatePublicUrlEnv(name, value);
|
|
77
|
+
if (err2) throw new Error(err2);
|
|
78
|
+
return trimTrailingSlash(value);
|
|
79
|
+
}
|
|
60
80
|
function loadMcpConfig(env = process.env) {
|
|
61
|
-
const backendBaseUrl =
|
|
62
|
-
|
|
81
|
+
const backendBaseUrl = resolvePublicUrlEnv(
|
|
82
|
+
"MUHAVEN_BACKEND_URL",
|
|
83
|
+
env.MUHAVEN_BACKEND_URL,
|
|
84
|
+
DEFAULT_BACKEND_URL
|
|
85
|
+
);
|
|
86
|
+
const dashboardBaseUrl = resolvePublicUrlEnv(
|
|
87
|
+
"MUHAVEN_DASHBOARD_URL",
|
|
88
|
+
env.MUHAVEN_DASHBOARD_URL,
|
|
89
|
+
DEFAULT_DASHBOARD_URL
|
|
90
|
+
);
|
|
63
91
|
const brokerEndpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
|
|
64
92
|
const readOnly = readEnvBool("MUHAVEN_READ_ONLY", false, env);
|
|
65
93
|
const requestTimeoutMs = readEnvInt("MUHAVEN_REQUEST_TIMEOUT_MS", DEFAULT_REQUEST_TIMEOUT_MS, env);
|
|
@@ -89,8 +117,16 @@ function loadBrokerConfig(env = process.env) {
|
|
|
89
117
|
const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
|
|
90
118
|
const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
|
|
91
119
|
const requestTimeoutMs = readEnvInt("MUHAVEN_BROKER_TIMEOUT_MS", DEFAULT_BROKER_TIMEOUT_MS, env);
|
|
92
|
-
const backendBaseUrl =
|
|
93
|
-
|
|
120
|
+
const backendBaseUrl = resolvePublicUrlEnv(
|
|
121
|
+
"MUHAVEN_BACKEND_URL",
|
|
122
|
+
env.MUHAVEN_BACKEND_URL,
|
|
123
|
+
DEFAULT_BACKEND_URL
|
|
124
|
+
);
|
|
125
|
+
const dashboardBaseUrl = resolvePublicUrlEnv(
|
|
126
|
+
"MUHAVEN_DASHBOARD_URL",
|
|
127
|
+
env.MUHAVEN_DASHBOARD_URL,
|
|
128
|
+
DEFAULT_DASHBOARD_URL
|
|
129
|
+
);
|
|
94
130
|
return {
|
|
95
131
|
endpoint,
|
|
96
132
|
sessionKeyHex,
|
|
@@ -463,25 +499,32 @@ var TOOL_DESCRIPTORS = [
|
|
|
463
499
|
{
|
|
464
500
|
name: "muhaven.position.buy",
|
|
465
501
|
group: "position",
|
|
466
|
-
description:
|
|
502
|
+
description: 'Prepare a Subscription buy. Returns a dashboard deep-link URL (muhaven.app/trade?mode=buy&...) the user opens to review the pre-filled form, then taps Authorize. The user\'s passkey + ZeroDev kernel sign on the dashboard \u2014 this MCP tool never holds or submits a signing key. Use after the user names a clear amount + token (e.g. "Buy 5 mhUSDC of TBILL1" \u2192 `amountUsdc: "5"`). Token accepts either a symbol ("TBILL1") or 0x-address. The `amountUsdc` field is HUMAN-DECIMAL mhUSDC ("5" = 5 mhUSDC, "0.5" = half a mhUSDC) \u2014 NOT base-6 integer. Max 6 fractional digits. Settlement is NOT observable from MCP \u2014 verify by calling muhaven.read.portfolio after the user confirms done.',
|
|
467
503
|
sensitive: true
|
|
468
504
|
},
|
|
469
505
|
{
|
|
470
506
|
name: "muhaven.position.sell",
|
|
471
507
|
group: "position",
|
|
472
|
-
description: "
|
|
508
|
+
description: "Prepare a Subscription sell. Returns a dashboard deep-link URL (muhaven.app/trade?mode=sell&...) with the form pre-filled. Same passkey + verify-after pattern as muhaven.position.buy. Input is amountShares (raw POSITIVE INTEGER share count, NOT mhUSDC notional) \u2014 fhERC-20 shares have no decimals so fractional inputs are rejected.",
|
|
473
509
|
sensitive: true
|
|
474
510
|
},
|
|
475
511
|
{
|
|
476
512
|
name: "muhaven.position.claim",
|
|
477
513
|
group: "position",
|
|
478
|
-
description: "
|
|
514
|
+
description: "Prepare a yield claim. Returns a dashboard deep-link URL (muhaven.app/yields?...) pointing at the YieldsPage. When escrowId is set, the matching epoch row is highlighted + scrolled into view; when omitted, the page renders the user's full claimable list and they pick. User passkey-signs on the dashboard.",
|
|
479
515
|
sensitive: true
|
|
480
516
|
},
|
|
481
517
|
{
|
|
482
518
|
name: "muhaven.position.rebalance",
|
|
483
519
|
group: "position",
|
|
484
|
-
description: "
|
|
520
|
+
description: "NOT IMPLEMENTED in this release \u2014 returns an error pointing the user at single-leg position.buy / position.sell or the dashboard /trade page. Multi-leg execute_plan lands in Wave 5 with a composite preview UI + executeBatch on the kernel.",
|
|
521
|
+
sensitive: true
|
|
522
|
+
},
|
|
523
|
+
// ── Path C cash group (2026-05-18) ────────────────────────────────
|
|
524
|
+
{
|
|
525
|
+
name: "muhaven.cash.wrap",
|
|
526
|
+
group: "cash",
|
|
527
|
+
description: 'Prepare a USDC \u2192 mhUSDC wrap (the encrypted-balance conversion that funds buys). Returns a dashboard deep-link URL (muhaven.app/cash?action=wrap&...) with the amount pre-filled. Input amountUsdc is human-readable USDC ("100" = $100). Common LLM chain: read.portfolio \u2192 notice 0 mhUSDC \u2192 cash.wrap \u2192 then position.buy (each is its own user-confirmed deep-link). Settlement not observable from MCP \u2014 re-call read.portfolio to verify.',
|
|
485
528
|
sensitive: true
|
|
486
529
|
},
|
|
487
530
|
{
|
|
@@ -607,6 +650,13 @@ function verifyDescriptorAgainstPin(descriptor, pinnedSha256) {
|
|
|
607
650
|
}
|
|
608
651
|
var HEX_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
609
652
|
var addressSchema = z.string().regex(HEX_ADDRESS_RE, "must be a 0x-prefixed 20-byte hex address");
|
|
653
|
+
var TOKEN_SYMBOL_RE = /^[A-Za-z][A-Za-z0-9]{0,11}$/;
|
|
654
|
+
var tokenIdentifierSchema = z.string().refine(
|
|
655
|
+
(v) => HEX_ADDRESS_RE.test(v) || TOKEN_SYMBOL_RE.test(v),
|
|
656
|
+
{
|
|
657
|
+
message: "must be a 0x-prefixed 20-byte hex address OR a token symbol (1-12 alphanumeric chars, starting with a letter)"
|
|
658
|
+
}
|
|
659
|
+
);
|
|
610
660
|
var tierSchema = z.enum(["advisory", "confirm-per-action", "policy-bound", "paused"]);
|
|
611
661
|
var surfaceSchema = z.enum(["havenbot", "mcp", "openclaw", "checkout"]);
|
|
612
662
|
z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]);
|
|
@@ -642,30 +692,55 @@ var ReadAuditInputSchema = z.object({
|
|
|
642
692
|
cursor: z.string().min(1).max(512).optional(),
|
|
643
693
|
limit: z.number().int().min(1).max(200).optional()
|
|
644
694
|
}).strict();
|
|
695
|
+
var decimalUsdcAmountSchema = z.string().regex(
|
|
696
|
+
/^(0|[1-9]\d*)(\.\d{1,6})?$/,
|
|
697
|
+
'must be a positive decimal mhUSDC amount with at most 6 fractional digits (e.g. "5", "0.5", "1234.567")'
|
|
698
|
+
).max(48, "must be at most 48 characters");
|
|
645
699
|
var PositionBuyInputSchema = z.object({
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
700
|
+
/** Symbol (e.g. "TBILL1") or 0x-address. Path C dashboard resolves either. */
|
|
701
|
+
token: tokenIdentifierSchema,
|
|
702
|
+
/**
|
|
703
|
+
* mhUSDC amount in human-decimal units ("5" = 5 mhUSDC, "0.5" =
|
|
704
|
+
* half a mhUSDC). Forwarded verbatim to the dashboard form via
|
|
705
|
+
* `/trade?amount=`. Replaces the prior `amountUsdc6` base-6 integer
|
|
706
|
+
* field — see schema doc above for rationale (LLM-footgun fix).
|
|
707
|
+
*/
|
|
708
|
+
amountUsdc: decimalUsdcAmountSchema
|
|
649
709
|
}).strict();
|
|
650
710
|
var PositionSellInputSchema = z.object({
|
|
651
|
-
token:
|
|
652
|
-
/**
|
|
653
|
-
|
|
711
|
+
token: tokenIdentifierSchema,
|
|
712
|
+
/**
|
|
713
|
+
* Share count to redeem. fhERC-20 shares are integer base units
|
|
714
|
+
* (no decimals — see memory `project_decimals_lie_wave4_p0`).
|
|
715
|
+
* Regex rejects any fractional input so a deep-link can't pre-fill
|
|
716
|
+
* "2.5 shares" that would silently floor on the on-chain submit.
|
|
717
|
+
*/
|
|
718
|
+
amountShares: z.string().regex(/^[1-9]\d*$/, "must be a positive integer share count")
|
|
654
719
|
}).strict();
|
|
655
720
|
var PositionClaimInputSchema = z.object({
|
|
656
|
-
token:
|
|
657
|
-
/** When set,
|
|
721
|
+
token: tokenIdentifierSchema,
|
|
722
|
+
/** When set, deep-link highlights the specific epoch row; else /yields renders the full claimable list. */
|
|
658
723
|
escrowId: z.string().regex(/^\d+$/).optional()
|
|
659
724
|
}).strict();
|
|
660
725
|
var PositionRebalanceInputSchema = z.object({
|
|
661
726
|
legs: z.array(
|
|
662
727
|
z.object({
|
|
663
|
-
token:
|
|
728
|
+
token: tokenIdentifierSchema,
|
|
664
729
|
side: z.enum(["buy", "sell"]),
|
|
665
730
|
amount: z.string().regex(/^\d+$/)
|
|
666
731
|
}).strict()
|
|
667
732
|
).min(2).max(8)
|
|
668
733
|
}).strict();
|
|
734
|
+
var CashWrapInputSchema = z.object({
|
|
735
|
+
/**
|
|
736
|
+
* USDC amount in human-decimal units ("100" for $100, "1.5" for
|
|
737
|
+
* $1.50). Same shape + same regex as `PositionBuyInputSchema.amountUsdc`
|
|
738
|
+
* so the LLM doesn't have to learn two different unit conventions
|
|
739
|
+
* across the Path C surface. Max 6 fractional digits (USDC's base
|
|
740
|
+
* unit floor); 48-char length cap is URL-bloat defense.
|
|
741
|
+
*/
|
|
742
|
+
amountUsdc: decimalUsdcAmountSchema
|
|
743
|
+
}).strict();
|
|
669
744
|
var PolicySetTierInputSchema = z.object({
|
|
670
745
|
targetTier: tierSchema,
|
|
671
746
|
/** Returned by an earlier `request` call. Omit for step-down or first-call. */
|
|
@@ -736,15 +811,6 @@ function authRequiredPayload() {
|
|
|
736
811
|
loginCommand: "muhaven-broker login"
|
|
737
812
|
};
|
|
738
813
|
}
|
|
739
|
-
function sessionKeyRequiredPayload(dashboardBaseUrl = "https://muhaven.app") {
|
|
740
|
-
const mintUrl = `${trimTrailingSlash(dashboardBaseUrl)}/agent/policy/transition`;
|
|
741
|
-
return {
|
|
742
|
-
ok: false,
|
|
743
|
-
code: "SESSION_KEY_REQUIRED",
|
|
744
|
-
message: `No session key loaded in broker (read-only posture). Mint one via the dashboard at ${mintUrl}, copy the 0x-prefixed hex into MUHAVEN_BROKER_SESSION_KEY, and restart the daemon. Do NOT run \`muhaven-broker login\` for this \u2014 that mints a JWT, not a session key.`,
|
|
745
|
-
mintUrl
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
814
|
|
|
749
815
|
// src/tools/handlers.ts
|
|
750
816
|
function ok(data) {
|
|
@@ -761,11 +827,6 @@ function mapBackendError(e) {
|
|
|
761
827
|
if (e instanceof Error) return err("backend.network", e.message);
|
|
762
828
|
return err("backend.network", "unknown backend error");
|
|
763
829
|
}
|
|
764
|
-
function mapBrokerError(e) {
|
|
765
|
-
if (e instanceof BrokerClientError) return err(`broker.${e.code}`, e.message);
|
|
766
|
-
if (e instanceof Error) return err("broker.network", e.message);
|
|
767
|
-
return err("broker.network", "unknown broker error");
|
|
768
|
-
}
|
|
769
830
|
async function readPortfolio(_input, deps) {
|
|
770
831
|
try {
|
|
771
832
|
const data = await deps.backend.get("/api/v1/portfolio");
|
|
@@ -819,103 +880,79 @@ async function readAudit(input, deps) {
|
|
|
819
880
|
return mapBackendError(e);
|
|
820
881
|
}
|
|
821
882
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
883
|
+
function buildPositionDeeplink(dashboardBaseUrl, action, params) {
|
|
884
|
+
const base = dashboardBaseUrl.replace(/\/+$/, "");
|
|
885
|
+
const path = action === "buy" || action === "sell" ? "/trade" : action === "claim" ? "/yields" : "/cash";
|
|
886
|
+
const search = new URLSearchParams();
|
|
887
|
+
if (action === "buy" || action === "sell") search.set("mode", action);
|
|
888
|
+
for (const [k, v] of Object.entries(params)) search.set(k, v);
|
|
889
|
+
search.set("from", "mcp");
|
|
890
|
+
return `${base}${path}?${search.toString()}`;
|
|
826
891
|
}
|
|
827
|
-
function
|
|
828
|
-
|
|
829
|
-
if (value && typeof value === "object") {
|
|
830
|
-
const obj = value;
|
|
831
|
-
const sorted = {};
|
|
832
|
-
for (const k of Object.keys(obj).sort()) sorted[k] = sortKeys(obj[k]);
|
|
833
|
-
return sorted;
|
|
834
|
-
}
|
|
835
|
-
return value;
|
|
836
|
-
}
|
|
837
|
-
var cachedHasSessionKeyProbe = null;
|
|
838
|
-
async function signEnvelope(intent, toolName, summary, deps) {
|
|
839
|
-
const intentHash = computeIntentHash(intent);
|
|
840
|
-
if (!deps.broker) {
|
|
841
|
-
return err(
|
|
842
|
-
"broker.unavailable",
|
|
843
|
-
"position tools require a running muhaven-broker daemon \u2014 see README \xA7Broker setup"
|
|
844
|
-
);
|
|
845
|
-
}
|
|
846
|
-
const broker = deps.broker;
|
|
847
|
-
if (cachedHasSessionKeyProbe === null) {
|
|
848
|
-
cachedHasSessionKeyProbe = (async () => {
|
|
849
|
-
try {
|
|
850
|
-
const hello = await broker.hello();
|
|
851
|
-
return hello.hasSessionKey ?? true;
|
|
852
|
-
} catch (err2) {
|
|
853
|
-
cachedHasSessionKeyProbe = null;
|
|
854
|
-
throw err2;
|
|
855
|
-
}
|
|
856
|
-
})();
|
|
857
|
-
}
|
|
858
|
-
let hasSessionKey;
|
|
859
|
-
try {
|
|
860
|
-
hasSessionKey = await cachedHasSessionKeyProbe;
|
|
861
|
-
} catch (e) {
|
|
862
|
-
return mapBrokerError(e);
|
|
863
|
-
}
|
|
864
|
-
if (hasSessionKey === false) {
|
|
865
|
-
return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
|
|
866
|
-
}
|
|
867
|
-
try {
|
|
868
|
-
const sig = await broker.signHash(intentHash, { tool: toolName, summary });
|
|
869
|
-
return ok({
|
|
870
|
-
intentHash,
|
|
871
|
-
unsignedUserOp: {
|
|
872
|
-
target: "see backend",
|
|
873
|
-
data: "see backend",
|
|
874
|
-
note: "P3 returns a placeholder envelope; P6 wires the canonical UserOp shape."
|
|
875
|
-
},
|
|
876
|
-
brokerSignature: sig.signature,
|
|
877
|
-
signerAddress: sig.signerAddress
|
|
878
|
-
});
|
|
879
|
-
} catch (e) {
|
|
880
|
-
if (e instanceof BrokerClientError && e.code === "broker_error" && /session_key_unavailable/.test(e.message)) {
|
|
881
|
-
cachedHasSessionKeyProbe = Promise.resolve(false);
|
|
882
|
-
return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
|
|
883
|
-
}
|
|
884
|
-
return mapBrokerError(e);
|
|
885
|
-
}
|
|
892
|
+
function resolveDashboardBaseUrl(deps) {
|
|
893
|
+
return deps.dashboardBaseUrl ?? "https://muhaven.app";
|
|
886
894
|
}
|
|
887
895
|
async function positionBuy(input, deps) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
896
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "buy", {
|
|
897
|
+
token: input.token,
|
|
898
|
+
amount: input.amountUsdc
|
|
899
|
+
});
|
|
900
|
+
return ok({
|
|
901
|
+
dashboardUrl,
|
|
902
|
+
action: "buy",
|
|
903
|
+
instructions: `Open this link to review and authorize the buy of ${input.amountUsdc} mhUSDC of ${input.token}:
|
|
904
|
+
${dashboardUrl}`,
|
|
905
|
+
echo: { action: "buy", token: input.token, amount: input.amountUsdc }
|
|
906
|
+
});
|
|
894
907
|
}
|
|
895
908
|
async function positionSell(input, deps) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
909
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "sell", {
|
|
910
|
+
token: input.token,
|
|
911
|
+
shares: input.amountShares
|
|
912
|
+
});
|
|
913
|
+
return ok({
|
|
914
|
+
dashboardUrl,
|
|
915
|
+
action: "sell",
|
|
916
|
+
instructions: `Open this link to review and authorize the sale of ${input.amountShares} shares of ${input.token}:
|
|
917
|
+
${dashboardUrl}`,
|
|
918
|
+
echo: { action: "sell", token: input.token, shares: input.amountShares }
|
|
919
|
+
});
|
|
902
920
|
}
|
|
903
921
|
async function positionClaim(input, deps) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
922
|
+
const params = { token: input.token };
|
|
923
|
+
if (input.escrowId) params.epoch = input.escrowId;
|
|
924
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "claim", params);
|
|
925
|
+
const claimDescriptor = input.escrowId ? `the claim for epoch #${input.escrowId} of ${input.token}` : `your claimable epochs for ${input.token}`;
|
|
926
|
+
return ok({
|
|
927
|
+
dashboardUrl,
|
|
928
|
+
action: "claim",
|
|
929
|
+
instructions: `Open this link to review and authorize ${claimDescriptor}:
|
|
930
|
+
${dashboardUrl}`,
|
|
931
|
+
echo: {
|
|
932
|
+
action: "claim",
|
|
933
|
+
token: input.token,
|
|
934
|
+
...input.escrowId ? { epoch: input.escrowId } : {}
|
|
935
|
+
}
|
|
936
|
+
});
|
|
910
937
|
}
|
|
911
|
-
async function positionRebalance(
|
|
912
|
-
return
|
|
913
|
-
|
|
914
|
-
"
|
|
915
|
-
`rebalance ${input.legs.length} legs`,
|
|
916
|
-
deps
|
|
938
|
+
async function positionRebalance(_input, _deps) {
|
|
939
|
+
return err(
|
|
940
|
+
"not_implemented",
|
|
941
|
+
"position.rebalance is deferred to Wave 5. Today, ask the user to execute legs one at a time via position.buy / position.sell, or use the dashboard /trade page directly."
|
|
917
942
|
);
|
|
918
943
|
}
|
|
944
|
+
async function cashWrap(input, deps) {
|
|
945
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "wrap", {
|
|
946
|
+
amount: input.amountUsdc
|
|
947
|
+
});
|
|
948
|
+
return ok({
|
|
949
|
+
dashboardUrl,
|
|
950
|
+
action: "wrap",
|
|
951
|
+
instructions: `Open this link to review and authorize the conversion of ${input.amountUsdc} USDC into mhUSDC:
|
|
952
|
+
${dashboardUrl}`,
|
|
953
|
+
echo: { action: "wrap", amount: input.amountUsdc }
|
|
954
|
+
});
|
|
955
|
+
}
|
|
919
956
|
async function policySetTier(input, deps) {
|
|
920
957
|
try {
|
|
921
958
|
const data = await deps.backend.post("/api/v1/agent/policy/transition", {
|
|
@@ -1123,6 +1160,11 @@ var HANDLERS = {
|
|
|
1123
1160
|
schema: PositionRebalanceInputSchema,
|
|
1124
1161
|
handler: positionRebalance
|
|
1125
1162
|
},
|
|
1163
|
+
// ── Path C cash group (2026-05-18) ────────────────────────────────
|
|
1164
|
+
"muhaven.cash.wrap": {
|
|
1165
|
+
schema: CashWrapInputSchema,
|
|
1166
|
+
handler: cashWrap
|
|
1167
|
+
},
|
|
1126
1168
|
"muhaven.policy.set_tier": {
|
|
1127
1169
|
schema: PolicySetTierInputSchema,
|
|
1128
1170
|
handler: policySetTier
|
|
@@ -1206,7 +1248,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
1206
1248
|
var SERVER_VERSION = resolveServerVersion();
|
|
1207
1249
|
function resolveServerVersion() {
|
|
1208
1250
|
{
|
|
1209
|
-
return "0.
|
|
1251
|
+
return "0.2.0";
|
|
1210
1252
|
}
|
|
1211
1253
|
}
|
|
1212
1254
|
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.
|
|
6
|
+
"version": "0.2.0",
|
|
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 22 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 return unsigned UserOps + broker signatures — 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.
|
|
3
|
+
"version": "0.2.0",
|
|
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": {
|
package/tool-hashes.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-05-
|
|
2
|
+
"generatedAt": "2026-05-17T19:03:52.417Z",
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "muhaven.read.portfolio",
|
|
@@ -23,19 +23,23 @@
|
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"name": "muhaven.position.buy",
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "4266f9d91df2086729709e3b1e76c34cb598fd90265aa24de90c256a3a6c217a"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"name": "muhaven.position.sell",
|
|
30
|
-
"sha256": "
|
|
30
|
+
"sha256": "58c5090b04ecf4068dafe1f6a62e60b0befc9e0ad1cbbaedbab57e623c0194ce"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"name": "muhaven.position.claim",
|
|
34
|
-
"sha256": "
|
|
34
|
+
"sha256": "31a6b9472f8b34e69ca28b888e60178dafd01b8644e2a61e6f36515660b68838"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"name": "muhaven.position.rebalance",
|
|
38
|
-
"sha256": "
|
|
38
|
+
"sha256": "ad32f2e9c2b6aafb6bd3acd33079dc7541035f0f405045178f95489f80cc922e"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "muhaven.cash.wrap",
|
|
42
|
+
"sha256": "1ae4cb949e0d09e7fb7e8957e51bfc63b43397c253849c217b835aaa9505b8c9"
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
"name": "muhaven.policy.set_tier",
|