midsummer-sol 0.1.4 → 0.2.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.
Files changed (5) hide show
  1. package/index.js +818 -51
  2. package/package.json +9 -33
  3. package/sol-mcp.js +373 -28
  4. package/sol-secret-mcp.js +3250 -0
  5. package/sol.js +9174 -1963
package/package.json CHANGED
@@ -1,39 +1,15 @@
1
1
  {
2
2
  "name": "midsummer-sol",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Sol — agent-native version control (a new git). CLI, MCP server, and no-filesystem SDK.",
5
- "bin": {
6
- "sol": "./sol.js",
7
- "sol-mcp": "./sol-mcp.js"
8
- },
5
+ "bin": { "sol": "./sol.js", "sol-mcp": "./sol-mcp.js", "sol-secret-mcp": "./sol-secret-mcp.js" },
9
6
  "main": "./index.js",
10
- "exports": {
11
- ".": "./index.js"
12
- },
7
+ "exports": { ".": "./index.js" },
13
8
  "type": "module",
14
- "files": [
15
- "sol.js",
16
- "sol-mcp.js",
17
- "index.js",
18
- "README.md",
19
- "LICENSE"
20
- ],
21
- "dependencies": {
22
- "@modelcontextprotocol/sdk": "^1.29.0"
23
- },
24
- "engines": {
25
- "node": ">=18"
26
- },
9
+ "files": ["sol.js", "sol-mcp.js", "sol-secret-mcp.js", "index.js", "README.md", "LICENSE"],
10
+ "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0" },
11
+ "engines": { "node": ">=18" },
27
12
  "license": "Apache-2.0",
28
- "keywords": [
29
- "vcs",
30
- "git",
31
- "version-control",
32
- "agent",
33
- "mcp",
34
- "no-filesystem"
35
- ],
36
- "publishConfig": {
37
- "access": "public"
38
- }
39
- }
13
+ "keywords": ["vcs", "git", "version-control", "agent", "mcp", "no-filesystem"],
14
+ "publishConfig": { "access": "public" }
15
+ }
package/sol-mcp.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
+ import { createRequire } from "node:module";
4
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
5
 
4
6
  // src/bin/sol-mcp.ts
5
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
7
  import { mkdirSync as mkdirSync2 } from "fs";
9
8
  import { join as join2 } from "path";
10
9
 
@@ -21,7 +20,9 @@ function canonicalOp(op) {
21
20
  path: op.path,
22
21
  rootAfter: op.rootAfter,
23
22
  by: op.by ?? null,
24
- message: op.message ?? null
23
+ message: op.message ?? null,
24
+ ...op.kind !== undefined ? { kind: op.kind } : {},
25
+ ...op.prov !== undefined ? { prov: op.prov } : {}
25
26
  });
26
27
  }
27
28
 
@@ -53,7 +54,10 @@ function hashNode(node) {
53
54
  canon = node.encoding ? `blob\x00${node.encoding}\x00${node.content}` : `blob\x00${node.content}`;
54
55
  } else if (node.kind === "sealed")
55
56
  canon = `sealed\x00${node.box}`;
