midsummer-sol 0.2.0 → 0.2.2
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/package.json +1 -1
- package/sol-mcp.js +11447 -94
- package/sol-secret-mcp.js +185 -22
- package/sol.js +4683 -280
package/sol-secret-mcp.js
CHANGED
|
@@ -246,8 +246,143 @@ 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
|
-
import { chmodSync as chmodSync2, existsSync as existsSync6, mkdirSync as
|
|
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";
|
|
252
387
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
253
388
|
function loadIdentity2() {
|
|
@@ -271,7 +406,7 @@ function loadVerified() {
|
|
|
271
406
|
function pinVerified(accountId, fingerprint) {
|
|
272
407
|
const all = loadVerified();
|
|
273
408
|
all[accountId] = fingerprint;
|
|
274
|
-
|
|
409
|
+
mkdirSync5(dirname2(VERIFIED_PATH2), { recursive: true });
|
|
275
410
|
writeFileSync6(VERIFIED_PATH2, JSON.stringify(all, null, 2), { mode: 384 });
|
|
276
411
|
try {
|
|
277
412
|
chmodSync2(VERIFIED_PATH2, 384);
|
|
@@ -1447,7 +1582,7 @@ function authorFingerprint(e) {
|
|
|
1447
1582
|
return e.authorKey ? fingerprintOf(e.authorKey) : undefined;
|
|
1448
1583
|
}
|
|
1449
1584
|
// src/secret/anchor.ts
|
|
1450
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1585
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1451
1586
|
import { join as join3 } from "node:path";
|
|
1452
1587
|
init_sign();
|
|
1453
1588
|
var creatorPinPath = (solDir) => join3(envDir(solDir), "creator.pin");
|
|
@@ -1641,6 +1776,7 @@ function validateGenesisAnchor(solDir) {
|
|
|
1641
1776
|
}
|
|
1642
1777
|
return out;
|
|
1643
1778
|
}
|
|
1779
|
+
var ENVSTATE_SKIP_FILES = new Set(["journal.jsonl", "creator.pin", "head.pin"]);
|
|
1644
1780
|
// src/secret/env-state.ts
|
|
1645
1781
|
init_sign();
|
|
1646
1782
|
// src/secret/gate-derive.ts
|
|
@@ -1741,7 +1877,7 @@ function canDeclare(solDir, prior, actor, id) {
|
|
|
1741
1877
|
return { allowed: !!id, guarded: true };
|
|
1742
1878
|
}
|
|
1743
1879
|
// src/secret/model.ts
|
|
1744
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
1880
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1745
1881
|
init_sign();
|
|
1746
1882
|
var BASE_ENV = "base";
|
|
1747
1883
|
function envInitialized(solDir) {
|
|
@@ -1774,13 +1910,15 @@ function resolveAll(world) {
|
|
|
1774
1910
|
return world.manifest.order.map((e) => resolveOne(world, e));
|
|
1775
1911
|
}
|
|
1776
1912
|
function audienceFor(world, env, entryAud) {
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
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;
|
|
1780
1918
|
}
|
|
1781
1919
|
function ensureEnvDir(solDir) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1920
|
+
mkdirSync3(envDir(solDir), { recursive: true });
|
|
1921
|
+
mkdirSync3(sealDir(solDir), { recursive: true });
|
|
1784
1922
|
}
|
|
1785
1923
|
function writeEnvFile(solDir, f) {
|
|
1786
1924
|
ensureEnvDir(solDir);
|
|
@@ -1867,7 +2005,14 @@ function validateWorld(solDir, world, opts = {}) {
|
|
|
1867
2005
|
const actual = new Set(recipientsOf(box));
|
|
1868
2006
|
const expected = new Set(resolvedAud.accountIds);
|
|
1869
2007
|
const extra = [...actual].filter((a) => !expected.has(a));
|
|
1870
|
-
|
|
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
|
+
}
|
|
1871
2016
|
if (extra.length || missing.length) {
|
|
1872
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\`` });
|
|
1873
2018
|
}
|
|
@@ -2777,7 +2922,9 @@ function secretInject(ctx, args) {
|
|
|
2777
2922
|
ctx.die("usage: sol secret inject --env E -- <cmd> [args...]");
|
|
2778
2923
|
const cmd = args.slice(dashdash + 1);
|
|
2779
2924
|
const world = loadWorld(ctx.solDir);
|
|
2780
|
-
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;
|
|
2781
2928
|
const injected = {};
|
|
2782
2929
|
let revealedCount = 0;
|
|
2783
2930
|
for (const s of resolveOne(world, env).secrets) {
|
|
@@ -2794,7 +2941,7 @@ function secretInject(ctx, args) {
|
|
|
2794
2941
|
revealedCount++;
|
|
2795
2942
|
}
|
|
2796
2943
|
if (!revealedCount)
|
|
2797
|
-
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)`);
|
|
2798
2945
|
process.stderr.write(`sol: injecting ${revealedCount} secret(s) into the subprocess env: ${Object.keys(injected).join(", ")}
|
|
2799
2946
|
`);
|
|
2800
2947
|
const res = spawnSync(cmd[0], cmd.slice(1), { stdio: "inherit", env: { ...process.env, ...injected } });
|
|
@@ -2911,6 +3058,11 @@ async function runtimeProvision(ctx, args) {
|
|
|
2911
3058
|
const { gate } = await audienceAdd(audCtx, world.gate, prov.handle, prov.accountId, prov.x25519Pub);
|
|
2912
3059
|
writeAudienceDoc(ctx.solDir, gate);
|
|
2913
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
|
+
}
|
|
2914
3066
|
world.manifest.runtime[env] = prov.handle;
|
|
2915
3067
|
writeManifest(ctx.solDir, world.manifest);
|
|
2916
3068
|
regenerateSchema(ctx.solDir, loadWorld(ctx.solDir));
|
|
@@ -3180,7 +3332,7 @@ async function callMcpTool(ctx, name, args = {}) {
|
|
|
3180
3332
|
}
|
|
3181
3333
|
|
|
3182
3334
|
// src/bin/identity-store.ts
|
|
3183
|
-
import { chmodSync, existsSync as existsSync5, mkdirSync as
|
|
3335
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3184
3336
|
import { homedir } from "node:os";
|
|
3185
3337
|
import { dirname, join as join4 } from "node:path";
|
|
3186
3338
|
var IDENTITY_PATH = join4(homedir(), ".sol", "identity.json");
|
|
@@ -3197,16 +3349,10 @@ var EXPORT_KDF = { N: 1 << 15, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
|
|
|
3197
3349
|
var VERIFIED_PATH = join4(homedir(), ".sol", "verified-keys.json");
|
|
3198
3350
|
|
|
3199
3351
|
// src/bin/sol-secret-mcp.ts
|
|
3200
|
-
|
|
3352
|
+
init_mcp_http();
|
|
3353
|
+
async function buildSecretServer(solDir) {
|
|
3201
3354
|
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
3202
|
-
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3203
3355
|
const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
3204
|
-
const solDir = opts.solDir || process.env.SOL_DIR || join7(process.cwd(), ".sol");
|
|
3205
|
-
if (!existsSync8(solDir)) {
|
|
3206
|
-
process.stderr.write(`sol-secret-mcp: no .sol at ${solDir} \u2014 run \`sol init\` first (or set SOL_DIR)
|
|
3207
|
-
`);
|
|
3208
|
-
process.exit(1);
|
|
3209
|
-
}
|
|
3210
3356
|
const dirUrl = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
|
|
3211
3357
|
async function fetchKey2(account) {
|
|
3212
3358
|
const { fetchKey: f } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
|
|
@@ -3236,10 +3382,27 @@ async function startSecretMcp(opts = {}) {
|
|
|
3236
3382
|
const r = await callMcpTool(ctx, req.params.name, req.params.arguments ?? {});
|
|
3237
3383
|
return { content: [{ type: "text", text: r.text }], isError: r.isError };
|
|
3238
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);
|
|
3239
3401
|
await server.connect(new StdioServerTransport);
|
|
3240
3402
|
}
|
|
3241
3403
|
if (__require.main == __require.module) {
|
|
3242
|
-
|
|
3404
|
+
const http = parseStandaloneHttp(process.argv.slice(2), "sol-secrets");
|
|
3405
|
+
startSecretMcp({ http }).catch((e) => {
|
|
3243
3406
|
process.stderr.write(`sol-secret-mcp: ${e?.message ?? e}
|
|
3244
3407
|
`);
|
|
3245
3408
|
process.exit(1);
|