@muhaven/mcp 0.1.7 → 0.2.1

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/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 '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 = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
62
- const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
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 = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
93
- const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
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,
@@ -337,6 +373,23 @@ var BackendClient = class {
337
373
  false
338
374
  );
339
375
  }
376
+ /**
377
+ * GET variant that sends no Authorization header. Use for backend
378
+ * endpoints that are intentionally public (e.g. `/api/v1/tokens`
379
+ * which the marketplace + the 0.2.1 `positionBuy` NAV-conversion
380
+ * both read). Avoids triggering the AUTH_REQUIRED branch for the
381
+ * "not yet logged in" case on read paths that don't need auth.
382
+ */
383
+ async getUnauth(path, query) {
384
+ const url = this.buildUrl(path, query);
385
+ return this.exchange(
386
+ "GET",
387
+ url,
388
+ void 0,
389
+ /* withAuth */
390
+ false
391
+ );
392
+ }
340
393
  buildUrl(path, query) {
341
394
  if (!path.startsWith("/")) {
342
395
  throw new BackendError("bad_request", `path must start with "/": ${path}`);
@@ -460,16 +513,22 @@ var TOOL_DESCRIPTORS = [
460
513
  description: `Return the authenticated user's tiered-autonomy audit log entries. Cursor-paginated. Useful for forensic review ("why was I paused?") and grant-reviewer demos. Read-only \u2014 never exposes other users' data.`,
461
514
  sensitive: false
462
515
  },
516
+ {
517
+ name: "muhaven.read.activity",
518
+ group: "read",
519
+ description: "Return the authenticated investor's on-chain activity feed (buys / sells / wraps / unwraps / yield claims / transfers). Each row carries token address, tx hash, block timestamp, and event type \u2014 but NEVER cleartext amounts (encrypted handles only, decryptable client-side via permit). USE THIS to verify a Path C dashboard action settled: after position.buy / position.sell / cash.wrap, the user opens the deep-link, taps Authorize, the on-chain tx lands \u2192 a new row appears here. Far more reliable than re-calling read.portfolio (which only changes shape when a NEW token enters the catalog).",
520
+ sensitive: false
521
+ },
463
522
  {
464
523
  name: "muhaven.position.buy",
465
524
  group: "position",
466
- 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"). Token accepts either a symbol ("TBILL1") or 0x-address. Settlement is NOT observable from MCP \u2014 verify by calling muhaven.read.portfolio after the user confirms done.`,
525
+ 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. The tool fetches the current on-chain NAV for the token and converts the notional to integer shares (floor) before building the URL \u2014 so "Buy 3 mhUSDC of GOLD1" at NAV $0.01 becomes "Buy 300 GOLD1 shares (~3 mhUSDC)". Refuses with `amount_too_small_for_share` when the notional won\'t buy at least 1 share at current NAV; the error message tells the user the minimum mhUSDC needed. Settlement is NOT observable from MCP \u2014 verify by calling muhaven.read.activity after the user confirms done (a new "buy" row with the tx hash will appear).',
467
526
  sensitive: true
468
527
  },
469
528
  {
470
529
  name: "muhaven.position.sell",
471
530
  group: "position",
472
- 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 share count), NOT mhUSDC notional.",
531
+ 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. Verify settlement by calling muhaven.read.activity (look for a "sell" or "sell-queued" row with the tx hash).',
473
532
  sensitive: true
474
533
  },
475
534
  {
@@ -488,7 +547,7 @@ var TOOL_DESCRIPTORS = [
488
547
  {
489
548
  name: "muhaven.cash.wrap",
490
549
  group: "cash",
491
- 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.',
550
+ 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). Verify settlement by calling muhaven.read.activity (a new "wrap" row will appear with the tx hash).',
492
551
  sensitive: true
493
552
  },
494
553
  {
@@ -656,16 +715,34 @@ var ReadAuditInputSchema = z.object({
656
715
  cursor: z.string().min(1).max(512).optional(),
657
716
  limit: z.number().int().min(1).max(200).optional()
658
717
  }).strict();
718
+ var ReadActivityInputSchema = z.object({
719
+ limit: z.number().int().min(1).max(50).optional(),
720
+ offset: z.number().int().min(0).max(1e3).optional()
721
+ }).strict();
722
+ var decimalUsdcAmountSchema = z.string().regex(
723
+ /^(0|[1-9]\d*)(\.\d{1,6})?$/,
724
+ 'must be a positive decimal mhUSDC amount with at most 6 fractional digits (e.g. "5", "0.5", "1234.567")'
725
+ ).max(48, "must be at most 48 characters");
659
726
  var PositionBuyInputSchema = z.object({
660
727
  /** Symbol (e.g. "TBILL1") or 0x-address. Path C dashboard resolves either. */
661
728
  token: tokenIdentifierSchema,
662
- /** Investor candidate spend, denominated in USDC base units (uint64). */
663
- amountUsdc6: z.string().regex(/^\d+$/, "must be a base-10 integer string")
729
+ /**
730
+ * mhUSDC amount in human-decimal units ("5" = 5 mhUSDC, "0.5" =
731
+ * half a mhUSDC). Forwarded verbatim to the dashboard form via
732
+ * `/trade?amount=`. Replaces the prior `amountUsdc6` base-6 integer
733
+ * field — see schema doc above for rationale (LLM-footgun fix).
734
+ */
735
+ amountUsdc: decimalUsdcAmountSchema
664
736
  }).strict();
665
737
  var PositionSellInputSchema = z.object({
666
738
  token: tokenIdentifierSchema,
667
- /** Encrypted-balance share count to redeem, denominated in fhERC-20 base units. */
668
- amountShares: z.string().regex(/^\d+$/, "must be a base-10 integer string")
739
+ /**
740
+ * Share count to redeem. fhERC-20 shares are integer base units
741
+ * (no decimals — see memory `project_decimals_lie_wave4_p0`).
742
+ * Regex rejects any fractional input so a deep-link can't pre-fill
743
+ * "2.5 shares" that would silently floor on the on-chain submit.
744
+ */
745
+ amountShares: z.string().regex(/^[1-9]\d*$/, "must be a positive integer share count")
669
746
  }).strict();
670
747
  var PositionClaimInputSchema = z.object({
671
748
  token: tokenIdentifierSchema,
@@ -683,11 +760,13 @@ var PositionRebalanceInputSchema = z.object({
683
760
  }).strict();
684
761
  var CashWrapInputSchema = z.object({
685
762
  /**
686
- * USDC amount in human-readable units ("100" for $100, "1.5" for
687
- * $1.50). Forwarded verbatim to `/cash?amount=`. Decimal optional
688
- * CashPage parses both `\d+` and `\d+\.\d+`.
763
+ * USDC amount in human-decimal units ("100" for $100, "1.5" for
764
+ * $1.50). Same shape + same regex as `PositionBuyInputSchema.amountUsdc`
765
+ * so the LLM doesn't have to learn two different unit conventions
766
+ * across the Path C surface. Max 6 fractional digits (USDC's base
767
+ * unit floor); 48-char length cap is URL-bloat defense.
689
768
  */
690
- amountUsdc: z.string().regex(/^\d+(\.\d+)?$/, "must be a positive decimal number string")
769
+ amountUsdc: decimalUsdcAmountSchema
691
770
  }).strict();
692
771
  var PolicySetTierInputSchema = z.object({
693
772
  targetTier: tierSchema,
@@ -760,6 +839,37 @@ function authRequiredPayload() {
760
839
  };
761
840
  }
762
841
 
842
+ // src/tools/decimal.ts
843
+ function parseDecimalToUsd6(decimal) {
844
+ const m = /^(\d+)(?:\.(\d+))?$/.exec(decimal);
845
+ if (!m) {
846
+ throw new Error(`Invalid decimal price: ${JSON.stringify(decimal)}`);
847
+ }
848
+ const intPart = m[1];
849
+ const fracPart = m[2] ?? "";
850
+ const fracPadded = (fracPart + "000000").slice(0, 6);
851
+ return BigInt(intPart + fracPadded);
852
+ }
853
+ function computeSharesFromUsd6(notionalUsd6, navUsd6) {
854
+ if (navUsd6 <= 0n) {
855
+ throw new Error("navUsd6 must be positive");
856
+ }
857
+ if (notionalUsd6 < 0n) {
858
+ throw new Error("notionalUsd6 must be non-negative");
859
+ }
860
+ return notionalUsd6 / navUsd6;
861
+ }
862
+ function formatUsd6AsDecimal(usd6) {
863
+ if (usd6 < 0n) {
864
+ throw new Error("usd6 must be non-negative");
865
+ }
866
+ const whole = usd6 / 1000000n;
867
+ const frac = usd6 % 1000000n;
868
+ if (frac === 0n) return whole.toString();
869
+ const fracStr = frac.toString().padStart(6, "0").replace(/0+$/, "");
870
+ return `${whole.toString()}.${fracStr}`;
871
+ }
872
+
763
873
  // src/tools/handlers.ts
764
874
  function ok(data) {
765
875
  return { ok: true, data };
@@ -828,6 +938,17 @@ async function readAudit(input, deps) {
828
938
  return mapBackendError(e);
829
939
  }
830
940
  }
941
+ async function readActivity(input, deps) {
942
+ try {
943
+ const data = await deps.backend.get("/api/v1/activity", {
944
+ limit: input.limit,
945
+ offset: input.offset
946
+ });
947
+ return ok(data);
948
+ } catch (e) {
949
+ return mapBackendError(e);
950
+ }
951
+ }
831
952
  function buildPositionDeeplink(dashboardBaseUrl, action, params) {
832
953
  const base = dashboardBaseUrl.replace(/\/+$/, "");
833
954
  const path = action === "buy" || action === "sell" ? "/trade" : action === "claim" ? "/yields" : "/cash";
@@ -837,39 +958,106 @@ function buildPositionDeeplink(dashboardBaseUrl, action, params) {
837
958
  search.set("from", "mcp");
838
959
  return `${base}${path}?${search.toString()}`;
839
960
  }
840
- function formatUsdc6ToDecimal(amountUsdc6) {
841
- if (!/^\d+$/.test(amountUsdc6)) {
842
- throw new Error(`amountUsdc6 must be a non-negative integer string: ${amountUsdc6}`);
843
- }
844
- const padded = amountUsdc6.padStart(7, "0");
845
- const intPart = padded.slice(0, -6).replace(/^0+(?=\d)/, "");
846
- const fracPart = padded.slice(-6).replace(/0+$/, "");
847
- return fracPart === "" ? intPart : `${intPart}.${fracPart}`;
848
- }
849
961
  function resolveDashboardBaseUrl(deps) {
850
962
  return deps.dashboardBaseUrl ?? "https://muhaven.app";
851
963
  }
964
+ function resolveTokenInCatalog(identifier, catalog) {
965
+ const needle = identifier.toLowerCase();
966
+ return catalog.find(
967
+ (t) => t.address.toLowerCase() === needle || t.symbol.toLowerCase() === needle
968
+ ) ?? null;
969
+ }
970
+ function sanitizeSymbolForLlmContext(raw) {
971
+ const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, "?");
972
+ return cleaned.length > 16 ? cleaned.slice(0, 16) : cleaned;
973
+ }
852
974
  async function positionBuy(input, deps) {
853
- const amount = formatUsdc6ToDecimal(input.amountUsdc6);
975
+ let catalog;
976
+ try {
977
+ catalog = await deps.backend.getUnauth("/api/v1/tokens");
978
+ } catch (e) {
979
+ return mapBackendError(e);
980
+ }
981
+ const token = resolveTokenInCatalog(input.token, catalog.tokens ?? []);
982
+ if (!token) {
983
+ return err(
984
+ "token_not_found",
985
+ `Token "${sanitizeSymbolForLlmContext(input.token)}" is not in the MuHaven catalog. Call muhaven.read.tokens for the canonical symbol list.`
986
+ );
987
+ }
988
+ const safeSymbol = sanitizeSymbolForLlmContext(token.symbol);
989
+ if (!token.latest_nav || !token.latest_nav.nav) {
990
+ return err(
991
+ "nav_unavailable",
992
+ `No NAV snapshot available for ${safeSymbol} yet. The nav-worker may not have written one \u2014 retry shortly, or use the dashboard /trade page directly.`
993
+ );
994
+ }
995
+ let navUsd6;
996
+ try {
997
+ navUsd6 = parseDecimalToUsd6(token.latest_nav.nav);
998
+ } catch (e) {
999
+ return err(
1000
+ "nav_malformed",
1001
+ `NAV for ${safeSymbol} is not a valid decimal price. Open the dashboard /trade page directly.`
1002
+ );
1003
+ }
1004
+ if (navUsd6 <= 0n) {
1005
+ return err(
1006
+ "nav_non_positive",
1007
+ `NAV for ${safeSymbol} is non-positive. Cannot quote a buy.`
1008
+ );
1009
+ }
1010
+ let notionalUsd6;
1011
+ try {
1012
+ notionalUsd6 = parseDecimalToUsd6(input.amountUsdc);
1013
+ } catch (e) {
1014
+ return err(
1015
+ "invalid_amount",
1016
+ `amountUsdc "${input.amountUsdc}" is not a valid decimal mhUSDC amount.`
1017
+ );
1018
+ }
1019
+ if (notionalUsd6 <= 0n) {
1020
+ return err(
1021
+ "invalid_amount",
1022
+ "amountUsdc must be greater than zero."
1023
+ );
1024
+ }
1025
+ const shares = computeSharesFromUsd6(notionalUsd6, navUsd6);
1026
+ if (shares <= 0n) {
1027
+ const navDisplay2 = formatUsd6AsDecimal(navUsd6);
1028
+ return err(
1029
+ "amount_too_small_for_share",
1030
+ `${input.amountUsdc} mhUSDC isn't enough to buy 1 share of ${safeSymbol} at the current NAV of $${navDisplay2}/share. Need at least ${navDisplay2} mhUSDC to buy 1 share. Ask the user for a larger amount, or chain muhaven.cash.wrap first if they're short on mhUSDC.`
1031
+ );
1032
+ }
1033
+ const effectiveNotionalUsd6 = shares * navUsd6;
1034
+ const effectiveNotionalDisplay = formatUsd6AsDecimal(effectiveNotionalUsd6);
1035
+ const navDisplay = formatUsd6AsDecimal(navUsd6);
1036
+ const sharesStr = shares.toString();
854
1037
  const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "buy", {
855
- token: input.token,
856
- amount
1038
+ token: token.symbol,
1039
+ amount: sharesStr
857
1040
  });
858
1041
  return ok({
859
1042
  dashboardUrl,
860
1043
  action: "buy",
861
- instructions: `Open this link to review and authorize the buy of ${amount} mhUSDC of ${input.token}:
1044
+ instructions: `Open this link to review and authorize the buy of ${sharesStr} ${safeSymbol} shares (~${effectiveNotionalDisplay} mhUSDC at current NAV $${navDisplay}/share):
862
1045
  ${dashboardUrl}`,
863
- echo: { action: "buy", token: input.token, amount }
1046
+ echo: {
1047
+ action: "buy",
1048
+ token: token.symbol,
1049
+ amount: sharesStr,
1050
+ shares: sharesStr,
1051
+ // Carry the original request + the conversion math so the LLM
1052
+ // (and a human auditor reading the trace) can see why the URL
1053
+ // shows the share count instead of the user-stated notional.
1054
+ amountUsdc: input.amountUsdc,
1055
+ effectiveNotionalUsd6: effectiveNotionalUsd6.toString(),
1056
+ navUsd6: navUsd6.toString()
1057
+ }
864
1058
  });
865
1059
  }
866
1060
  async function positionSell(input, deps) {
867
- if (!/^\d+(\.\d+)?$/.test(input.amountShares)) {
868
- return err(
869
- "invalid_input",
870
- `amountShares must be a non-negative number string: ${JSON.stringify(input.amountShares)}`
871
- );
872
- }
873
1061
  const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "sell", {
874
1062
  token: input.token,
875
1063
  shares: input.amountShares
@@ -1108,6 +1296,10 @@ var HANDLERS = {
1108
1296
  schema: ReadAuditInputSchema,
1109
1297
  handler: readAudit
1110
1298
  },
1299
+ "muhaven.read.activity": {
1300
+ schema: ReadActivityInputSchema,
1301
+ handler: readActivity
1302
+ },
1111
1303
  "muhaven.position.buy": {
1112
1304
  schema: PositionBuyInputSchema,
1113
1305
  handler: positionBuy
@@ -1212,7 +1404,7 @@ var SERVER_NAME = "@muhaven/mcp";
1212
1404
  var SERVER_VERSION = resolveServerVersion();
1213
1405
  function resolveServerVersion() {
1214
1406
  {
1215
- return "0.1.7";
1407
+ return "0.2.1";
1216
1408
  }
1217
1409
  }
1218
1410
  function toJsonInputSchema(schema) {
package/manifest.json CHANGED
@@ -3,9 +3,9 @@
3
3
  "manifest_version": "0.2",
4
4
  "name": "muhaven-mcp",
5
5
  "display_name": "MuHaven (RWA portfolio)",
6
- "version": "0.1.7",
6
+ "version": "0.2.1",
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
- "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.",
8
+ "long_description": "MuHaven MCP exposes 23 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": {
10
10
  "name": "MuHaven",
11
11
  "email": "hello@muhaven.app",
@@ -42,7 +42,8 @@
42
42
  { "name": "muhaven.read.distribution", "description": "Distribution status for a (token, epoch).", "sensitive": false },
43
43
  { "name": "muhaven.read.tokens", "description": "RWA tokens the user holds.", "sensitive": false },
44
44
  { "name": "muhaven.read.audit", "description": "User's tiered-autonomy audit log.", "sensitive": false },
45
- { "name": "muhaven.position.buy", "description": "Propose a Subscription buy. Returns unsigned UserOp.", "sensitive": true },
45
+ { "name": "muhaven.read.activity", "description": "User's on-chain activity feed (buys / sells / wraps / yields) — verify Path C settles.", "sensitive": false },
46
+ { "name": "muhaven.position.buy", "description": "Prepare a Subscription buy (converts mhUSDC → integer shares at current NAV). Returns dashboard deep-link.", "sensitive": true },
46
47
  { "name": "muhaven.position.sell", "description": "Propose a redemption-queue sell. Returns unsigned UserOp.", "sensitive": true },
47
48
  { "name": "muhaven.position.claim", "description": "Propose a yield claim. Returns unsigned UserOp.", "sensitive": true },
48
49
  { "name": "muhaven.position.rebalance", "description": "Propose a multi-leg atomic rebalance.", "sensitive": true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhaven/mcp",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
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-17T18:17:06.859Z",
2
+ "generatedAt": "2026-05-18T03:07:17.347Z",
3
3
  "tools": [
4
4
  {
5
5
  "name": "muhaven.read.portfolio",
@@ -21,13 +21,17 @@
21
21
  "name": "muhaven.read.audit",
22
22
  "sha256": "a55b698911e8774b8022c98e86582368e76b12a7c8447afa57a4cfa9d3d6ae04"
23
23
  },
24
+ {
25
+ "name": "muhaven.read.activity",
26
+ "sha256": "43294a47fa629b5a009a4fa3860aef4764d7c67ce7f9349cf70c4a1614c42d29"
27
+ },
24
28
  {
25
29
  "name": "muhaven.position.buy",
26
- "sha256": "f24c9628249e91ed485031f9114053fa6b174a5d55aedfb4109392eb5795c9e4"
30
+ "sha256": "3a26611be6f189d5c67d74e9db18f1c18888e2d9851222f94c320d975005b5c5"
27
31
  },
28
32
  {
29
33
  "name": "muhaven.position.sell",
30
- "sha256": "707545106fdd505d7d942153bcb34d7ead53c2539b9699e0733159a57c72177d"
34
+ "sha256": "2cd3cd7b542de0273b7314d82f8d8c811cff439d62c3f16fa38109a493bb9c7f"
31
35
  },
32
36
  {
33
37
  "name": "muhaven.position.claim",
@@ -39,7 +43,7 @@
39
43
  },
40
44
  {
41
45
  "name": "muhaven.cash.wrap",
42
- "sha256": "1ae4cb949e0d09e7fb7e8957e51bfc63b43397c253849c217b835aaa9505b8c9"
46
+ "sha256": "e74607e49092bd7090b9ce6df92936713c01e2da4ad5a728f55064d5026bcaec"
43
47
  },
44
48
  {
45
49
  "name": "muhaven.policy.set_tier",