56
- else
57
+ else if (node.kind === "prov") {
58
+ const f = (k, v) => v === undefined ? "" : `\x00${k}\x00${v}`;
59
+ canon = "prov" + f("agentId", node.agentId) + f("sessionId", node.sessionId) + f("model", node.model) + f("intent", node.intent) + f("tool", node.tool) + f("rationale", node.rationale) + f("parent", node.parent);
60
+ } else
57
61
  canon = "tree\x00" + Object.keys(node.entries).sort().map((k) => {
58
62
  const e = node.entries[k];
59
63
  return e.mode === undefined ? `${k}\x00${e.kind}\x00${e.hash}` : `${k}\x00${e.kind}\x00${e.hash}\x00${e.mode}`;
@@ -122,9 +126,128 @@ class MemoryAsyncStore {
122
126
  }
123
127
  }
124
128
 
129
+ // src/struct.ts
130
+ import { randomBytes } from "node:crypto";
131
+ var SLOT_PREFIX = "\x00slot\x00";
132
+ var HIDDEN_KEY = "\x00hidden";
133
+ function isReservedKey(key) {
134
+ return key.startsWith("\x00");
135
+ }
136
+ function slotKey(slotId) {
137
+ return SLOT_PREFIX + slotId;
138
+ }
139
+ function slotIdFromKey(key) {
140
+ return key.startsWith(SLOT_PREFIX) ? key.slice(SLOT_PREFIX.length) : undefined;
141
+ }
142
+ function mintSlotId() {
143
+ return randomBytes(16).toString("base64url");
144
+ }
145
+ var STRUCT_HEADER = "__solStruct";
146
+ function parseStruct(plaintext) {
147
+ if (!plaintext.startsWith("{"))
148
+ return;
149
+ let v;
150
+ try {
151
+ v = JSON.parse(plaintext);
152
+ } catch {
153
+ return;
154
+ }
155
+ if (!v || typeof v !== "object")
156
+ return;
157
+ const tag = v[STRUCT_HEADER];
158
+ if (tag === "subtree" || tag === "name" || tag === "entries")
159
+ return v;
160
+ return;
161
+ }
162
+ function nextBucket(n) {
163
+ let b = 256;
164
+ while (b < n)
165
+ b *= 2;
166
+ return b;
167
+ }
168
+ function padPlaintext(p) {
169
+ const bare = JSON.stringify(p);
170
+ const target = nextBucket(bare.length);
171
+ if (bare.length >= target)
172
+ return bare;
173
+ const overhead = `,"${"__pad"}":""`.length;
174
+ const fill = Math.max(0, target - bare.length - overhead);
175
+ return JSON.stringify({ ...p, __pad: " ".repeat(fill) });
176
+ }
177
+ async function collectAsync(store, hash, into) {
178
+ if (into[hash])
179
+ return;
180
+ const node = await store.get(hash);
181
+ if (!node)
182
+ throw new Error(`sealSubtree: node missing from store: ${hash}`);
183
+ into[hash] = node;
184
+ if (node.kind === "tree")
185
+ for (const e of Object.values(node.entries))
186
+ await collectAsync(store, e.hash, into);
187
+ }
188
+ async function serializeSubtreeAsync(store, dirRoot) {
189
+ const nodes = {};
190
+ await collectAsync(store, dirRoot, nodes);
191
+ return padPlaintext({ __solStruct: "subtree", bundle: { root: dirRoot, nodes } });
192
+ }
193
+ async function subtreeRootAt(store, root, dirPath) {
194
+ const segs = dirPath.split("/").filter(Boolean);
195
+ let cur = root;
196
+ for (let i = 0;i < segs.length; i++) {
197
+ const entry = (await getTree(store, cur)).entries[segs[i]];
198
+ if (!entry || entry.kind !== "tree")
199
+ return;
200
+ cur = entry.hash;
201
+ }
202
+ return cur;
203
+ }
204
+ async function entryAt(store, root, path) {
205
+ const segs = path.split("/").filter(Boolean);
206
+ let cur = root;
207
+ for (let i = 0;i < segs.length; i++) {
208
+ const entry = (await getTree(store, cur)).entries[segs[i]];
209
+ if (!entry)
210
+ return;
211
+ if (i === segs.length - 1)
212
+ return entry;
213
+ if (entry.kind !== "tree")
214
+ return;
215
+ cur = entry.hash;
216
+ }
217
+ return;
218
+ }
219
+ async function serializeNameAsync(store, name, entry) {
220
+ const nodes = {};
221
+ await collectAsync(store, entry.hash, nodes);
222
+ const payload = {
223
+ __solStruct: "name",
224
+ name,
225
+ entryKind: entry.kind,
226
+ ...entry.mode === undefined ? {} : { entryMode: entry.mode },
227
+ bundle: { root: entry.hash, nodes }
228
+ };
229
+ return padPlaintext(payload);
230
+ }
231
+ async function hiddenEntryAsync(store, entry) {
232
+ const nodes = {};
233
+ await collectAsync(store, entry.hash, nodes);
234
+ return { entryKind: entry.kind, ...entry.mode === undefined ? {} : { entryMode: entry.mode }, bundle: { root: entry.hash, nodes } };
235
+ }
236
+ function serializeHiddenEntries(slots) {
237
+ return padPlaintext({ __solStruct: "entries", slots });
238
+ }
239
+
125
240
  // src/async-tree.ts
126
241
  function segments(path) {
127
- return path.split("/").filter(Boolean);
242
+ const segs = path.split("/").filter(Boolean);
243
+ for (const s of segs) {
244
+ if (!s.includes("\x00"))
245
+ continue;
246
+ const reserved = s.startsWith("\x00slot\x00") ? s.slice("\x00slot\x00".length) : s === "\x00hidden" ? "" : undefined;
247
+ if (reserved === undefined || reserved.includes("\x00"))
248
+ throw new Error("illegal NUL byte in path");
249
+ }
250
+ return segs;
128
251
  }
129
252
  async function emptyRoot(store) {
130
253
  return store.put({ kind: "tree", entries: {} });
@@ -138,6 +261,70 @@ async function writeSealed(store, root, path, box) {
138
261
  const hash = await store.put({ kind: "sealed", box });
139
262
  return writeInto(store, root, segments(path), hash, "sealed");
140
263
  }
264
+ async function hideName(store, root, path, box, slotId) {
265
+ const hash = await store.put({ kind: "sealed", box });
266
+ return hideInto(store, root, segments(path), hash, slotKey(slotId));
267
+ }
268
+ async function reHideName(store, root, dirSegs, key, box) {
269
+ const hash = await store.put({ kind: "sealed", box });
270
+ if (dirSegs.length === 0) {
271
+ const tree2 = await getTree(store, root);
272
+ return store.put({ kind: "tree", entries: { ...tree2.entries, [key]: { kind: "sealed", hash } } });
273
+ }
274
+ const tree = await getTree(store, root);
275
+ const entries = { ...tree.entries };
276
+ const [head, ...rest] = dirSegs;
277
+ const child = entries[head];
278
+ if (!child || child.kind !== "tree")
279
+ return root;
280
+ entries[head] = { kind: "tree", hash: await reHideName(store, child.hash, rest, key, box) };
281
+ return store.put({ kind: "tree", entries });
282
+ }
283
+ async function hideInto(store, treeHash, segs, sealedHash, key) {
284
+ const tree = await getTree(store, treeHash);
285
+ const entries = { ...tree.entries };
286
+ const [head, ...rest] = segs;
287
+ if (rest.length === 0) {
288
+ delete entries[head];
289
+ entries[key] = { kind: "sealed", hash: sealedHash };
290
+ } else {
291
+ const child = entries[head];
292
+ if (!child || child.kind !== "tree")
293
+ return treeHash;
294
+ entries[head] = { kind: "tree", hash: await hideInto(store, child.hash, rest, sealedHash, key) };
295
+ }
296
+ return store.put({ kind: "tree", entries });
297
+ }
298
+ async function hideExistence(store, root, path, hiddenBox) {
299
+ const hash = await store.put({ kind: "sealed", box: hiddenBox });
300
+ return hideExistenceInto(store, root, segments(path), hash);
301
+ }
302
+ async function hideExistenceInto(store, treeHash, segs, sidecarHash) {
303
+ const tree = await getTree(store, treeHash);
304
+ const entries = { ...tree.entries };
305
+ const [head, ...rest] = segs;
306
+ if (rest.length === 0) {
307
+ delete entries[head];
308
+ entries[HIDDEN_KEY] = { kind: "sealed", hash: sidecarHash };
309
+ } else {
310
+ const child = entries[head];
311
+ if (!child || child.kind !== "tree")
312
+ return treeHash;
313
+ entries[head] = { kind: "tree", hash: await hideExistenceInto(store, child.hash, rest, sidecarHash) };
314
+ }
315
+ return store.put({ kind: "tree", entries });
316
+ }
317
+ async function dirEntriesAt(store, root, dirPath) {
318
+ const segs = segments(dirPath);
319
+ let cur = root;
320
+ for (const seg of segs) {
321
+ const entry = (await getTree(store, cur)).entries[seg];
322
+ if (!entry || entry.kind !== "tree")
323
+ return;
324
+ cur = entry.hash;
325
+ }
326
+ return (await getTree(store, cur)).entries;
327
+ }
141
328
  async function writeInto(store, treeHash, segs, hash, leafKind, mode) {
142
329
  const tree = await getTree(store, treeHash);
143
330
  const entries = { ...tree.entries };
@@ -265,6 +452,10 @@ async function listAll(store, root, prefix = "") {
265
452
  const tree = await getTree(store, root);
266
453
  const out = [];
267
454
  for (const [name, entry] of Object.entries(tree.entries)) {
455
+ if (isReservedKey(name)) {
456
+ out.push(prefix ? `${prefix}/${name}` : name);
457
+ continue;
458
+ }
268
459
  const p = prefix ? `${prefix}/${name}` : name;
269
460
  if (entry.kind === "tree")
270
461
  out.push(...await listAll(store, entry.hash, p));
@@ -274,6 +465,43 @@ async function listAll(store, root, prefix = "") {
274
465
  return out.sort();
275
466
  }
276
467
 
468
+ // src/sign.ts
469
+ var ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
470
+
471
+ // src/prov.ts
472
+ function provenanceFromEnv(env = process.env) {
473
+ const kind = env.SOL_KIND === "agent" ? "agent" : "human";
474
+ const node = buildProvNode({
475
+ agentId: env.SOL_AGENT_ID || (kind === "agent" ? env.SOL_ACTOR : undefined),
476
+ sessionId: env.SOL_SESSION,
477
+ model: env.SOL_MODEL,
478
+ intent: env.SOL_INTENT,
479
+ tool: env.SOL_TOOL
480
+ });
481
+ return node ? { kind, node } : { kind };
482
+ }
483
+ function buildProvNode(fields) {
484
+ const clean = { kind: "prov" };
485
+ let any = false;
486
+ for (const k of ["agentId", "sessionId", "model", "intent", "tool", "rationale", "parent"]) {
487
+ const v = fields[k];
488
+ if (v) {
489
+ clean[k] = v;
490
+ any = true;
491
+ }
492
+ }
493
+ return any ? clean : undefined;
494
+ }
495
+ async function captureProv(sink, env = process.env) {
496
+ const { kind, node } = provenanceFromEnv(env);
497
+ const out = {};
498
+ if (kind === "agent")
499
+ out.kind = "agent";
500
+ if (node)
501
+ out.prov = await sink.put(node);
502
+ return out;
503
+ }
504
+
277
505
  // src/types.ts
278
506
  var SEALED = "<<sealed>>";
279
507
 
@@ -284,14 +512,27 @@ function pageOps(ops, opts = {}) {
284
512
  out = out.slice(0, opts.limit);
285
513
  return out;
286
514
  }
515
+ var noSign = (op) => op;
516
+
287
517
  class AsyncRepo {
288
518
  store;
289
519
  log;
290
520
  actor;
291
- constructor(store, log, actor = "anon") {
521
+ sign;
522
+ constructor(store, log, actor = "anon", sign = noSign) {
292
523
  this.store = store;
293
524
  this.log = log;
294
525
  this.actor = actor;
526
+ this.sign = sign;
527
+ }
528
+ async appendAuthored(entry, by) {
529
+ await this.log.append(by === this.actor ? this.sign(entry) : entry);
530
+ }
531
+ provFor;
532
+ async provenance(by) {
533
+ if (by !== this.actor)
534
+ return {};
535
+ return this.provFor ??= captureProv(this.store);
295
536
  }
296
537
  async currentRoot() {
297
538
  const head = await this.log.head();
@@ -308,7 +549,7 @@ class AsyncRepo {
308
549
  }
309
550
  async writeFile(path, content, at = Date.now(), by = this.actor) {
310
551
  const rootAfter = await writeFile(this.store, await this.currentRoot(), path, content);
311
- await this.log.append({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by });
552
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by, ...await this.provenance(by) }, by);
312
553
  return rootAfter;
313
554
  }
314
555
  async applyBatch(changes) {
@@ -327,7 +568,7 @@ class AsyncRepo {
327
568
  }
328
569
  async writeBytes(path, data, at = Date.now(), by = this.actor) {
329
570
  const rootAfter = await writeFile(this.store, await this.currentRoot(), path, Buffer.from(data).toString("base64"), { encoding: "base64" });
330
- await this.log.append({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by });
571
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by, ...await this.provenance(by) }, by);
331
572
  return rootAfter;
332
573
  }
333
574
  async readBytes(path) {
@@ -346,7 +587,7 @@ class AsyncRepo {
346
587
  if (!f)
347
588
  return this.currentRoot();
348
589
  const rootAfter = await writeFile(this.store, await this.currentRoot(), path, f.content, { encoding: f.encoding, mode });
349
- await this.log.append({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by: this.actor });
590
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "write", path, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
350
591
  return rootAfter;
351
592
  }
352
593
  async mode(path) {
@@ -354,7 +595,96 @@ class AsyncRepo {
354
595
  }
355
596
  async writeSealed(path, box, at = Date.now()) {
356
597
  const rootAfter = await writeSealed(this.store, await this.currentRoot(), path, box);
357
- await this.log.append({ seq: await this.log.seq() + 1, type: "seal", path, rootAfter, at, by: this.actor });
598
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
599
+ return rootAfter;
600
+ }
601
+ async sealSubtree(dirPath, seal, at = Date.now()) {
602
+ const subRoot = await subtreeRootAt(this.store, await this.currentRoot(), dirPath);
603
+ if (subRoot === undefined)
604
+ return;
605
+ const plaintext = await serializeSubtreeAsync(this.store, subRoot);
606
+ return this.writeSealed(dirPath, seal(plaintext), at);
607
+ }
608
+ async hideName(path, seal, opts = {}, at = Date.now()) {
609
+ const root = await this.currentRoot();
610
+ const segs = path.split("/").filter(Boolean);
611
+ const name = segs[segs.length - 1];
612
+ let entry = await entryAt(this.store, root, path);
613
+ if (!entry) {
614
+ if (opts.contentSeal && opts.absentContent !== undefined) {
615
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(opts.absentContent) });
616
+ entry = { kind: "sealed", hash: sealedHash };
617
+ } else {
618
+ return;
619
+ }
620
+ }
621
+ if (slotIdFromKey(name) !== undefined)
622
+ return slotIdFromKey(name);
623
+ if (opts.contentSeal && entry.kind === "blob") {
624
+ const blob = await this.store.get(entry.hash);
625
+ if (blob && blob.kind === "blob") {
626
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(blob.content) });
627
+ entry = entry.mode === undefined ? { kind: "sealed", hash: sealedHash } : { kind: "sealed", hash: sealedHash, mode: entry.mode };
628
+ }
629
+ }
630
+ const slotId = opts.slotId ?? mintSlotId();
631
+ const plaintext = await serializeNameAsync(this.store, name, entry);
632
+ const rootAfter = await hideName(this.store, root, path, seal(plaintext), slotId);
633
+ const slotPath = segs.length > 1 ? `${segs.slice(0, -1).join("/")}/${slotKey(slotId)}` : slotKey(slotId);
634
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path: slotPath, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
635
+ return slotId;
636
+ }
637
+ async reHideName(realDir, name, slotId, newContent, seal, contentSeal, at = Date.now()) {
638
+ const root = await this.currentRoot();
639
+ const sealedHash = await this.store.put({ kind: "sealed", box: contentSeal(newContent) });
640
+ const childEntry = { kind: "sealed", hash: sealedHash };
641
+ const plaintext = await serializeNameAsync(this.store, name, childEntry);
642
+ const dirSegs = realDir.split("/").filter(Boolean);
643
+ const rootAfter = await reHideName(this.store, root, dirSegs, slotKey(slotId), seal(plaintext));
644
+ const slotPath = dirSegs.length ? `${dirSegs.join("/")}/${slotKey(slotId)}` : slotKey(slotId);
645
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path: slotPath, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
646
+ return rootAfter;
647
+ }
648
+ async hideExistence(path, seal, opts = {}, at = Date.now()) {
649
+ const root = await this.currentRoot();
650
+ const segs = path.split("/").filter(Boolean);
651
+ const name = segs[segs.length - 1];
652
+ const dirPath = segs.slice(0, -1).join("/");
653
+ let entry = await entryAt(this.store, root, path);
654
+ if (name === HIDDEN_KEY)
655
+ return root;
656
+ if (!entry) {
657
+ if (opts.contentSeal && opts.absentContent !== undefined) {
658
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(opts.absentContent) });
659
+ entry = { kind: "sealed", hash: sealedHash };
660
+ } else {
661
+ return;
662
+ }
663
+ }
664
+ if (opts.contentSeal && entry.kind === "blob") {
665
+ const blob = await this.store.get(entry.hash);
666
+ if (blob && blob.kind === "blob") {
667
+ const sealedHash = await this.store.put({ kind: "sealed", box: opts.contentSeal(blob.content) });
668
+ entry = entry.mode === undefined ? { kind: "sealed", hash: sealedHash } : { kind: "sealed", hash: sealedHash, mode: entry.mode };
669
+ }
670
+ }
671
+ const slots = {};
672
+ const dirEntries = await dirEntriesAt(this.store, root, dirPath);
673
+ const prior = dirEntries?.[HIDDEN_KEY];
674
+ if (prior && prior.kind === "sealed" && opts.openPrior) {
675
+ const priorNode = await this.store.get(prior.hash);
676
+ if (priorNode && priorNode.kind === "sealed") {
677
+ const opened = opts.openPrior(priorNode.box);
678
+ const payload = opened === undefined ? undefined : parseStruct(opened);
679
+ if (payload?.__solStruct === "entries")
680
+ Object.assign(slots, payload.slots);
681
+ }
682
+ }
683
+ slots[name] = await hiddenEntryAsync(this.store, entry);
684
+ const plaintext = serializeHiddenEntries(slots);
685
+ const rootAfter = await hideExistence(this.store, root, path, seal(plaintext));
686
+ const hiddenPath = dirPath ? `${dirPath}/${HIDDEN_KEY}` : HIDDEN_KEY;
687
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "seal", path: hiddenPath, rootAfter, at, by: this.actor, ...await this.provenance(this.actor) }, this.actor);
358
688
  return rootAfter;
