@muhaven/mcp 0.1.2 → 0.1.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 +71 -1
- package/dist/broker.cjs +138 -22
- package/dist/broker.d.cts +21 -1
- package/dist/broker.d.ts +21 -1
- package/dist/broker.js +138 -23
- package/dist/index.cjs +124 -20
- package/dist/index.d.cts +143 -13
- package/dist/index.d.ts +143 -13
- package/dist/index.js +120 -21
- package/manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { unlink, readFile, mkdir, chmod, writeFile, stat } from 'fs/promises';
|
|
2
|
+
import 'fs';
|
|
2
3
|
import { join, dirname } from 'path';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
5
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -77,23 +78,26 @@ function loadMcpConfig(env = process.env) {
|
|
|
77
78
|
}
|
|
78
79
|
var PRIVKEY_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
79
80
|
function loadBrokerConfig(env = process.env) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
|
|
81
|
+
const sessionKeyHexRaw = env.MUHAVEN_BROKER_SESSION_KEY;
|
|
82
|
+
let sessionKeyHex;
|
|
83
|
+
if (sessionKeyHexRaw && sessionKeyHexRaw.length > 0) {
|
|
84
|
+
if (!PRIVKEY_HEX_RE.test(sessionKeyHexRaw)) {
|
|
85
|
+
throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
|
|
86
|
+
}
|
|
87
|
+
sessionKeyHex = sessionKeyHexRaw;
|
|
88
88
|
}
|
|
89
89
|
const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
|
|
90
90
|
const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
|
|
91
91
|
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);
|
|
92
94
|
return {
|
|
93
95
|
endpoint,
|
|
94
96
|
sessionKeyHex,
|
|
95
97
|
maxRequestBytes,
|
|
96
|
-
requestTimeoutMs
|
|
98
|
+
requestTimeoutMs,
|
|
99
|
+
backendBaseUrl,
|
|
100
|
+
dashboardBaseUrl
|
|
97
101
|
};
|
|
98
102
|
}
|
|
99
103
|
var BrokerClientError = class extends Error {
|
|
@@ -732,6 +736,15 @@ function authRequiredPayload() {
|
|
|
732
736
|
loginCommand: "muhaven-broker login"
|
|
733
737
|
};
|
|
734
738
|
}
|
|
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
|
+
}
|
|
735
748
|
|
|
736
749
|
// src/tools/handlers.ts
|
|
737
750
|
function ok(data) {
|
|
@@ -821,6 +834,7 @@ function sortKeys(value) {
|
|
|
821
834
|
}
|
|
822
835
|
return value;
|
|
823
836
|
}
|
|
837
|
+
var cachedHasSessionKeyProbe = null;
|
|
824
838
|
async function signEnvelope(intent, toolName, summary, deps) {
|
|
825
839
|
const intentHash = computeIntentHash(intent);
|
|
826
840
|
if (!deps.broker) {
|
|
@@ -829,8 +843,29 @@ async function signEnvelope(intent, toolName, summary, deps) {
|
|
|
829
843
|
"position tools require a running muhaven-broker daemon \u2014 see README \xA7Broker setup"
|
|
830
844
|
);
|
|
831
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;
|
|
832
859
|
try {
|
|
833
|
-
|
|
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 });
|
|
834
869
|
return ok({
|
|
835
870
|
intentHash,
|
|
836
871
|
unsignedUserOp: {
|
|
@@ -842,6 +877,10 @@ async function signEnvelope(intent, toolName, summary, deps) {
|
|
|
842
877
|
signerAddress: sig.signerAddress
|
|
843
878
|
});
|
|
844
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
|
+
}
|
|
845
884
|
return mapBrokerError(e);
|
|
846
885
|
}
|
|
847
886
|
}
|
|
@@ -1164,7 +1203,12 @@ function selectRegistry(readOnly) {
|
|
|
1164
1203
|
|
|
1165
1204
|
// src/server.ts
|
|
1166
1205
|
var SERVER_NAME = "@muhaven/mcp";
|
|
1167
|
-
var SERVER_VERSION =
|
|
1206
|
+
var SERVER_VERSION = resolveServerVersion();
|
|
1207
|
+
function resolveServerVersion() {
|
|
1208
|
+
{
|
|
1209
|
+
return "0.1.3";
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1168
1212
|
function toJsonInputSchema(schema) {
|
|
1169
1213
|
return {
|
|
1170
1214
|
type: "object",
|
|
@@ -1238,7 +1282,8 @@ function buildMcpServer(opts) {
|
|
|
1238
1282
|
const result = await entry.handler(parsed, {
|
|
1239
1283
|
backend: opts.backend,
|
|
1240
1284
|
broker: opts.broker,
|
|
1241
|
-
surface: "mcp"
|
|
1285
|
+
surface: "mcp",
|
|
1286
|
+
dashboardBaseUrl: opts.dashboardBaseUrl
|
|
1242
1287
|
});
|
|
1243
1288
|
return toolJsonResponse(result);
|
|
1244
1289
|
} catch (err2) {
|
|
@@ -1300,7 +1345,8 @@ async function runMcpStdioCli(opts = {}) {
|
|
|
1300
1345
|
const server = buildMcpServer({
|
|
1301
1346
|
registry,
|
|
1302
1347
|
backend,
|
|
1303
|
-
broker: config.readOnly ? void 0 : broker
|
|
1348
|
+
broker: config.readOnly ? void 0 : broker,
|
|
1349
|
+
dashboardBaseUrl: config.dashboardBaseUrl
|
|
1304
1350
|
});
|
|
1305
1351
|
const transport = new StdioServerTransport();
|
|
1306
1352
|
await server.connect(transport);
|
|
@@ -1455,7 +1501,7 @@ async function safeJson(res) {
|
|
|
1455
1501
|
}
|
|
1456
1502
|
|
|
1457
1503
|
// src/broker/protocol.ts
|
|
1458
|
-
var BROKER_PROTOCOL_VERSION = "0.
|
|
1504
|
+
var BROKER_PROTOCOL_VERSION = "0.3.0";
|
|
1459
1505
|
var HASH_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
1460
1506
|
var JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
1461
1507
|
function isHashHex(value) {
|
|
@@ -1541,6 +1587,21 @@ function parseBrokerRequest(line) {
|
|
|
1541
1587
|
function serializeResponse(res) {
|
|
1542
1588
|
return JSON.stringify(res) + "\n";
|
|
1543
1589
|
}
|
|
1590
|
+
var MissingSessionKeyError = class extends Error {
|
|
1591
|
+
constructor() {
|
|
1592
|
+
super(
|
|
1593
|
+
"session_key_unavailable: daemon booted in read-only posture (no MUHAVEN_BROKER_SESSION_KEY at env-load time). Mint a session key via the dashboard /agent/policy/transition flow, set MUHAVEN_BROKER_SESSION_KEY, and restart the daemon. (Note: `muhaven-broker login` mints a JWT, NOT a session key \u2014 do not loop on that command for this error.)"
|
|
1594
|
+
);
|
|
1595
|
+
this.name = "MissingSessionKeyError";
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1599
|
+
var NullSigner = class {
|
|
1600
|
+
address = ZERO_ADDRESS;
|
|
1601
|
+
async signHash(_hash) {
|
|
1602
|
+
throw new MissingSessionKeyError();
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1544
1605
|
var ViemSigner = class {
|
|
1545
1606
|
account;
|
|
1546
1607
|
constructor(privateKey) {
|
|
@@ -1712,7 +1773,7 @@ async function openKeystore(options = {}) {
|
|
|
1712
1773
|
// src/broker/daemon.ts
|
|
1713
1774
|
var noopLogger = (_e) => {
|
|
1714
1775
|
};
|
|
1715
|
-
async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3)) {
|
|
1776
|
+
async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3), options = {}) {
|
|
1716
1777
|
switch (req.type) {
|
|
1717
1778
|
case "hello": {
|
|
1718
1779
|
let hasJwt = false;
|
|
@@ -1722,16 +1783,26 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
|
|
|
1722
1783
|
} catch {
|
|
1723
1784
|
hasJwt = false;
|
|
1724
1785
|
}
|
|
1786
|
+
const hasSessionKey = options.hasSessionKey ?? true;
|
|
1725
1787
|
return {
|
|
1726
1788
|
type: "hello",
|
|
1727
1789
|
version: BROKER_PROTOCOL_VERSION,
|
|
1728
1790
|
sessionKeyAddress: signer.address,
|
|
1729
|
-
hasJwt
|
|
1791
|
+
hasJwt,
|
|
1792
|
+
hasSessionKey,
|
|
1793
|
+
...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
|
|
1730
1794
|
};
|
|
1731
1795
|
}
|
|
1732
1796
|
case "sign_hash": {
|
|
1733
|
-
|
|
1734
|
-
|
|
1797
|
+
try {
|
|
1798
|
+
const signature = await signer.signHash(req.hash);
|
|
1799
|
+
return { type: "sign_hash", signature, signerAddress: signer.address };
|
|
1800
|
+
} catch (err2) {
|
|
1801
|
+
if (err2 instanceof MissingSessionKeyError) {
|
|
1802
|
+
return errorResponse("session_key_unavailable", err2.message);
|
|
1803
|
+
}
|
|
1804
|
+
throw err2;
|
|
1805
|
+
}
|
|
1735
1806
|
}
|
|
1736
1807
|
case "store_jwt": {
|
|
1737
1808
|
try {
|
|
@@ -1803,9 +1874,24 @@ var BrokerDaemon = class {
|
|
|
1803
1874
|
log;
|
|
1804
1875
|
config;
|
|
1805
1876
|
keystore;
|
|
1877
|
+
/**
|
|
1878
|
+
* Whether a session-key private half is actually loaded. `false` =
|
|
1879
|
+
* daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
|
|
1880
|
+
* at env-load time) and uses a `NullSigner` whose `signHash` throws.
|
|
1881
|
+
*/
|
|
1882
|
+
hasSessionKey;
|
|
1806
1883
|
constructor(options) {
|
|
1807
1884
|
this.config = options.config;
|
|
1808
|
-
|
|
1885
|
+
if (options.signer) {
|
|
1886
|
+
this.signer = options.signer;
|
|
1887
|
+
this.hasSessionKey = true;
|
|
1888
|
+
} else if (options.config.sessionKeyHex) {
|
|
1889
|
+
this.signer = new ViemSigner(options.config.sessionKeyHex);
|
|
1890
|
+
this.hasSessionKey = true;
|
|
1891
|
+
} else {
|
|
1892
|
+
this.signer = new NullSigner();
|
|
1893
|
+
this.hasSessionKey = false;
|
|
1894
|
+
}
|
|
1809
1895
|
this.keystore = options.keystore ?? null;
|
|
1810
1896
|
this.log = options.logger ?? noopLogger;
|
|
1811
1897
|
this.server = createServer((socket) => this.onConnection(socket));
|
|
@@ -1843,6 +1929,7 @@ var BrokerDaemon = class {
|
|
|
1843
1929
|
meta: {
|
|
1844
1930
|
endpoint: this.config.endpoint,
|
|
1845
1931
|
signer: this.signer.address,
|
|
1932
|
+
hasSessionKey: this.hasSessionKey,
|
|
1846
1933
|
keystore: this.keystore.backend,
|
|
1847
1934
|
version: BROKER_PROTOCOL_VERSION
|
|
1848
1935
|
}
|
|
@@ -1924,7 +2011,19 @@ var BrokerDaemon = class {
|
|
|
1924
2011
|
return;
|
|
1925
2012
|
}
|
|
1926
2013
|
try {
|
|
1927
|
-
const res = await handleBrokerRequest(
|
|
2014
|
+
const res = await handleBrokerRequest(
|
|
2015
|
+
parsed,
|
|
2016
|
+
this.signer,
|
|
2017
|
+
this.keystore,
|
|
2018
|
+
void 0,
|
|
2019
|
+
{
|
|
2020
|
+
hasSessionKey: this.hasSessionKey,
|
|
2021
|
+
effectiveConfig: {
|
|
2022
|
+
backendBaseUrl: this.config.backendBaseUrl,
|
|
2023
|
+
dashboardBaseUrl: this.config.dashboardBaseUrl
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
);
|
|
1928
2027
|
socket.end(serializeResponse(res));
|
|
1929
2028
|
} catch (err2) {
|
|
1930
2029
|
this.log({
|
|
@@ -1939,4 +2038,4 @@ var BrokerDaemon = class {
|
|
|
1939
2038
|
}
|
|
1940
2039
|
};
|
|
1941
2040
|
|
|
1942
|
-
export { BROKER_PROTOCOL_VERSION, BackendClient, BackendError, BrokerClient, BrokerClientError, BrokerDaemon, DeviceFlowAbortedError, DeviceFlowClient, JwtSource, KeystoreError, NoJwtAvailableError, TOOL_DESCRIPTORS, buildMcpServer, buildToolHashTable, defaultBrokerEndpoint, fullToolRegistry, handleBrokerRequest, hashToolDescriptor, loadBrokerConfig, loadMcpConfig, openKeystore, parseBrokerRequest, registryForReadOnly, runMcpStdioCli, selectRegistry, serializeResponse, verifyDescriptorAgainstPin };
|
|
2041
|
+
export { BROKER_PROTOCOL_VERSION, BackendClient, BackendError, BrokerClient, BrokerClientError, BrokerDaemon, DeviceFlowAbortedError, DeviceFlowClient, JwtSource, KeystoreError, MissingSessionKeyError, NoJwtAvailableError, NullSigner, SERVER_VERSION, TOOL_DESCRIPTORS, ViemSigner, ZERO_ADDRESS, buildMcpServer, buildToolHashTable, defaultBrokerEndpoint, fullToolRegistry, handleBrokerRequest, hashToolDescriptor, loadBrokerConfig, loadMcpConfig, openKeystore, parseBrokerRequest, registryForReadOnly, runMcpStdioCli, selectRegistry, serializeResponse, verifyDescriptorAgainstPin };
|
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.1.
|
|
6
|
+
"version": "0.1.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 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.1.
|
|
3
|
+
"version": "0.1.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": {
|