midsummer-sol 0.2.1 → 0.3.0
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/index.js +200 -6
- package/package.json +1 -1
- package/sol-mcp.js +13029 -98
- package/sol-secret-mcp.js +177 -15
- package/sol.js +6259 -284
package/sol-secret-mcp.js
CHANGED
|
@@ -246,6 +246,141 @@ var init_sign = __esm(() => {
|
|
|
246
246
|
ED25519_PKCS8_PREFIX2 = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
+
// src/bin/mcp-http.ts
|
|
250
|
+
var exports_mcp_http = {};
|
|
251
|
+
__export(exports_mcp_http, {
|
|
252
|
+
serveMcpHttp: () => serveMcpHttp,
|
|
253
|
+
resolveMcpToken: () => resolveMcpToken,
|
|
254
|
+
parseStandaloneHttp: () => parseStandaloneHttp
|
|
255
|
+
});
|
|
256
|
+
import { randomUUID, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
257
|
+
import { createServer } from "node:http";
|
|
258
|
+
function tokenMatches(provided, expected) {
|
|
259
|
+
const a = Buffer.from(provided);
|
|
260
|
+
const b = Buffer.from(expected);
|
|
261
|
+
if (a.length !== b.length)
|
|
262
|
+
return false;
|
|
263
|
+
return timingSafeEqual2(a, b);
|
|
264
|
+
}
|
|
265
|
+
function bearer(req) {
|
|
266
|
+
const h = req.headers["authorization"];
|
|
267
|
+
if (typeof h !== "string")
|
|
268
|
+
return;
|
|
269
|
+
const m = /^Bearer\s+(.+)$/i.exec(h.trim());
|
|
270
|
+
return m ? m[1].trim() : undefined;
|
|
271
|
+
}
|
|
272
|
+
function writeJson(res, status, body) {
|
|
273
|
+
const s = JSON.stringify(body);
|
|
274
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
275
|
+
res.end(s);
|
|
276
|
+
}
|
|
277
|
+
async function readBody(req) {
|
|
278
|
+
const chunks = [];
|
|
279
|
+
for await (const c of req)
|
|
280
|
+
chunks.push(c);
|
|
281
|
+
if (chunks.length === 0)
|
|
282
|
+
return;
|
|
283
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
284
|
+
if (!raw.trim())
|
|
285
|
+
return;
|
|
286
|
+
return JSON.parse(raw);
|
|
287
|
+
}
|
|
288
|
+
async function serveMcpHttp(buildServer, opts) {
|
|
289
|
+
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
290
|
+
const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
|
|
291
|
+
const port = opts.port ?? 8765;
|
|
292
|
+
const host = opts.host ?? "127.0.0.1";
|
|
293
|
+
const transports = new Map;
|
|
294
|
+
const http = createServer(async (req, res) => {
|
|
295
|
+
try {
|
|
296
|
+
const tok = bearer(req);
|
|
297
|
+
if (!tok || !tokenMatches(tok, opts.token)) {
|
|
298
|
+
res.setHeader("WWW-Authenticate", "Bearer");
|
|
299
|
+
writeJson(res, 401, { jsonrpc: "2.0", error: { code: -32001, message: "unauthorized: a valid Authorization: Bearer token is required" }, id: null });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const url = (req.url ?? "").split("?")[0];
|
|
303
|
+
if (url !== MCP_PATH) {
|
|
304
|
+
writeJson(res, 404, { jsonrpc: "2.0", error: { code: -32601, message: `not found — the MCP endpoint is ${MCP_PATH}` }, id: null });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
308
|
+
const sid = typeof sessionId === "string" ? sessionId : undefined;
|
|
309
|
+
if (req.method === "POST") {
|
|
310
|
+
const body = await readBody(req);
|
|
311
|
+
let transport = sid ? transports.get(sid) : undefined;
|
|
312
|
+
if (!transport) {
|
|
313
|
+
if (sid || !isInitializeRequest(body)) {
|
|
314
|
+
writeJson(res, 400, { jsonrpc: "2.0", error: { code: -32000, message: "no valid session — send an initialize request first" }, id: null });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
transport = new StreamableHTTPServerTransport({
|
|
318
|
+
sessionIdGenerator: () => randomUUID(),
|
|
319
|
+
onsessioninitialized: (id) => void transports.set(id, transport)
|
|
320
|
+
});
|
|
321
|
+
transport.onclose = () => {
|
|
322
|
+
if (transport.sessionId)
|
|
323
|
+
transports.delete(transport.sessionId);
|
|
324
|
+
};
|
|
325
|
+
const server = await buildServer();
|
|
326
|
+
await server.connect(transport);
|
|
327
|
+
}
|
|
328
|
+
await transport.handleRequest(req, res, body);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
332
|
+
const transport = sid ? transports.get(sid) : undefined;
|
|
333
|
+
if (!transport) {
|
|
334
|
+
writeJson(res, 400, { jsonrpc: "2.0", error: { code: -32000, message: "unknown or missing Mcp-Session-Id" }, id: null });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
await transport.handleRequest(req, res);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
writeJson(res, 405, { jsonrpc: "2.0", error: { code: -32000, message: "method not allowed" }, id: null });
|
|
341
|
+
} catch (e) {
|
|
342
|
+
if (!res.headersSent) {
|
|
343
|
+
writeJson(res, 500, { jsonrpc: "2.0", error: { code: -32603, message: "internal error: " + (e instanceof Error ? e.message : String(e)) }, id: null });
|
|
344
|
+
} else {
|
|
345
|
+
try {
|
|
346
|
+
res.end();
|
|
347
|
+
} catch {}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
await new Promise((resolve, reject) => {
|
|
352
|
+
http.once("error", reject);
|
|
353
|
+
http.listen(port, host, () => {
|
|
354
|
+
http.off("error", reject);
|
|
355
|
+
process.stderr.write(`${opts.label} MCP (HTTP) listening on http://${host}:${port}${MCP_PATH} — Authorization: Bearer required
|
|
356
|
+
`);
|
|
357
|
+
resolve();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
await new Promise(() => {});
|
|
361
|
+
}
|
|
362
|
+
function resolveMcpToken(flagToken) {
|
|
363
|
+
const t = (flagToken ?? process.env.SOL_MCP_TOKEN ?? "").trim();
|
|
364
|
+
return t.length ? t : undefined;
|
|
365
|
+
}
|
|
366
|
+
function parseStandaloneHttp(argv, label = "sol") {
|
|
367
|
+
if (!argv.includes("--http"))
|
|
368
|
+
return;
|
|
369
|
+
const val = (name) => {
|
|
370
|
+
const i = argv.indexOf(name);
|
|
371
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined;
|
|
372
|
+
};
|
|
373
|
+
const token = resolveMcpToken(val("--token"));
|
|
374
|
+
if (!token) {
|
|
375
|
+
process.stderr.write("refusing to start an OPEN HTTP MCP — set SOL_MCP_TOKEN (or --token <T>). every request must send `Authorization: Bearer <token>`.\n");
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
const portRaw = val("--port");
|
|
379
|
+
return { token, port: portRaw ? Number(portRaw) : undefined, host: val("--host"), label };
|
|
380
|
+
}
|
|
381
|
+
var MCP_PATH = "/mcp";
|
|
382
|
+
var init_mcp_http = () => {};
|
|
383
|
+
|
|
249
384
|
// src/bin/identity-store.ts
|
|
250
385
|
import { chmodSync as chmodSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
251
386
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -1775,9 +1910,11 @@ function resolveAll(world) {
|
|
|
1775
1910
|
return world.manifest.order.map((e) => resolveOne(world, e));
|
|
1776
1911
|
}
|
|
1777
1912
|
function audienceFor(world, env, entryAud) {
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1913
|
+
const base = entryAud && entryAud.length ? entryAud : world.files[env]?.audience ?? [];
|
|
1914
|
+
const runtime = world.manifest.runtime[env];
|
|
1915
|
+
if (runtime && !base.includes(runtime))
|
|
1916
|
+
return [...base, runtime];
|
|
1917
|
+
return base;
|
|
1781
1918
|
}
|
|
1782
1919
|
function ensureEnvDir(solDir) {
|
|
1783
1920
|
mkdirSync3(envDir(solDir), { recursive: true });
|
|
@@ -1868,7 +2005,14 @@ function validateWorld(solDir, world, opts = {}) {
|
|
|
1868
2005
|
const actual = new Set(recipientsOf(box));
|
|
1869
2006
|
const expected = new Set(resolvedAud.accountIds);
|
|
1870
2007
|
const extra = [...actual].filter((a) => !expected.has(a));
|
|
1871
|
-
|
|
2008
|
+
let missing = [...expected].filter((a) => !actual.has(a));
|
|
2009
|
+
const runtimeHandle2 = world.manifest.runtime[r.env];
|
|
2010
|
+
const runtimeAccts = new Set((runtimeHandle2 ? world.gate.handles[runtimeHandle2] ?? [] : []).map((rec) => rec.accountId));
|
|
2011
|
+
const missingRuntime = missing.filter((a) => runtimeAccts.has(a));
|
|
2012
|
+
missing = missing.filter((a) => !runtimeAccts.has(a));
|
|
2013
|
+
if (missingRuntime.length) {
|
|
2014
|
+
issues.push({ severity: "warn", check: "audience-integrity", message: `${r.env}/${s.name}: the runtime recipient [${missingRuntime.join(", ")}] joined the env @audience after this value was sealed — it can't inject this secret yet. reseal it (\`sol secret set ${s.name} --env ${r.env}\`) to include the runtime.` });
|
|
2015
|
+
}
|
|
1872
2016
|
if (extra.length || missing.length) {
|
|
1873
2017
|
issues.push({ severity: "error", check: "audience-integrity", message: `${r.env}/${s.name}: sealed recipients drift from the audience (missing: [${missing.join(", ")}], extra: [${extra.join(", ")}]) — reseal with \`sol secret set\` or \`sol secret audience\`` });
|
|
1874
2018
|
}
|
|
@@ -2778,7 +2922,9 @@ function secretInject(ctx, args) {
|
|
|
2778
2922
|
ctx.die("usage: sol secret inject --env E -- <cmd> [args...]");
|
|
2779
2923
|
const cmd = args.slice(dashdash + 1);
|
|
2780
2924
|
const world = loadWorld(ctx.solDir);
|
|
2781
|
-
const
|
|
2925
|
+
const runtimeRoot = process.env.SOL_RUNTIME_RECOVERY_CODE;
|
|
2926
|
+
const self = runtimeRoot ? runtimeSelfFromRoot(env, runtimeRoot) : ctx.loadSelfIdentity();
|
|
2927
|
+
const recipientActor = self?.accountId ?? ctx.actor;
|
|
2782
2928
|
const injected = {};
|
|
2783
2929
|
let revealedCount = 0;
|
|
2784
2930
|
for (const s of resolveOne(world, env).secrets) {
|
|
@@ -2795,7 +2941,7 @@ function secretInject(ctx, args) {
|
|
|
2795
2941
|
revealedCount++;
|
|
2796
2942
|
}
|
|
2797
2943
|
if (!revealedCount)
|
|
2798
|
-
ctx.die(`no secrets in ${env} are readable by ${
|
|
2944
|
+
ctx.die(`no secrets in ${env} are readable by ${recipientActor} — are you a recipient? (set SOL_RECOVERY_CODE, or SOL_RUNTIME_RECOVERY_CODE when injecting from the runtime)`);
|
|
2799
2945
|
process.stderr.write(`sol: injecting ${revealedCount} secret(s) into the subprocess env: ${Object.keys(injected).join(", ")}
|
|
2800
2946
|
`);
|
|
2801
2947
|
const res = spawnSync(cmd[0], cmd.slice(1), { stdio: "inherit", env: { ...process.env, ...injected } });
|
|
@@ -2912,6 +3058,11 @@ async function runtimeProvision(ctx, args) {
|
|
|
2912
3058
|
const { gate } = await audienceAdd(audCtx, world.gate, prov.handle, prov.accountId, prov.x25519Pub);
|
|
2913
3059
|
writeAudienceDoc(ctx.solDir, gate);
|
|
2914
3060
|
journal2(ctx, id, { op: "audience-add", handle: prov.handle, members: memberSnapshot(gate, prov.handle), recipientAtOp: true, prevAud, newAud: (gate.handles[prov.handle] ?? []).map((r) => r.accountId), at: Date.now() });
|
|
3061
|
+
const envFile = world.files[env];
|
|
3062
|
+
if (envFile && !(envFile.audience ?? []).includes(prov.handle)) {
|
|
3063
|
+
envFile.audience = [...envFile.audience ?? [], prov.handle];
|
|
3064
|
+
writeEnvFile(ctx.solDir, envFile);
|
|
3065
|
+
}
|
|
2915
3066
|
world.manifest.runtime[env] = prov.handle;
|
|
2916
3067
|
writeManifest(ctx.solDir, world.manifest);
|
|
2917
3068
|
regenerateSchema(ctx.solDir, loadWorld(ctx.solDir));
|
|
@@ -3198,16 +3349,10 @@ var EXPORT_KDF = { N: 1 << 15, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
|
|
|
3198
3349
|
var VERIFIED_PATH = join4(homedir(), ".sol", "verified-keys.json");
|
|
3199
3350
|
|
|
3200
3351
|
// src/bin/sol-secret-mcp.ts
|
|
3201
|
-
|
|
3352
|
+
init_mcp_http();
|
|
3353
|
+
async function buildSecretServer(solDir) {
|
|
3202
3354
|
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
3203
|
-
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3204
3355
|
const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
3205
|
-
const solDir = opts.solDir || process.env.SOL_DIR || join7(process.cwd(), ".sol");
|
|
3206
|
-
if (!existsSync8(solDir)) {
|
|
3207
|
-
process.stderr.write(`sol-secret-mcp: no .sol at ${solDir} \u2014 run \`sol init\` first (or set SOL_DIR)
|
|
3208
|
-
`);
|
|
3209
|
-
process.exit(1);
|
|
3210
|
-
}
|
|
3211
3356
|
const dirUrl = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
|
|
3212
3357
|
async function fetchKey2(account) {
|
|
3213
3358
|
const { fetchKey: f } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
|
|
@@ -3237,10 +3382,27 @@ async function startSecretMcp(opts = {}) {
|
|
|
3237
3382
|
const r = await callMcpTool(ctx, req.params.name, req.params.arguments ?? {});
|
|
3238
3383
|
return { content: [{ type: "text", text: r.text }], isError: r.isError };
|
|
3239
3384
|
});
|
|
3385
|
+
return server;
|
|
3386
|
+
}
|
|
3387
|
+
async function startSecretMcp(opts = {}) {
|
|
3388
|
+
const solDir = opts.solDir || process.env.SOL_DIR || join7(process.cwd(), ".sol");
|
|
3389
|
+
if (!existsSync8(solDir)) {
|
|
3390
|
+
process.stderr.write(`sol-secret-mcp: no .sol at ${solDir} \u2014 run \`sol init\` first (or set SOL_DIR)
|
|
3391
|
+
`);
|
|
3392
|
+
process.exit(1);
|
|
3393
|
+
}
|
|
3394
|
+
if (opts.http) {
|
|
3395
|
+
const { serveMcpHttp: serveMcpHttp2 } = await Promise.resolve().then(() => (init_mcp_http(), exports_mcp_http));
|
|
3396
|
+
await serveMcpHttp2(() => buildSecretServer(solDir), opts.http);
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3400
|
+
const server = await buildSecretServer(solDir);
|
|
3240
3401
|
await server.connect(new StdioServerTransport);
|
|
3241
3402
|
}
|
|
3242
3403
|
if (__require.main == __require.module) {
|
|
3243
|
-
|
|
3404
|
+
const http = parseStandaloneHttp(process.argv.slice(2), "sol-secrets");
|
|
3405
|
+
startSecretMcp({ http }).catch((e) => {
|
|
3244
3406
|
process.stderr.write(`sol-secret-mcp: ${e?.message ?? e}
|
|
3245
3407
|
`);
|
|
3246
3408
|
process.exit(1);
|