359
689
  }
360
690
  async read(path) {
@@ -362,7 +692,7 @@ class AsyncRepo {
362
692
  }
363
693
  async deleteFile(path, at = Date.now(), by = this.actor) {
364
694
  const rootAfter = await deleteFile(this.store, await this.currentRoot(), path);
365
- await this.log.append({ seq: await this.log.seq() + 1, type: "delete", path, rootAfter, at, by });
695
+ await this.appendAuthored({ seq: await this.log.seq() + 1, type: "delete", path, rootAfter, at, by, ...await this.provenance(by) }, by);
366
696
  return rootAfter;
367
697
  }
368
698
  async readFile(path) {
@@ -393,8 +723,8 @@ function decodeObject(raw) {
393
723
 
394
724
  class FileStore {
395
725
  objects;
396
- constructor(solDir) {
397
- this.objects = join(solDir, "objects");
726
+ constructor(solDir, objectsDir) {
727
+ this.objects = objectsDir ?? join(solDir, "objects");
398
728
  mkdirSync(this.objects, { recursive: true });
399
729
  }
400
730
  path(hash) {
@@ -575,9 +905,6 @@ class SolWorkspace {
575
905
  }
576
906
 
577
907
  // src/bin/sol-mcp.ts
578
- var solDir = process.env.SOL_DIR || join2(process.cwd(), ".sol");
579
- mkdirSync2(solDir, { recursive: true });
580
- var ws = new SolWorkspace(new FileStore(solDir), new FileOpLog(solDir), process.env.SOL_ACTOR || "agent");
581
908
  var tools = [
582
909
  { name: "sol_write", description: "Create or overwrite a text file in the sol workspace. Authoring goes here \u2014 not to a disk.", inputSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
583
910
  { name: "sol_read", description: "Read a text file from the sol workspace.", inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
@@ -589,7 +916,7 @@ var tools = [
589
916
  { name: "sol_history", description: "Show the authoring history (the op-log).", inputSchema: { type: "object", properties: {} } }
590
917
  ];
591
918
  var text = (s) => ({ content: [{ type: "text", text: s }] });
592
- async function handle(name, a) {
919
+ async function handle(ws, name, a) {
593
920
  switch (name) {
594
921
  case "sol_write":
595
922
  await ws.write(a.path, a.content);
@@ -626,13 +953,31 @@ async function handle(name, a) {
626
953
  throw new Error("unknown tool: " + name);
627
954
  }
628
955
  }
629
- var server = new Server({ name: "sol", version: "0.1.0" }, { capabilities: { tools: {} } });
630
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
631
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
632
- try {
633
- return await handle(req.params.name, req.params.arguments ?? {});
634
- } catch (e) {
635
- return { content: [{ type: "text", text: "sol error: " + (e?.message ?? e) }], isError: true };
636
- }
637
- });
638
- await server.connect(new StdioServerTransport);
956
+ async function startWorkspaceMcp(opts = {}) {
957
+ const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
958
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
959
+ const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
960
+ const solDir = opts.solDir || process.env.SOL_DIR || join2(process.cwd(), ".sol");
961
+ mkdirSync2(solDir, { recursive: true });
962
+ const ws = new SolWorkspace(new FileStore(solDir), new FileOpLog(solDir), process.env.SOL_ACTOR || "agent");
963
+ const server = new Server({ name: "sol", version: "0.1.0" }, { capabilities: { tools: {} } });
964
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
965
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
966
+ try {
967
+ return await handle(ws, req.params.name, req.params.arguments ?? {});
968
+ } catch (e) {
969
+ return { content: [{ type: "text", text: "sol error: " + (e?.message ?? e) }], isError: true };
970
+ }
971
+ });
972
+ await server.connect(new StdioServerTransport);
973
+ }
974
+ if (__require.main == __require.module) {
975
+ startWorkspaceMcp().catch((e) => {
976
+ process.stderr.write(`sol-mcp: ${e?.message ?? e}
977
+ `);
978
+ process.exit(1);
979
+ });
980
+ }
981
+ export {
982
+ startWorkspaceMcp
983
+ };