@muhaven/mcp 0.1.5 → 0.1.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 +155 -0
- package/dist/broker.cjs +168 -5
- package/dist/broker.js +168 -5
- package/dist/index.cjs +116 -110
- package/dist/index.d.cts +1 -21
- package/dist/index.d.ts +1 -21
- package/dist/index.js +116 -110
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/tool-hashes.json +9 -5
package/dist/index.cjs
CHANGED
|
@@ -11,7 +11,7 @@ var zod = require('zod');
|
|
|
11
11
|
var os = require('os');
|
|
12
12
|
var net = require('net');
|
|
13
13
|
var crypto = require('crypto');
|
|
14
|
-
|
|
14
|
+
require('viem');
|
|
15
15
|
var accounts = require('viem/accounts');
|
|
16
16
|
|
|
17
17
|
// ../../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
|
|
@@ -467,25 +467,32 @@ var TOOL_DESCRIPTORS = [
|
|
|
467
467
|
{
|
|
468
468
|
name: "muhaven.position.buy",
|
|
469
469
|
group: "position",
|
|
470
|
-
description:
|
|
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.`,
|
|
471
471
|
sensitive: true
|
|
472
472
|
},
|
|
473
473
|
{
|
|
474
474
|
name: "muhaven.position.sell",
|
|
475
475
|
group: "position",
|
|
476
|
-
description: "
|
|
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.",
|
|
477
477
|
sensitive: true
|
|
478
478
|
},
|
|
479
479
|
{
|
|
480
480
|
name: "muhaven.position.claim",
|
|
481
481
|
group: "position",
|
|
482
|
-
description: "
|
|
482
|
+
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.",
|
|
483
483
|
sensitive: true
|
|
484
484
|
},
|
|
485
485
|
{
|
|
486
486
|
name: "muhaven.position.rebalance",
|
|
487
487
|
group: "position",
|
|
488
|
-
description: "
|
|
488
|
+
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.",
|
|
489
|
+
sensitive: true
|
|
490
|
+
},
|
|
491
|
+
// ── Path C cash group (2026-05-18) ────────────────────────────────
|
|
492
|
+
{
|
|
493
|
+
name: "muhaven.cash.wrap",
|
|
494
|
+
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.',
|
|
489
496
|
sensitive: true
|
|
490
497
|
},
|
|
491
498
|
{
|
|
@@ -611,6 +618,13 @@ function verifyDescriptorAgainstPin(descriptor, pinnedSha256) {
|
|
|
611
618
|
}
|
|
612
619
|
var HEX_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
613
620
|
var addressSchema = zod.z.string().regex(HEX_ADDRESS_RE, "must be a 0x-prefixed 20-byte hex address");
|
|
621
|
+
var TOKEN_SYMBOL_RE = /^[A-Za-z][A-Za-z0-9]{0,11}$/;
|
|
622
|
+
var tokenIdentifierSchema = zod.z.string().refine(
|
|
623
|
+
(v) => HEX_ADDRESS_RE.test(v) || TOKEN_SYMBOL_RE.test(v),
|
|
624
|
+
{
|
|
625
|
+
message: "must be a 0x-prefixed 20-byte hex address OR a token symbol (1-12 alphanumeric chars, starting with a letter)"
|
|
626
|
+
}
|
|
627
|
+
);
|
|
614
628
|
var tierSchema = zod.z.enum(["advisory", "confirm-per-action", "policy-bound", "paused"]);
|
|
615
629
|
var surfaceSchema = zod.z.enum(["havenbot", "mcp", "openclaw", "checkout"]);
|
|
616
630
|
zod.z.union([zod.z.literal(1), zod.z.literal(2), zod.z.literal(3), zod.z.literal(4)]);
|
|
@@ -647,29 +661,38 @@ var ReadAuditInputSchema = zod.z.object({
|
|
|
647
661
|
limit: zod.z.number().int().min(1).max(200).optional()
|
|
648
662
|
}).strict();
|
|
649
663
|
var PositionBuyInputSchema = zod.z.object({
|
|
650
|
-
|
|
664
|
+
/** Symbol (e.g. "TBILL1") or 0x-address. Path C dashboard resolves either. */
|
|
665
|
+
token: tokenIdentifierSchema,
|
|
651
666
|
/** Investor candidate spend, denominated in USDC base units (uint64). */
|
|
652
667
|
amountUsdc6: zod.z.string().regex(/^\d+$/, "must be a base-10 integer string")
|
|
653
668
|
}).strict();
|
|
654
669
|
var PositionSellInputSchema = zod.z.object({
|
|
655
|
-
token:
|
|
670
|
+
token: tokenIdentifierSchema,
|
|
656
671
|
/** Encrypted-balance share count to redeem, denominated in fhERC-20 base units. */
|
|
657
672
|
amountShares: zod.z.string().regex(/^\d+$/, "must be a base-10 integer string")
|
|
658
673
|
}).strict();
|
|
659
674
|
var PositionClaimInputSchema = zod.z.object({
|
|
660
|
-
token:
|
|
661
|
-
/** When set,
|
|
675
|
+
token: tokenIdentifierSchema,
|
|
676
|
+
/** When set, deep-link highlights the specific epoch row; else /yields renders the full claimable list. */
|
|
662
677
|
escrowId: zod.z.string().regex(/^\d+$/).optional()
|
|
663
678
|
}).strict();
|
|
664
679
|
var PositionRebalanceInputSchema = zod.z.object({
|
|
665
680
|
legs: zod.z.array(
|
|
666
681
|
zod.z.object({
|
|
667
|
-
token:
|
|
682
|
+
token: tokenIdentifierSchema,
|
|
668
683
|
side: zod.z.enum(["buy", "sell"]),
|
|
669
684
|
amount: zod.z.string().regex(/^\d+$/)
|
|
670
685
|
}).strict()
|
|
671
686
|
).min(2).max(8)
|
|
672
687
|
}).strict();
|
|
688
|
+
var CashWrapInputSchema = zod.z.object({
|
|
689
|
+
/**
|
|
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+`.
|
|
693
|
+
*/
|
|
694
|
+
amountUsdc: zod.z.string().regex(/^\d+(\.\d+)?$/, "must be a positive decimal number string")
|
|
695
|
+
}).strict();
|
|
673
696
|
var PolicySetTierInputSchema = zod.z.object({
|
|
674
697
|
targetTier: tierSchema,
|
|
675
698
|
/** Returned by an earlier `request` call. Omit for step-down or first-call. */
|
|
@@ -740,15 +763,6 @@ function authRequiredPayload() {
|
|
|
740
763
|
loginCommand: "muhaven-broker login"
|
|
741
764
|
};
|
|
742
765
|
}
|
|
743
|
-
function sessionKeyRequiredPayload(dashboardBaseUrl = "https://muhaven.app") {
|
|
744
|
-
const mintUrl = `${trimTrailingSlash(dashboardBaseUrl)}/agent/policy/transition`;
|
|
745
|
-
return {
|
|
746
|
-
ok: false,
|
|
747
|
-
code: "SESSION_KEY_REQUIRED",
|
|
748
|
-
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.`,
|
|
749
|
-
mintUrl
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
766
|
|
|
753
767
|
// src/tools/handlers.ts
|
|
754
768
|
function ok(data) {
|
|
@@ -765,11 +779,6 @@ function mapBackendError(e) {
|
|
|
765
779
|
if (e instanceof Error) return err("backend.network", e.message);
|
|
766
780
|
return err("backend.network", "unknown backend error");
|
|
767
781
|
}
|
|
768
|
-
function mapBrokerError(e) {
|
|
769
|
-
if (e instanceof BrokerClientError) return err(`broker.${e.code}`, e.message);
|
|
770
|
-
if (e instanceof Error) return err("broker.network", e.message);
|
|
771
|
-
return err("broker.network", "unknown broker error");
|
|
772
|
-
}
|
|
773
782
|
async function readPortfolio(_input, deps) {
|
|
774
783
|
try {
|
|
775
784
|
const data = await deps.backend.get("/api/v1/portfolio");
|
|
@@ -823,103 +832,95 @@ async function readAudit(input, deps) {
|
|
|
823
832
|
return mapBackendError(e);
|
|
824
833
|
}
|
|
825
834
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
835
|
+
function buildPositionDeeplink(dashboardBaseUrl, action, params) {
|
|
836
|
+
const base = dashboardBaseUrl.replace(/\/+$/, "");
|
|
837
|
+
const path = action === "buy" || action === "sell" ? "/trade" : action === "claim" ? "/yields" : "/cash";
|
|
838
|
+
const search = new URLSearchParams();
|
|
839
|
+
if (action === "buy" || action === "sell") search.set("mode", action);
|
|
840
|
+
for (const [k, v] of Object.entries(params)) search.set(k, v);
|
|
841
|
+
search.set("from", "mcp");
|
|
842
|
+
return `${base}${path}?${search.toString()}`;
|
|
830
843
|
}
|
|
831
|
-
function
|
|
832
|
-
if (
|
|
833
|
-
|
|
834
|
-
const obj = value;
|
|
835
|
-
const sorted = {};
|
|
836
|
-
for (const k of Object.keys(obj).sort()) sorted[k] = sortKeys(obj[k]);
|
|
837
|
-
return sorted;
|
|
844
|
+
function formatUsdc6ToDecimal(amountUsdc6) {
|
|
845
|
+
if (!/^\d+$/.test(amountUsdc6)) {
|
|
846
|
+
throw new Error(`amountUsdc6 must be a non-negative integer string: ${amountUsdc6}`);
|
|
838
847
|
}
|
|
839
|
-
|
|
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}`;
|
|
840
852
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const intentHash = computeIntentHash(intent);
|
|
844
|
-
if (!deps.broker) {
|
|
845
|
-
return err(
|
|
846
|
-
"broker.unavailable",
|
|
847
|
-
"position tools require a running muhaven-broker daemon \u2014 see README \xA7Broker setup"
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
const broker = deps.broker;
|
|
851
|
-
if (cachedHasSessionKeyProbe === null) {
|
|
852
|
-
cachedHasSessionKeyProbe = (async () => {
|
|
853
|
-
try {
|
|
854
|
-
const hello = await broker.hello();
|
|
855
|
-
return hello.hasSessionKey ?? true;
|
|
856
|
-
} catch (err2) {
|
|
857
|
-
cachedHasSessionKeyProbe = null;
|
|
858
|
-
throw err2;
|
|
859
|
-
}
|
|
860
|
-
})();
|
|
861
|
-
}
|
|
862
|
-
let hasSessionKey;
|
|
863
|
-
try {
|
|
864
|
-
hasSessionKey = await cachedHasSessionKeyProbe;
|
|
865
|
-
} catch (e) {
|
|
866
|
-
return mapBrokerError(e);
|
|
867
|
-
}
|
|
868
|
-
if (hasSessionKey === false) {
|
|
869
|
-
return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
|
|
870
|
-
}
|
|
871
|
-
try {
|
|
872
|
-
const sig = await broker.signHash(intentHash, { tool: toolName, summary });
|
|
873
|
-
return ok({
|
|
874
|
-
intentHash,
|
|
875
|
-
unsignedUserOp: {
|
|
876
|
-
target: "see backend",
|
|
877
|
-
data: "see backend",
|
|
878
|
-
note: "P3 returns a placeholder envelope; P6 wires the canonical UserOp shape."
|
|
879
|
-
},
|
|
880
|
-
brokerSignature: sig.signature,
|
|
881
|
-
signerAddress: sig.signerAddress
|
|
882
|
-
});
|
|
883
|
-
} catch (e) {
|
|
884
|
-
if (e instanceof BrokerClientError && e.code === "broker_error" && /session_key_unavailable/.test(e.message)) {
|
|
885
|
-
cachedHasSessionKeyProbe = Promise.resolve(false);
|
|
886
|
-
return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
|
|
887
|
-
}
|
|
888
|
-
return mapBrokerError(e);
|
|
889
|
-
}
|
|
853
|
+
function resolveDashboardBaseUrl(deps) {
|
|
854
|
+
return deps.dashboardBaseUrl ?? "https://muhaven.app";
|
|
890
855
|
}
|
|
891
856
|
async function positionBuy(input, deps) {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
857
|
+
const amount = formatUsdc6ToDecimal(input.amountUsdc6);
|
|
858
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "buy", {
|
|
859
|
+
token: input.token,
|
|
860
|
+
amount
|
|
861
|
+
});
|
|
862
|
+
return ok({
|
|
863
|
+
dashboardUrl,
|
|
864
|
+
action: "buy",
|
|
865
|
+
instructions: `Open this link to review and authorize the buy of ${amount} mhUSDC of ${input.token}:
|
|
866
|
+
${dashboardUrl}`,
|
|
867
|
+
echo: { action: "buy", token: input.token, amount }
|
|
868
|
+
});
|
|
898
869
|
}
|
|
899
870
|
async function positionSell(input, deps) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "sell", {
|
|
878
|
+
token: input.token,
|
|
879
|
+
shares: input.amountShares
|
|
880
|
+
});
|
|
881
|
+
return ok({
|
|
882
|
+
dashboardUrl,
|
|
883
|
+
action: "sell",
|
|
884
|
+
instructions: `Open this link to review and authorize the sale of ${input.amountShares} shares of ${input.token}:
|
|
885
|
+
${dashboardUrl}`,
|
|
886
|
+
echo: { action: "sell", token: input.token, shares: input.amountShares }
|
|
887
|
+
});
|
|
906
888
|
}
|
|
907
889
|
async function positionClaim(input, deps) {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
890
|
+
const params = { token: input.token };
|
|
891
|
+
if (input.escrowId) params.epoch = input.escrowId;
|
|
892
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "claim", params);
|
|
893
|
+
const claimDescriptor = input.escrowId ? `the claim for epoch #${input.escrowId} of ${input.token}` : `your claimable epochs for ${input.token}`;
|
|
894
|
+
return ok({
|
|
895
|
+
dashboardUrl,
|
|
896
|
+
action: "claim",
|
|
897
|
+
instructions: `Open this link to review and authorize ${claimDescriptor}:
|
|
898
|
+
${dashboardUrl}`,
|
|
899
|
+
echo: {
|
|
900
|
+
action: "claim",
|
|
901
|
+
token: input.token,
|
|
902
|
+
...input.escrowId ? { epoch: input.escrowId } : {}
|
|
903
|
+
}
|
|
904
|
+
});
|
|
914
905
|
}
|
|
915
|
-
async function positionRebalance(
|
|
916
|
-
return
|
|
917
|
-
|
|
918
|
-
"
|
|
919
|
-
`rebalance ${input.legs.length} legs`,
|
|
920
|
-
deps
|
|
906
|
+
async function positionRebalance(_input, _deps) {
|
|
907
|
+
return err(
|
|
908
|
+
"not_implemented",
|
|
909
|
+
"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."
|
|
921
910
|
);
|
|
922
911
|
}
|
|
912
|
+
async function cashWrap(input, deps) {
|
|
913
|
+
const dashboardUrl = buildPositionDeeplink(resolveDashboardBaseUrl(deps), "wrap", {
|
|
914
|
+
amount: input.amountUsdc
|
|
915
|
+
});
|
|
916
|
+
return ok({
|
|
917
|
+
dashboardUrl,
|
|
918
|
+
action: "wrap",
|
|
919
|
+
instructions: `Open this link to review and authorize the conversion of ${input.amountUsdc} USDC into mhUSDC:
|
|
920
|
+
${dashboardUrl}`,
|
|
921
|
+
echo: { action: "wrap", amount: input.amountUsdc }
|
|
922
|
+
});
|
|
923
|
+
}
|
|
923
924
|
async function policySetTier(input, deps) {
|
|
924
925
|
try {
|
|
925
926
|
const data = await deps.backend.post("/api/v1/agent/policy/transition", {
|
|
@@ -1127,6 +1128,11 @@ var HANDLERS = {
|
|
|
1127
1128
|
schema: PositionRebalanceInputSchema,
|
|
1128
1129
|
handler: positionRebalance
|
|
1129
1130
|
},
|
|
1131
|
+
// ── Path C cash group (2026-05-18) ────────────────────────────────
|
|
1132
|
+
"muhaven.cash.wrap": {
|
|
1133
|
+
schema: CashWrapInputSchema,
|
|
1134
|
+
handler: cashWrap
|
|
1135
|
+
},
|
|
1130
1136
|
"muhaven.policy.set_tier": {
|
|
1131
1137
|
schema: PolicySetTierInputSchema,
|
|
1132
1138
|
handler: policySetTier
|
|
@@ -1210,7 +1216,7 @@ var SERVER_NAME = "@muhaven/mcp";
|
|
|
1210
1216
|
var SERVER_VERSION = resolveServerVersion();
|
|
1211
1217
|
function resolveServerVersion() {
|
|
1212
1218
|
{
|
|
1213
|
-
return "0.1.
|
|
1219
|
+
return "0.1.7";
|
|
1214
1220
|
}
|
|
1215
1221
|
}
|
|
1216
1222
|
function toJsonInputSchema(schema) {
|
package/dist/index.d.cts
CHANGED
|
@@ -298,7 +298,7 @@ interface ToolDescriptor {
|
|
|
298
298
|
* `read` so `--read-only` keeps them available; the two propose
|
|
299
299
|
* tools (`muhaven.governance.propose`, `muhaven.governance.cast_vote`)
|
|
300
300
|
* are filtered off in read-only mode. */
|
|
301
|
-
readonly group: 'read' | 'position' | 'policy' | 'issuer' | 'governance';
|
|
301
|
+
readonly group: 'read' | 'position' | 'policy' | 'issuer' | 'governance' | 'cash';
|
|
302
302
|
/** Human-readable description shown in the host UI. */
|
|
303
303
|
readonly description: string;
|
|
304
304
|
/** When true, the host SHOULD render a confirmation cue before invoking. */
|
|
@@ -384,26 +384,6 @@ interface SessionKeyRequiredPayload {
|
|
|
384
384
|
readonly mintUrl: string;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
/**
|
|
388
|
-
* Tool handlers — pure functions of `(input, deps)` returning a structured
|
|
389
|
-
* tool result. The MCP server transport layer (`src/server.ts`) wires
|
|
390
|
-
* these to the host LLM via `@modelcontextprotocol/sdk`.
|
|
391
|
-
*
|
|
392
|
-
* Design notes:
|
|
393
|
-
* - Handlers NEVER throw. They translate every error into a structured
|
|
394
|
-
* `{ ok: false, code, message }` payload so the host LLM can decide
|
|
395
|
-
* how to surface it without crashing the MCP server. This matches the
|
|
396
|
-
* MCPB error-presentation convention.
|
|
397
|
-
* - Position handlers DO NOT submit UserOps. They return an unsigned
|
|
398
|
-
* envelope plus a broker signature; the host (or the MuHaven
|
|
399
|
-
* dashboard via deep-link) is responsible for bundler submission.
|
|
400
|
-
* Splitting submission from signing is the lethal-trifecta defense.
|
|
401
|
-
* - Backend errors with status >= 500 are surfaced as `server_error`
|
|
402
|
-
* so the host can retry; client errors (4xx) bubble up as the
|
|
403
|
-
* discriminating code (`unauthorized`, `forbidden`, etc.). The host
|
|
404
|
-
* MUST NOT auto-retry 4xx.
|
|
405
|
-
*/
|
|
406
|
-
|
|
407
387
|
interface ToolDeps {
|
|
408
388
|
backend: BackendClient;
|
|
409
389
|
broker?: BrokerClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -298,7 +298,7 @@ interface ToolDescriptor {
|
|
|
298
298
|
* `read` so `--read-only` keeps them available; the two propose
|
|
299
299
|
* tools (`muhaven.governance.propose`, `muhaven.governance.cast_vote`)
|
|
300
300
|
* are filtered off in read-only mode. */
|
|
301
|
-
readonly group: 'read' | 'position' | 'policy' | 'issuer' | 'governance';
|
|
301
|
+
readonly group: 'read' | 'position' | 'policy' | 'issuer' | 'governance' | 'cash';
|
|
302
302
|
/** Human-readable description shown in the host UI. */
|
|
303
303
|
readonly description: string;
|
|
304
304
|
/** When true, the host SHOULD render a confirmation cue before invoking. */
|
|
@@ -384,26 +384,6 @@ interface SessionKeyRequiredPayload {
|
|
|
384
384
|
readonly mintUrl: string;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
/**
|
|
388
|
-
* Tool handlers — pure functions of `(input, deps)` returning a structured
|
|
389
|
-
* tool result. The MCP server transport layer (`src/server.ts`) wires
|
|
390
|
-
* these to the host LLM via `@modelcontextprotocol/sdk`.
|
|
391
|
-
*
|
|
392
|
-
* Design notes:
|
|
393
|
-
* - Handlers NEVER throw. They translate every error into a structured
|
|
394
|
-
* `{ ok: false, code, message }` payload so the host LLM can decide
|
|
395
|
-
* how to surface it without crashing the MCP server. This matches the
|
|
396
|
-
* MCPB error-presentation convention.
|
|
397
|
-
* - Position handlers DO NOT submit UserOps. They return an unsigned
|
|
398
|
-
* envelope plus a broker signature; the host (or the MuHaven
|
|
399
|
-
* dashboard via deep-link) is responsible for bundler submission.
|
|
400
|
-
* Splitting submission from signing is the lethal-trifecta defense.
|
|
401
|
-
* - Backend errors with status >= 500 are surfaced as `server_error`
|
|
402
|
-
* so the host can retry; client errors (4xx) bubble up as the
|
|
403
|
-
* discriminating code (`unauthorized`, `forbidden`, etc.). The host
|
|
404
|
-
* MUST NOT auto-retry 4xx.
|
|
405
|
-
*/
|
|
406
|
-
|
|
407
387
|
interface ToolDeps {
|
|
408
388
|
backend: BackendClient;
|
|
409
389
|
broker?: BrokerClient;
|