@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.cjs CHANGED
@@ -11,7 +11,6 @@ var zod = require('zod');
11
11
  var os = require('os');
12
12
  var net = require('net');
13
13
  var crypto = require('crypto');
14
- require('viem');
15
14
  var accounts = require('viem/accounts');
16
15
 
17
16
  // ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
@@ -61,9 +60,38 @@ function deriveAllowedHosts(baseUrl) {
61
60
  function trimTrailingSlash(s) {
62
61
  return s.endsWith("/") ? s.slice(0, -1) : s;
63
62
  }
63
+ function validatePublicUrlEnv(name, value) {
64
+ let parsed;
65
+ try {
66
+ parsed = new URL(value);
67
+ } catch {
68
+ return `${name} is not a valid URL: ${value}`;
69
+ }
70
+ if (parsed.protocol === "https:") return null;
71
+ if (parsed.protocol === "http:") {
72
+ const host = parsed.hostname;
73
+ if (host === "localhost" || host === "127.0.0.1" || host === "[::1]") return null;
74
+ return `${name} must use https:// (got http:// to ${host} \u2014 refusing to route MCP deep-links over cleartext to a non-loopback host)`;
75
+ }
76
+ return `${name} must use https:// (got ${parsed.protocol})`;
77
+ }
78
+ function resolvePublicUrlEnv(name, rawValue, defaultValue) {
79
+ const value = rawValue ?? defaultValue;
80
+ const err2 = validatePublicUrlEnv(name, value);
81
+ if (err2) throw new Error(err2);
82
+ return trimTrailingSlash(value);
83
+ }
64
84
  function loadMcpConfig(env = process.env) {
65
- const backendBaseUrl = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
66
- const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
85
+ const backendBaseUrl = resolvePublicUrlEnv(
86
+ "MUHAVEN_BACKEND_URL",
87
+ env.MUHAVEN_BACKEND_URL,
88
+ DEFAULT_BACKEND_URL
89
+ );
90
+ const dashboardBaseUrl = resolvePublicUrlEnv(
91
+ "MUHAVEN_DASHBOARD_URL",
92
+ env.MUHAVEN_DASHBOARD_URL,
93
+ DEFAULT_DASHBOARD_URL
94
+ );
67
95
  const brokerEndpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
68
96
  const readOnly = readEnvBool("MUHAVEN_READ_ONLY", false, env);
69
97
  const requestTimeoutMs = readEnvInt("MUHAVEN_REQUEST_TIMEOUT_MS", DEFAULT_REQUEST_TIMEOUT_MS, env);
@@ -93,8 +121,16 @@ function loadBrokerConfig(env = process.env) {
93
121
  const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
94
122
  const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
95
123
  const requestTimeoutMs = readEnvInt("MUHAVEN_BROKER_TIMEOUT_MS", DEFAULT_BROKER_TIMEOUT_MS, env);
96
- const backendBaseUrl = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
97
- const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
124
+ const backendBaseUrl = resolvePublicUrlEnv(
125
+ "MUHAVEN_BACKEND_URL",
126
+ env.MUHAVEN_BACKEND_URL,
127
+ DEFAULT_BACKEND_URL
128
+ );
129
+ const dashboardBaseUrl = resolvePublicUrlEnv(
130
+ "MUHAVEN_DASHBOARD_URL",
131
+ env.MUHAVEN_DASHBOARD_URL,
132
+ DEFAULT_DASHBOARD_URL
133
+ );
98
134
  return {
99
135
  endpoint,
100
136
  sessionKeyHex,
@@ -341,6 +377,23 @@ var BackendClient = class {
341
377
  false
342
378
  );
343
379
  }
380
+ /**
381
+ * GET variant that sends no Authorization header. Use for backend
382
+ * endpoints that are intentionally public (e.g. `/api/v1/tokens`
383
+ * which the marketplace + the 0.2.1 `positionBuy` NAV-conversion
384
+ * both read). Avoids triggering the AUTH_REQUIRED branch for the
385
+ * "not yet logged in" case on read paths that don't need auth.
386
+ */
387
+ async getUnauth(path, query) {
388
+ const url = this.buildUrl(path, query);
389
+ return this.exchange(
390
+ "GET",
391
+ url,
392
+ void 0,
393
+ /* withAuth */
394
+ false
395
+ );
396
+ }
344
397
  buildUrl(path, query) {
345
398
  if (!path.startsWith("/")) {
346
399
  throw new BackendError("bad_request", `path must start with "/": ${path}`);
@@ -464,16 +517,22 @@ var TOOL_DESCRIPTORS = [
464
517
  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.`,
465
518
  sensitive: false
466
519
  },
520
+ {
521
+ name: "muhaven.read.activity",
522
+ group: "read",
523
+ 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).",
524
+ sensitive: false
525
+ },
467
526
  {
468
527
  name: "muhaven.position.buy",
469
528
  group: "position",
470
- 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.`,
529
+ 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).',
471
530
  sensitive: true
472
531
  },
473
532
  {
474
533
  name: "muhaven.position.sell",
475
534
  group: "position",
476
- 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.",
535
+ 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).',
477
536
  sensitive: true
478
537
  },
479
538
  {
@@ -492,7 +551,7 @@ var TOOL_DESCRIPTORS = [
492
551
  {
493
552
  name: "muhaven.cash.wrap",
494
553
  group: "cash",
495
- 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.',
554
+ 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).',
496
555
  sensitive: true
497
556
  },
498
557
  {
@@ -660,16 +719,34 @@ var ReadAuditInputSchema = zod.z.object({
660
719
  cursor: zod.z.string().min(1).max(512).optional(),
661
720
  limit: zod.z.number().int().min(1).max(200).optional()
662
721
  }).strict();
722
+ var ReadActivityInputSchema = zod.z.object({
723
+ limit: zod.z.number().int().min(1).max(50).optional(),
724
+ offset: zod.z.number().int().min(0).max(1e3).optional()
725
+ }).strict();
726
+ var decimalUsdcAmountSchema = zod.z.string().regex(
727
+ /^(0|[1-9]\d*)(\.\d{1,6})?$/,
728
+ 'must be a positive decimal mhUSDC amount with at most 6 fractional digits (e.g. "5", "0.5", "1234.567")'
729
+ ).max(48, "must be at most 48 characters");
663
730
  var PositionBuyInputSchema = zod.z.object({
664
731
  /** Symbol (e.g. "TBILL1") or 0x-address. Path C dashboard resolves either. */
665
732
  token: tokenIdentifierSchema,
666
- /** Investor candidate spend, denominated in USDC base units (uint64). */
667
- amountUsdc6: zod.z.string().regex(/^\d+$/, "must be a base-10 integer string")
733
+ /**
734
+ * mhUSDC amount in human-decimal units ("5" = 5 mhUSDC, "0.5" =
735
+ * half a mhUSDC). Forwarded verbatim to the dashboard form via
736
+ * `/trade?amount=`. Replaces the prior `amountUsdc6` base-6 integer
737
+ * field — see schema doc above for rationale (LLM-footgun fix).
738
+ */
739
+ amountUsdc: decimalUsdcAmountSchema
668
740
  }).strict();
669
741
  var PositionSellInputSchema = zod.z.object({
670
742
  token: tokenIdentifierSchema,
671
- /** Encrypted-balance share count to redeem, denominated in fhERC-20 base units. */
672
- amountShares: zod.z.string().regex(/^\d+$/, "must be a base-10 integer string")
743
+ /**
744
+ * Share count to redeem. fhERC-20 shares are integer base units
745
+ * (no decimals — see memory `project_decimals_lie_wave4_p0`).
746
+ * Regex rejects any fractional input so a deep-link can't pre-fill
747
+ * "2.5 shares" that would silently floor on the on-chain submit.
748
+ */
749
+ amountShares: zod.z.string().regex(/^[1-9]\d*$/, "must be a positive integer share count")
673
750
  }).strict();
674
751
  var PositionClaimInputSchema = zod.z.object({
675
752
  token: tokenIdentifierSchema,
@@ -687,11 +764,13 @@ var PositionRebalanceInputSchema = zod.z.object({
687
764
  }).strict();
688
765
  var CashWrapInputSchema = zod.z.object({
689
766
  /**
690
- * USDC amount in human-readable units ("100" for $100, "1.5" for
691
- * $1.50). Forwarded verbatim to `/cash?amount=`. Decimal optional
692
- * CashPage parses both `\d+` and `\d+\.\d+`.
767
+ * USDC amount in human-decimal units ("100" for $100, "1.5" for
768
+ * $1.50). Same shape + same regex as `PositionBuyInputSchema.amountUsdc`
769
+ * so the LLM doesn't have to learn two different unit conventions
770
+ * across the Path C surface. Max 6 fractional digits (USDC's base
771
+ * unit floor); 48-char length cap is URL-bloat defense.
693
772
  */
694
- amountUsdc: zod.z.string().regex(/^\d+(\.\d+)?$/, "must be a positive decimal number string")
773
+ amountUsdc: decimalUsdcAmountSchema
695
774
  }).strict();
696
775
  var PolicySetTierInputSchema = zod.z.object({
697
776
  targetTier: tierSchema,
@@ -764,6 +843,37 @@ function authRequiredPayload() {
764
843
  };
765
844
  }
766
845
 
846
+ // src/tools/decimal.ts
847
+ function parseDecimalToUsd6(decimal) {
848
+ const m = /^(\d+)(?:\.(\d+))?$/.exec(decimal);
849
+ if (!m) {
850
+ throw new Error(`Invalid decimal price: ${JSON.stringify(decimal)}`);
851
+ }
852
+ const intPart = m[1];
853
+ const fracPart = m[2] ?? "";
854
+ const fracPadded = (fracPart + "000000").slice(0, 6);
855
+ return BigInt(intPart + fracPadded);
856
+ }
857
+ function computeSharesFromUsd6(notionalUsd6, navUsd6) {
858
+ if (navUsd6 <= 0n) {
859
+ throw new Error("navUsd6 must be positive");
860
+ }
861
+ if (notionalUsd6 < 0n) {
862
+ throw new Error("notionalUsd6 must be non-negative");
863
+ }
864
+ return notionalUsd6 / navUsd6;
865
+ }
866
+ function formatUsd6AsDecimal(usd6) {
867
+ if (usd6 < 0n) {
868
+ throw new Error("usd6 must be non-negative");
869
+ }
870
+ const whole = usd6 / 1000000n;
871
+ const frac = usd6 % 1000000n;
872
+ if (frac === 0n) return whole.toString();
873
+ const fracStr = frac.toString().padStart(6, "0").replace(/0+$/, "");
874
+ return `${whole.toString()}.${fracStr}`;
875
+ }
876
+
767
877
  // src/tools/handlers.ts
768
878
  function ok(data) {
769
879
  return { ok: true, data };
@@ -832,6 +942,17 @@ async function readAudit(input, deps) {
832
942
  return mapBackendError(e);
833
943
  }
834
944
  }
945
+ async function readActivity(input, deps) {
946
+ try {
947
+ const data = await deps.backend.get("/api/v1/activity", {
948
+ limit: input.limit,
949
+ offset: input.offset
950
+ });
951
+ return ok(data);
952
+ } catch (e) {
953
+ return mapBackendError(e);
954
+ }
955
+ }
835
956
  function buildPositionDeeplink(dashboardBaseUrl, action, params) {
836
957
  const base = dashboardBaseUrl.replace(/\/+$/, "");
837
958
  const path = action === "buy" || action === "sell" ? "/trade" : action === "claim" ? "/yields" : "/cash";
@@ -841,39 +962,106 @@ function buildPositionDeeplink(dashboardBaseUrl, action, params) {
841
962
  search.set("from", "mcp");
842
963
  return `${base}${path}?${search.toString()}`;
843
964
  }
844
- function formatUsdc6ToDecimal(amountUsdc6) {
845
- if (!/^\d+$/.test(amountUsdc6)) {
846
- throw new Error(`amountUsdc6 must be a non-negative integer string: ${amountUsdc6}`);
847
- }
848
- const padded = amountUsdc6.padStart(7, "0");
849
- const intPart = padded.slice(0, -6).replace(/^0+(?=\d)/, "");
850
- const fracPart = padded.slice(-6).replace(/0+$/, "");
851
- return fracPart === "" ? intPart : `${intPart}.${fracPart}`;
852
- }
853
965
  function resolveDashboardBaseUrl(deps) {
854
966
  return deps.dashboardBaseUrl ?? "https://muhaven.app";
855
967
  }
968
+ function resolveTokenInCatalog(identifier, catalog) {
969
+ const needle = identifier.toLowerCase();
970
+ return catalog.find(
971
+ (t) => t.address.toLowerCase() === needle || t.symbol.toLowerCase() === needle
972
+ ) ?? null;
973
+ }
974
+ function sanitizeSymbolForLlmContext(raw) {
975
+ const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, "?");
976
+ return cleaned.length > 16 ? cleaned.slice(0, 16) : cleaned;
977
+ }
856
978
  async function positionBuy(input, deps) {
857
- const amount = formatUsdc6ToDecimal(input.amountUsdc6);
979
+ let catalog;
980
+ try {
981
+ catalog = await deps.backend.getUnauth("/api/v1/tokens");
982
+ } catch (e) {
983
+ return mapBackendError(e);
984
+ }
985
+ const token = resolveTokenInCatalog(input.token, catalog.tokens ?? []);
986
+ if (!token) {
987
+ return err(
988
+ "token_not_found",
989
+ `Token "${sanitizeSymbolForLlmContext(input.token)}" is not in the MuHaven catalog. Call muhaven.read.tokens for the canonical symbol list.`
990
+ );
991
+ }
992
+ const safeSymbol = sanitizeSymbolForLlmContext(token.symbol);
993
+ if (!token.latest_nav || !token.latest_nav.nav) {
994
+ return err(
995
+ "nav_unavailable",
996
+ `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.`
997
+ );
998
+ }
999
+ let navUsd6;
1000
+ try {
1001
+ navUsd6 = parseDecimalToUsd6(token.latest_nav.nav);
1002
+ } catch (e) {
1003
+ return err(
1004
+ "nav_malformed",
1005
+ `NAV for ${safeSymbol} is not a valid decimal price. Open the dashboard /trade page directly.`
1006
+ );
1007
+ }
1008
+ if (navUsd6 <= 0n) {
1009
+ return err(
1010
+ "nav_non_positive",
1011
+ `NAV for ${safeSymbol} is non-positive. Cannot quote a buy.`
1012
+ );
1013
+ }
1014
+ let notionalUsd6;
1015
+ try {
1016
+ notionalUsd6 = parseDecimalToUsd6(input.amountUsdc);
1017
+ } catch (e) {
1018
+ return err(
1019
+ "invalid_amount",
1020
+ `amountUsdc "${input.amountUsdc}" is not a valid decimal mhUSDC amount.`
1021
+ );
1022
+ }
1023
+ if (notionalUsd6 <= 0n) {
1024
+ return err(
1025
+ "invalid_amount",
1026
+ "amountUsdc must be greater than zero."
1027
+ );
1028
+ }
1029
+ const shares = computeSharesFromUsd6(notionalUsd6, navUsd6);
1030
+ if (shares <= 0n) {
1031
+ const navDisplay2 = formatUsd6AsDecimal(navUsd6);
1032
+ return err(
1033
+ "amount_too_small_for_share",
1034
+ `${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.`
1035
+ );
1036
+ }
1037
+ const effectiveNotionalUsd6 = shares * navUsd6;
1038
+ const effectiveNotionalDisplay = formatUsd6AsDecimal(effectiveNotionalUsd6);
1039
+ const navDisplay = formatUsd6AsDecimal(navUsd6);
1040
+ const sharesStr = shares.toString();
858
1041
  const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "buy", {
859
- token: input.token,
860
- amount
1042
+ token: token.symbol,
1043
+ amount: sharesStr
861
1044
  });
862
1045
  return ok({
863
1046
  dashboardUrl,
864
1047
  action: "buy",
865
- instructions: `Open this link to review and authorize the buy of ${amount} mhUSDC of ${input.token}:
1048
+ instructions: `Open this link to review and authorize the buy of ${sharesStr} ${safeSymbol} shares (~${effectiveNotionalDisplay} mhUSDC at current NAV $${navDisplay}/share):
866
1049
  ${dashboardUrl}`,
867
- echo: { action: "buy", token: input.token, amount }
1050
+ echo: {
1051
+ action: "buy",
1052
+ token: token.symbol,
1053
+ amount: sharesStr,
1054
+ shares: sharesStr,
1055
+ // Carry the original request + the conversion math so the LLM
1056
+ // (and a human auditor reading the trace) can see why the URL
1057
+ // shows the share count instead of the user-stated notional.
1058
+ amountUsdc: input.amountUsdc,
1059
+ effectiveNotionalUsd6: effectiveNotionalUsd6.toString(),
1060
+ navUsd6: navUsd6.toString()
1061
+ }
868
1062
  });
869
1063
  }
870
1064
  async function positionSell(input, deps) {
871
- if (!/^\d+(\.\d+)?$/.test(input.amountShares)) {
872
- return err(
873
- "invalid_input",
874
- `amountShares must be a non-negative number string: ${JSON.stringify(input.amountShares)}`
875
- );
876
- }
877
1065
  const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "sell", {
878
1066
  token: input.token,
879
1067
  shares: input.amountShares
@@ -1112,6 +1300,10 @@ var HANDLERS = {
1112
1300
  schema: ReadAuditInputSchema,
1113
1301
  handler: readAudit
1114
1302
  },
1303
+ "muhaven.read.activity": {
1304
+ schema: ReadActivityInputSchema,
1305
+ handler: readActivity
1306
+ },
1115
1307
  "muhaven.position.buy": {
1116
1308
  schema: PositionBuyInputSchema,
1117
1309
  handler: positionBuy
@@ -1216,7 +1408,7 @@ var SERVER_NAME = "@muhaven/mcp";
1216
1408
  var SERVER_VERSION = resolveServerVersion();
1217
1409
  function resolveServerVersion() {
1218
1410
  {
1219
- return "0.1.7";
1411
+ return "0.2.1";
1220
1412
  }
1221
1413
  }
1222
1414
  function toJsonInputSchema(schema) {
package/dist/index.d.cts CHANGED
@@ -263,6 +263,14 @@ declare class BackendClient {
263
263
  * Authorization header.
264
264
  */
265
265
  postUnauth<T>(path: string, body: unknown): Promise<T>;
266
+ /**
267
+ * GET variant that sends no Authorization header. Use for backend
268
+ * endpoints that are intentionally public (e.g. `/api/v1/tokens`
269
+ * which the marketplace + the 0.2.1 `positionBuy` NAV-conversion
270
+ * both read). Avoids triggering the AUTH_REQUIRED branch for the
271
+ * "not yet logged in" case on read paths that don't need auth.
272
+ */
273
+ getUnauth<T>(path: string, query?: Record<string, string | number | undefined>): Promise<T>;
266
274
  private buildUrl;
267
275
  private exchangeWithRetry;
268
276
  private exchange;
@@ -305,14 +313,15 @@ interface ToolDescriptor {
305
313
  readonly sensitive: boolean;
306
314
  }
307
315
  /**
308
- * The 22 Wave 4 MCP tools across five groups:
309
- * muhaven.read.* (7 — incl. P11 protection_coverage + kyc_attestation)
316
+ * The 23 MCP tools across five groups:
317
+ * muhaven.read.* (8 — incl. P11 protection_coverage + kyc_attestation
318
+ * + 0.2.1 read.activity for Path C settle verify)
310
319
  * muhaven.position.* (4)
311
320
  * muhaven.policy.* (4)
312
321
  * muhaven.issuer.* (5 — P7)
313
322
  * muhaven.governance.* (2 — P11; cast_vote frontend runner deferred to Wave 5)
314
323
  *
315
- * `MUHAVEN_READ_ONLY=true` exposes only the 7 `muhaven.read.*` tools.
324
+ * `MUHAVEN_READ_ONLY=true` exposes only the 8 `muhaven.read.*` tools.
316
325
  * P5's `muhaven.checkout.*` namespace was retired before Wave 4 close — the
317
326
  * hosted checkout surface ships as a separate Vite SPA (apps/checkout-pay/),
318
327
  * not as an MCP tool group.
package/dist/index.d.ts CHANGED
@@ -263,6 +263,14 @@ declare class BackendClient {
263
263
  * Authorization header.
264
264
  */
265
265
  postUnauth<T>(path: string, body: unknown): Promise<T>;
266
+ /**
267
+ * GET variant that sends no Authorization header. Use for backend
268
+ * endpoints that are intentionally public (e.g. `/api/v1/tokens`
269
+ * which the marketplace + the 0.2.1 `positionBuy` NAV-conversion
270
+ * both read). Avoids triggering the AUTH_REQUIRED branch for the
271
+ * "not yet logged in" case on read paths that don't need auth.
272
+ */
273
+ getUnauth<T>(path: string, query?: Record<string, string | number | undefined>): Promise<T>;
266
274
  private buildUrl;
267
275
  private exchangeWithRetry;
268
276
  private exchange;
@@ -305,14 +313,15 @@ interface ToolDescriptor {
305
313
  readonly sensitive: boolean;
306
314
  }
307
315
  /**
308
- * The 22 Wave 4 MCP tools across five groups:
309
- * muhaven.read.* (7 — incl. P11 protection_coverage + kyc_attestation)
316
+ * The 23 MCP tools across five groups:
317
+ * muhaven.read.* (8 — incl. P11 protection_coverage + kyc_attestation
318
+ * + 0.2.1 read.activity for Path C settle verify)
310
319
  * muhaven.position.* (4)
311
320
  * muhaven.policy.* (4)
312
321
  * muhaven.issuer.* (5 — P7)
313
322
  * muhaven.governance.* (2 — P11; cast_vote frontend runner deferred to Wave 5)
314
323
  *
315
- * `MUHAVEN_READ_ONLY=true` exposes only the 7 `muhaven.read.*` tools.
324
+ * `MUHAVEN_READ_ONLY=true` exposes only the 8 `muhaven.read.*` tools.
316
325
  * P5's `muhaven.checkout.*` namespace was retired before Wave 4 close — the
317
326
  * hosted checkout surface ships as a separate Vite SPA (apps/checkout-pay/),
318
327
  * not as an MCP tool group.