@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/broker.js
CHANGED
|
@@ -71,23 +71,26 @@ function loadMcpConfig(env = process.env) {
|
|
|
71
71
|
}
|
|
72
72
|
var PRIVKEY_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
73
73
|
function loadBrokerConfig(env = process.env) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
|
|
74
|
+
const sessionKeyHexRaw = env.MUHAVEN_BROKER_SESSION_KEY;
|
|
75
|
+
let sessionKeyHex;
|
|
76
|
+
if (sessionKeyHexRaw && sessionKeyHexRaw.length > 0) {
|
|
77
|
+
if (!PRIVKEY_HEX_RE.test(sessionKeyHexRaw)) {
|
|
78
|
+
throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
|
|
79
|
+
}
|
|
80
|
+
sessionKeyHex = sessionKeyHexRaw;
|
|
82
81
|
}
|
|
83
82
|
const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
|
|
84
83
|
const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
|
|
85
84
|
const requestTimeoutMs = readEnvInt("MUHAVEN_BROKER_TIMEOUT_MS", DEFAULT_BROKER_TIMEOUT_MS, env);
|
|
85
|
+
const backendBaseUrl = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
|
|
86
|
+
const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
|
|
86
87
|
return {
|
|
87
88
|
endpoint,
|
|
88
89
|
sessionKeyHex,
|
|
89
90
|
maxRequestBytes,
|
|
90
|
-
requestTimeoutMs
|
|
91
|
+
requestTimeoutMs,
|
|
92
|
+
backendBaseUrl,
|
|
93
|
+
dashboardBaseUrl
|
|
91
94
|
};
|
|
92
95
|
}
|
|
93
96
|
var BrokerClientError = class extends Error {
|
|
@@ -520,7 +523,7 @@ async function openKeystore(options = {}) {
|
|
|
520
523
|
}
|
|
521
524
|
|
|
522
525
|
// src/broker/protocol.ts
|
|
523
|
-
var BROKER_PROTOCOL_VERSION = "0.
|
|
526
|
+
var BROKER_PROTOCOL_VERSION = "0.3.0";
|
|
524
527
|
var HASH_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
525
528
|
var JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
526
529
|
function isHashHex(value) {
|
|
@@ -606,6 +609,21 @@ function parseBrokerRequest(line) {
|
|
|
606
609
|
function serializeResponse(res) {
|
|
607
610
|
return JSON.stringify(res) + "\n";
|
|
608
611
|
}
|
|
612
|
+
var MissingSessionKeyError = class extends Error {
|
|
613
|
+
constructor() {
|
|
614
|
+
super(
|
|
615
|
+
"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.)"
|
|
616
|
+
);
|
|
617
|
+
this.name = "MissingSessionKeyError";
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
621
|
+
var NullSigner = class {
|
|
622
|
+
address = ZERO_ADDRESS;
|
|
623
|
+
async signHash(_hash) {
|
|
624
|
+
throw new MissingSessionKeyError();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
609
627
|
var ViemSigner = class {
|
|
610
628
|
account;
|
|
611
629
|
constructor(privateKey) {
|
|
@@ -622,7 +640,7 @@ var ViemSigner = class {
|
|
|
622
640
|
// src/broker/daemon.ts
|
|
623
641
|
var noopLogger = (_e) => {
|
|
624
642
|
};
|
|
625
|
-
async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3)) {
|
|
643
|
+
async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3), options = {}) {
|
|
626
644
|
switch (req.type) {
|
|
627
645
|
case "hello": {
|
|
628
646
|
let hasJwt = false;
|
|
@@ -632,16 +650,26 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
|
|
|
632
650
|
} catch {
|
|
633
651
|
hasJwt = false;
|
|
634
652
|
}
|
|
653
|
+
const hasSessionKey = options.hasSessionKey ?? true;
|
|
635
654
|
return {
|
|
636
655
|
type: "hello",
|
|
637
656
|
version: BROKER_PROTOCOL_VERSION,
|
|
638
657
|
sessionKeyAddress: signer.address,
|
|
639
|
-
hasJwt
|
|
658
|
+
hasJwt,
|
|
659
|
+
hasSessionKey,
|
|
660
|
+
...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
|
|
640
661
|
};
|
|
641
662
|
}
|
|
642
663
|
case "sign_hash": {
|
|
643
|
-
|
|
644
|
-
|
|
664
|
+
try {
|
|
665
|
+
const signature = await signer.signHash(req.hash);
|
|
666
|
+
return { type: "sign_hash", signature, signerAddress: signer.address };
|
|
667
|
+
} catch (err) {
|
|
668
|
+
if (err instanceof MissingSessionKeyError) {
|
|
669
|
+
return errorResponse("session_key_unavailable", err.message);
|
|
670
|
+
}
|
|
671
|
+
throw err;
|
|
672
|
+
}
|
|
645
673
|
}
|
|
646
674
|
case "store_jwt": {
|
|
647
675
|
try {
|
|
@@ -713,9 +741,24 @@ var BrokerDaemon = class {
|
|
|
713
741
|
log;
|
|
714
742
|
config;
|
|
715
743
|
keystore;
|
|
744
|
+
/**
|
|
745
|
+
* Whether a session-key private half is actually loaded. `false` =
|
|
746
|
+
* daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
|
|
747
|
+
* at env-load time) and uses a `NullSigner` whose `signHash` throws.
|
|
748
|
+
*/
|
|
749
|
+
hasSessionKey;
|
|
716
750
|
constructor(options) {
|
|
717
751
|
this.config = options.config;
|
|
718
|
-
|
|
752
|
+
if (options.signer) {
|
|
753
|
+
this.signer = options.signer;
|
|
754
|
+
this.hasSessionKey = true;
|
|
755
|
+
} else if (options.config.sessionKeyHex) {
|
|
756
|
+
this.signer = new ViemSigner(options.config.sessionKeyHex);
|
|
757
|
+
this.hasSessionKey = true;
|
|
758
|
+
} else {
|
|
759
|
+
this.signer = new NullSigner();
|
|
760
|
+
this.hasSessionKey = false;
|
|
761
|
+
}
|
|
719
762
|
this.keystore = options.keystore ?? null;
|
|
720
763
|
this.log = options.logger ?? noopLogger;
|
|
721
764
|
this.server = createServer((socket) => this.onConnection(socket));
|
|
@@ -753,6 +796,7 @@ var BrokerDaemon = class {
|
|
|
753
796
|
meta: {
|
|
754
797
|
endpoint: this.config.endpoint,
|
|
755
798
|
signer: this.signer.address,
|
|
799
|
+
hasSessionKey: this.hasSessionKey,
|
|
756
800
|
keystore: this.keystore.backend,
|
|
757
801
|
version: BROKER_PROTOCOL_VERSION
|
|
758
802
|
}
|
|
@@ -834,7 +878,19 @@ var BrokerDaemon = class {
|
|
|
834
878
|
return;
|
|
835
879
|
}
|
|
836
880
|
try {
|
|
837
|
-
const res = await handleBrokerRequest(
|
|
881
|
+
const res = await handleBrokerRequest(
|
|
882
|
+
parsed,
|
|
883
|
+
this.signer,
|
|
884
|
+
this.keystore,
|
|
885
|
+
void 0,
|
|
886
|
+
{
|
|
887
|
+
hasSessionKey: this.hasSessionKey,
|
|
888
|
+
effectiveConfig: {
|
|
889
|
+
backendBaseUrl: this.config.backendBaseUrl,
|
|
890
|
+
dashboardBaseUrl: this.config.dashboardBaseUrl
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
);
|
|
838
894
|
socket.end(serializeResponse(res));
|
|
839
895
|
} catch (err) {
|
|
840
896
|
this.log({
|
|
@@ -850,6 +906,15 @@ var BrokerDaemon = class {
|
|
|
850
906
|
};
|
|
851
907
|
async function runBrokerDaemonCli() {
|
|
852
908
|
const config = loadBrokerConfig();
|
|
909
|
+
if (!config.sessionKeyHex) {
|
|
910
|
+
process.stderr.write(
|
|
911
|
+
JSON.stringify({
|
|
912
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
913
|
+
level: "info",
|
|
914
|
+
msg: "broker booting in read-only posture (no MUHAVEN_BROKER_SESSION_KEY)"
|
|
915
|
+
}) + "\n"
|
|
916
|
+
);
|
|
917
|
+
}
|
|
853
918
|
const daemon = new BrokerDaemon({
|
|
854
919
|
config,
|
|
855
920
|
logger: (e) => {
|
|
@@ -902,9 +967,11 @@ function parseLoginFlags(argv) {
|
|
|
902
967
|
let brokerEndpoint;
|
|
903
968
|
let backendBaseUrl;
|
|
904
969
|
let dashboardBaseUrl;
|
|
970
|
+
let fromDaemon = false;
|
|
905
971
|
for (let i = 0; i < argv.length; i++) {
|
|
906
972
|
const a = argv[i];
|
|
907
973
|
if (a === "--no-launch-browser") noLaunchBrowser = true;
|
|
974
|
+
else if (a === "--from-daemon") fromDaemon = true;
|
|
908
975
|
else if (a === "--broker-endpoint" && i + 1 < argv.length) {
|
|
909
976
|
brokerEndpoint = argv[++i];
|
|
910
977
|
} else if (a === "--backend-base-url" && i + 1 < argv.length) {
|
|
@@ -915,7 +982,12 @@ function parseLoginFlags(argv) {
|
|
|
915
982
|
throw new Error(`unknown flag: ${a}`);
|
|
916
983
|
}
|
|
917
984
|
}
|
|
918
|
-
|
|
985
|
+
if (fromDaemon && (backendBaseUrl || dashboardBaseUrl)) {
|
|
986
|
+
throw new Error(
|
|
987
|
+
"--from-daemon is mutually exclusive with --backend-base-url / --dashboard-base-url"
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
return { noLaunchBrowser, brokerEndpoint, backendBaseUrl, dashboardBaseUrl, fromDaemon };
|
|
919
991
|
}
|
|
920
992
|
async function tryLaunchBrowser(url) {
|
|
921
993
|
return new Promise((resolve) => {
|
|
@@ -929,7 +1001,9 @@ async function runLogin(argv) {
|
|
|
929
1001
|
flags = parseLoginFlags(argv);
|
|
930
1002
|
} catch (err) {
|
|
931
1003
|
printErr(`error: ${err.message}`);
|
|
932
|
-
printErr(
|
|
1004
|
+
printErr(
|
|
1005
|
+
"usage: muhaven-broker login [--no-launch-browser] [--broker-endpoint PATH] [--from-daemon | (--backend-base-url URL --dashboard-base-url URL)]"
|
|
1006
|
+
);
|
|
933
1007
|
return 2;
|
|
934
1008
|
}
|
|
935
1009
|
const env = process.env;
|
|
@@ -943,8 +1017,9 @@ async function runLogin(argv) {
|
|
|
943
1017
|
endpoint: config.brokerEndpoint,
|
|
944
1018
|
timeoutMs: config.brokerTimeoutMs
|
|
945
1019
|
});
|
|
1020
|
+
let helloResult;
|
|
946
1021
|
try {
|
|
947
|
-
await broker.hello();
|
|
1022
|
+
helloResult = await broker.hello();
|
|
948
1023
|
} catch (err) {
|
|
949
1024
|
printErr(
|
|
950
1025
|
`cannot reach muhaven-broker daemon at ${config.brokerEndpoint}: ${err.message}`
|
|
@@ -952,9 +1027,42 @@ async function runLogin(argv) {
|
|
|
952
1027
|
printErr("hint: start the daemon first (`muhaven-broker` with no subcommand).");
|
|
953
1028
|
return 1;
|
|
954
1029
|
}
|
|
1030
|
+
let backendBaseUrl = config.backendBaseUrl;
|
|
1031
|
+
let dashboardBaseUrl = config.dashboardBaseUrl;
|
|
1032
|
+
if (flags.fromDaemon) {
|
|
1033
|
+
if (!helloResult.effectiveConfig) {
|
|
1034
|
+
printErr(
|
|
1035
|
+
"--from-daemon requested but broker did not return effectiveConfig (daemon is older than protocol 0.3.0). Upgrade the daemon (`@muhaven/mcp@0.1.3+`) or drop the flag."
|
|
1036
|
+
);
|
|
1037
|
+
return 1;
|
|
1038
|
+
}
|
|
1039
|
+
const daemonBackend = helloResult.effectiveConfig.backendBaseUrl;
|
|
1040
|
+
const daemonDashboard = helloResult.effectiveConfig.dashboardBaseUrl;
|
|
1041
|
+
if (!daemonBackend || !daemonDashboard) {
|
|
1042
|
+
printErr(
|
|
1043
|
+
"--from-daemon: daemon returned an empty backend/dashboard URL \u2014 refusing to proceed."
|
|
1044
|
+
);
|
|
1045
|
+
return 1;
|
|
1046
|
+
}
|
|
1047
|
+
if (daemonBackend !== config.backendBaseUrl) {
|
|
1048
|
+
print(
|
|
1049
|
+
`\u26A0 daemon backend (${daemonBackend}) differs from CLI env (${config.backendBaseUrl}). Using daemon's value per --from-daemon.`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
if (daemonDashboard !== config.dashboardBaseUrl) {
|
|
1053
|
+
print(
|
|
1054
|
+
`\u26A0 daemon dashboard (${daemonDashboard}) differs from CLI env (${config.dashboardBaseUrl}). Using daemon's value per --from-daemon.`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
backendBaseUrl = daemonBackend;
|
|
1058
|
+
dashboardBaseUrl = daemonDashboard;
|
|
1059
|
+
print(`Using daemon's effective config:`);
|
|
1060
|
+
print(` backend: ${backendBaseUrl}`);
|
|
1061
|
+
print(` dashboard: ${dashboardBaseUrl}`);
|
|
1062
|
+
}
|
|
955
1063
|
const flow = new DeviceFlowClient({
|
|
956
|
-
backendBaseUrl
|
|
957
|
-
dashboardBaseUrl
|
|
1064
|
+
backendBaseUrl,
|
|
1065
|
+
dashboardBaseUrl,
|
|
958
1066
|
requesterMetadata: {
|
|
959
1067
|
processName: detectMcpHost(),
|
|
960
1068
|
hostname: hostname(),
|
|
@@ -1069,7 +1177,13 @@ async function runDoctor() {
|
|
|
1069
1177
|
});
|
|
1070
1178
|
try {
|
|
1071
1179
|
const h = await broker.hello();
|
|
1072
|
-
|
|
1180
|
+
const hasKey = h.hasSessionKey ?? true;
|
|
1181
|
+
const keyTag = hasKey ? `signer ${h.sessionKeyAddress}` : "NO SESSION KEY (read-only posture)";
|
|
1182
|
+
print(`Broker daemon : reachable (proto v${h.version}, ${keyTag}, hasJwt=${h.hasJwt})`);
|
|
1183
|
+
if (h.effectiveConfig) {
|
|
1184
|
+
print(`Daemon backend URL: ${h.effectiveConfig.backendBaseUrl}`);
|
|
1185
|
+
print(`Daemon dashboard : ${h.effectiveConfig.dashboardBaseUrl}`);
|
|
1186
|
+
}
|
|
1073
1187
|
return 0;
|
|
1074
1188
|
} catch (err) {
|
|
1075
1189
|
print(`Broker daemon : NOT reachable (${err.message})`);
|
|
@@ -1082,6 +1196,7 @@ function printUsage() {
|
|
|
1082
1196
|
print("");
|
|
1083
1197
|
print(" (no subcommand) Run the daemon (production mode)");
|
|
1084
1198
|
print(" login Acquire a JWT via the device-code flow + store in keystore");
|
|
1199
|
+
print(" [--from-daemon] resolves backend/dashboard URLs from the running daemon");
|
|
1085
1200
|
print(" logout Clear the JWT from the keystore");
|
|
1086
1201
|
print(" doctor Print environment + keystore + reachability report");
|
|
1087
1202
|
print(" -h, --help Show this help");
|
|
@@ -1109,4 +1224,4 @@ async function runCli(argv) {
|
|
|
1109
1224
|
}
|
|
1110
1225
|
}
|
|
1111
1226
|
|
|
1112
|
-
export { runCli, runDoctor, runLogin, runLogout };
|
|
1227
|
+
export { parseLoginFlags, runCli, runDoctor, runLogin, runLogout };
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var promises = require('fs/promises');
|
|
4
|
+
require('fs');
|
|
4
5
|
var path = require('path');
|
|
5
6
|
var url = require('url');
|
|
6
7
|
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
@@ -81,23 +82,26 @@ function loadMcpConfig(env = process.env) {
|
|
|
81
82
|
}
|
|
82
83
|
var PRIVKEY_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
83
84
|
function loadBrokerConfig(env = process.env) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
|
|
85
|
+
const sessionKeyHexRaw = env.MUHAVEN_BROKER_SESSION_KEY;
|
|
86
|
+
let sessionKeyHex;
|
|
87
|
+
if (sessionKeyHexRaw && sessionKeyHexRaw.length > 0) {
|
|
88
|
+
if (!PRIVKEY_HEX_RE.test(sessionKeyHexRaw)) {
|
|
89
|
+
throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
|
|
90
|
+
}
|
|
91
|
+
sessionKeyHex = sessionKeyHexRaw;
|
|
92
92
|
}
|
|
93
93
|
const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
|
|
94
94
|
const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
|
|
95
95
|
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);
|
|
96
98
|
return {
|
|
97
99
|
endpoint,
|
|
98
100
|
sessionKeyHex,
|
|
99
101
|
maxRequestBytes,
|
|
100
|
-
requestTimeoutMs
|
|
102
|
+
requestTimeoutMs,
|
|
103
|
+
backendBaseUrl,
|
|
104
|
+
dashboardBaseUrl
|
|
101
105
|
};
|
|
102
106
|
}
|
|
103
107
|
var BrokerClientError = class extends Error {
|
|
@@ -736,6 +740,15 @@ function authRequiredPayload() {
|
|
|
736
740
|
loginCommand: "muhaven-broker login"
|
|
737
741
|
};
|
|
738
742
|
}
|
|
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
|
+
}
|
|
739
752
|
|
|
740
753
|
// src/tools/handlers.ts
|
|
741
754
|
function ok(data) {
|
|
@@ -825,6 +838,7 @@ function sortKeys(value) {
|
|
|
825
838
|
}
|
|
826
839
|
return value;
|
|
827
840
|
}
|
|
841
|
+
var cachedHasSessionKeyProbe = null;
|
|
828
842
|
async function signEnvelope(intent, toolName, summary, deps) {
|
|
829
843
|
const intentHash = computeIntentHash(intent);
|
|
830
844
|
if (!deps.broker) {
|
|
@@ -833,8 +847,29 @@ async function signEnvelope(intent, toolName, summary, deps) {
|
|
|
833
847
|
"position tools require a running muhaven-broker daemon \u2014 see README \xA7Broker setup"
|
|
834
848
|
);
|
|
835
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;
|
|
836
863
|
try {
|
|
837
|
-
|
|
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 });
|
|
838
873
|
return ok({
|
|
839
874
|
intentHash,
|
|
840
875
|
unsignedUserOp: {
|
|
@@ -846,6 +881,10 @@ async function signEnvelope(intent, toolName, summary, deps) {
|
|
|
846
881
|
signerAddress: sig.signerAddress
|
|
847
882
|
});
|
|
848
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
|
+
}
|
|
849
888
|
return mapBrokerError(e);
|
|
850
889
|
}
|
|
851
890
|
}
|
|
@@ -1168,7 +1207,12 @@ function selectRegistry(readOnly) {
|
|
|
1168
1207
|
|
|
1169
1208
|
// src/server.ts
|
|
1170
1209
|
var SERVER_NAME = "@muhaven/mcp";
|
|
1171
|
-
var SERVER_VERSION =
|
|
1210
|
+
var SERVER_VERSION = resolveServerVersion();
|
|
1211
|
+
function resolveServerVersion() {
|
|
1212
|
+
{
|
|
1213
|
+
return "0.1.3";
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1172
1216
|
function toJsonInputSchema(schema) {
|
|
1173
1217
|
return {
|
|
1174
1218
|
type: "object",
|
|
@@ -1242,7 +1286,8 @@ function buildMcpServer(opts) {
|
|
|
1242
1286
|
const result = await entry.handler(parsed, {
|
|
1243
1287
|
backend: opts.backend,
|
|
1244
1288
|
broker: opts.broker,
|
|
1245
|
-
surface: "mcp"
|
|
1289
|
+
surface: "mcp",
|
|
1290
|
+
dashboardBaseUrl: opts.dashboardBaseUrl
|
|
1246
1291
|
});
|
|
1247
1292
|
return toolJsonResponse(result);
|
|
1248
1293
|
} catch (err2) {
|
|
@@ -1304,7 +1349,8 @@ async function runMcpStdioCli(opts = {}) {
|
|
|
1304
1349
|
const server = buildMcpServer({
|
|
1305
1350
|
registry,
|
|
1306
1351
|
backend,
|
|
1307
|
-
broker: config.readOnly ? void 0 : broker
|
|
1352
|
+
broker: config.readOnly ? void 0 : broker,
|
|
1353
|
+
dashboardBaseUrl: config.dashboardBaseUrl
|
|
1308
1354
|
});
|
|
1309
1355
|
const transport = new stdio_js.StdioServerTransport();
|
|
1310
1356
|
await server.connect(transport);
|
|
@@ -1459,7 +1505,7 @@ async function safeJson(res) {
|
|
|
1459
1505
|
}
|
|
1460
1506
|
|
|
1461
1507
|
// src/broker/protocol.ts
|
|
1462
|
-
var BROKER_PROTOCOL_VERSION = "0.
|
|
1508
|
+
var BROKER_PROTOCOL_VERSION = "0.3.0";
|
|
1463
1509
|
var HASH_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
1464
1510
|
var JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
1465
1511
|
function isHashHex(value) {
|
|
@@ -1545,6 +1591,21 @@ function parseBrokerRequest(line) {
|
|
|
1545
1591
|
function serializeResponse(res) {
|
|
1546
1592
|
return JSON.stringify(res) + "\n";
|
|
1547
1593
|
}
|
|
1594
|
+
var MissingSessionKeyError = class extends Error {
|
|
1595
|
+
constructor() {
|
|
1596
|
+
super(
|
|
1597
|
+
"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.)"
|
|
1598
|
+
);
|
|
1599
|
+
this.name = "MissingSessionKeyError";
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1603
|
+
var NullSigner = class {
|
|
1604
|
+
address = ZERO_ADDRESS;
|
|
1605
|
+
async signHash(_hash) {
|
|
1606
|
+
throw new MissingSessionKeyError();
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1548
1609
|
var ViemSigner = class {
|
|
1549
1610
|
account;
|
|
1550
1611
|
constructor(privateKey) {
|
|
@@ -1716,7 +1777,7 @@ async function openKeystore(options = {}) {
|
|
|
1716
1777
|
// src/broker/daemon.ts
|
|
1717
1778
|
var noopLogger = (_e) => {
|
|
1718
1779
|
};
|
|
1719
|
-
async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3)) {
|
|
1780
|
+
async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3), options = {}) {
|
|
1720
1781
|
switch (req.type) {
|
|
1721
1782
|
case "hello": {
|
|
1722
1783
|
let hasJwt = false;
|
|
@@ -1726,16 +1787,26 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
|
|
|
1726
1787
|
} catch {
|
|
1727
1788
|
hasJwt = false;
|
|
1728
1789
|
}
|
|
1790
|
+
const hasSessionKey = options.hasSessionKey ?? true;
|
|
1729
1791
|
return {
|
|
1730
1792
|
type: "hello",
|
|
1731
1793
|
version: BROKER_PROTOCOL_VERSION,
|
|
1732
1794
|
sessionKeyAddress: signer.address,
|
|
1733
|
-
hasJwt
|
|
1795
|
+
hasJwt,
|
|
1796
|
+
hasSessionKey,
|
|
1797
|
+
...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
|
|
1734
1798
|
};
|
|
1735
1799
|
}
|
|
1736
1800
|
case "sign_hash": {
|
|
1737
|
-
|
|
1738
|
-
|
|
1801
|
+
try {
|
|
1802
|
+
const signature = await signer.signHash(req.hash);
|
|
1803
|
+
return { type: "sign_hash", signature, signerAddress: signer.address };
|
|
1804
|
+
} catch (err2) {
|
|
1805
|
+
if (err2 instanceof MissingSessionKeyError) {
|
|
1806
|
+
return errorResponse("session_key_unavailable", err2.message);
|
|
1807
|
+
}
|
|
1808
|
+
throw err2;
|
|
1809
|
+
}
|
|
1739
1810
|
}
|
|
1740
1811
|
case "store_jwt": {
|
|
1741
1812
|
try {
|
|
@@ -1807,9 +1878,24 @@ var BrokerDaemon = class {
|
|
|
1807
1878
|
log;
|
|
1808
1879
|
config;
|
|
1809
1880
|
keystore;
|
|
1881
|
+
/**
|
|
1882
|
+
* Whether a session-key private half is actually loaded. `false` =
|
|
1883
|
+
* daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
|
|
1884
|
+
* at env-load time) and uses a `NullSigner` whose `signHash` throws.
|
|
1885
|
+
*/
|
|
1886
|
+
hasSessionKey;
|
|
1810
1887
|
constructor(options) {
|
|
1811
1888
|
this.config = options.config;
|
|
1812
|
-
|
|
1889
|
+
if (options.signer) {
|
|
1890
|
+
this.signer = options.signer;
|
|
1891
|
+
this.hasSessionKey = true;
|
|
1892
|
+
} else if (options.config.sessionKeyHex) {
|
|
1893
|
+
this.signer = new ViemSigner(options.config.sessionKeyHex);
|
|
1894
|
+
this.hasSessionKey = true;
|
|
1895
|
+
} else {
|
|
1896
|
+
this.signer = new NullSigner();
|
|
1897
|
+
this.hasSessionKey = false;
|
|
1898
|
+
}
|
|
1813
1899
|
this.keystore = options.keystore ?? null;
|
|
1814
1900
|
this.log = options.logger ?? noopLogger;
|
|
1815
1901
|
this.server = net.createServer((socket) => this.onConnection(socket));
|
|
@@ -1847,6 +1933,7 @@ var BrokerDaemon = class {
|
|
|
1847
1933
|
meta: {
|
|
1848
1934
|
endpoint: this.config.endpoint,
|
|
1849
1935
|
signer: this.signer.address,
|
|
1936
|
+
hasSessionKey: this.hasSessionKey,
|
|
1850
1937
|
keystore: this.keystore.backend,
|
|
1851
1938
|
version: BROKER_PROTOCOL_VERSION
|
|
1852
1939
|
}
|
|
@@ -1928,7 +2015,19 @@ var BrokerDaemon = class {
|
|
|
1928
2015
|
return;
|
|
1929
2016
|
}
|
|
1930
2017
|
try {
|
|
1931
|
-
const res = await handleBrokerRequest(
|
|
2018
|
+
const res = await handleBrokerRequest(
|
|
2019
|
+
parsed,
|
|
2020
|
+
this.signer,
|
|
2021
|
+
this.keystore,
|
|
2022
|
+
void 0,
|
|
2023
|
+
{
|
|
2024
|
+
hasSessionKey: this.hasSessionKey,
|
|
2025
|
+
effectiveConfig: {
|
|
2026
|
+
backendBaseUrl: this.config.backendBaseUrl,
|
|
2027
|
+
dashboardBaseUrl: this.config.dashboardBaseUrl
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
);
|
|
1932
2031
|
socket.end(serializeResponse(res));
|
|
1933
2032
|
} catch (err2) {
|
|
1934
2033
|
this.log({
|
|
@@ -1953,8 +2052,13 @@ exports.DeviceFlowAbortedError = DeviceFlowAbortedError;
|
|
|
1953
2052
|
exports.DeviceFlowClient = DeviceFlowClient;
|
|
1954
2053
|
exports.JwtSource = JwtSource;
|
|
1955
2054
|
exports.KeystoreError = KeystoreError;
|
|
2055
|
+
exports.MissingSessionKeyError = MissingSessionKeyError;
|
|
1956
2056
|
exports.NoJwtAvailableError = NoJwtAvailableError;
|
|
2057
|
+
exports.NullSigner = NullSigner;
|
|
2058
|
+
exports.SERVER_VERSION = SERVER_VERSION;
|
|
1957
2059
|
exports.TOOL_DESCRIPTORS = TOOL_DESCRIPTORS;
|
|
2060
|
+
exports.ViemSigner = ViemSigner;
|
|
2061
|
+
exports.ZERO_ADDRESS = ZERO_ADDRESS;
|
|
1958
2062
|
exports.buildMcpServer = buildMcpServer;
|
|
1959
2063
|
exports.buildToolHashTable = buildToolHashTable;
|
|
1960
2064
|
exports.defaultBrokerEndpoint = defaultBrokerEndpoint;
|