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.
Files changed (4) hide show
  1. package/package.json +1 -1
  2. package/sol-mcp.js +11447 -94
  3. package/sol-secret-mcp.js +185 -22
  4. 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 mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
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
- mkdirSync4(dirname2(VERIFIED_PATH2), { recursive: true });
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 mkdirSync2, readdirSync, readFileSync as readFileSync4, statSync, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
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
- if (entryAud && entryAud.length)
1778
- return entryAud;
1779
- return world.files[env]?.audience ?? [];
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
- mkdirSync2(envDir(solDir), { recursive: true });
1783
- mkdirSync2(sealDir(solDir), { recursive: true });
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
- const missing = [...expected].filter((a) => !actual.has(a));
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 self = ctx.loadSelfIdentity();
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 ${ctx.actor} — are you a recipient? (set SOL_RECOVERY_CODE)`);
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 mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
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
- async function startSecretMcp(opts = {}) {
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
- startSecretMcp().catch((e) => {
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);