@tensor-cad/mcp 0.1.2 → 0.1.4

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/stdio.js CHANGED
@@ -312,7 +312,12 @@ Content-Length: 0\r
312
312
  setTimeout(() => socket.destroy(), 50).unref();
313
313
  return;
314
314
  }
315
- this.wss.handleUpgrade(req, socket, head, (ws) => this.attach(ws));
315
+ this.wss.handleUpgrade(req, socket, head, (ws) => {
316
+ this.attach(ws).catch((e) => {
317
+ this.log(`tensorcad bridge: could not greet a client: ${e.message}`);
318
+ this.send(ws, { type: "error", message: e.message });
319
+ });
320
+ });
316
321
  });
317
322
  }
318
323
  get port() {
@@ -386,14 +391,14 @@ Content-Length: 0\r
386
391
  }
387
392
  return;
388
393
  }
389
- attach(ws) {
394
+ async attach(ws) {
390
395
  this.send(ws, {
391
396
  type: "hello",
392
397
  protocol: BRIDGE_PROTOCOL,
393
398
  server: this.options.name,
394
399
  version: this.options.version,
395
400
  root: this.options.root,
396
- designs: this.options.store.list()
401
+ designs: await this.options.store.list()
397
402
  });
398
403
  ws.on("message", (raw) => {
399
404
  let message;
@@ -403,23 +408,21 @@ Content-Length: 0\r
403
408
  this.send(ws, { type: "error", message: `not JSON: ${e.message}` });
404
409
  return;
405
410
  }
406
- try {
407
- this.handle(ws, message);
408
- } catch (e) {
411
+ this.handle(ws, message).catch((e) => {
409
412
  this.send(ws, { type: "error", message: e.message, about: message?.type });
410
- }
413
+ });
411
414
  });
412
415
  }
413
- handle(ws, message) {
416
+ async handle(ws, message) {
414
417
  switch (message?.type) {
415
418
  case "publish": {
416
419
  const doc = asDocument(message.doc);
417
- const record = this.during(ws, () => this.options.store.adopt(doc));
420
+ const record = await this.during(ws, () => this.options.store.adopt(doc));
418
421
  this.send(ws, designMessage(record, "published"));
419
422
  return;
420
423
  }
421
424
  case "attach": {
422
- const record = this.options.store.get(message.design_id);
425
+ const record = await this.options.store.get(message.design_id);
423
426
  this.send(ws, designMessage(record, "requested"));
424
427
  return;
425
428
  }
@@ -427,12 +430,12 @@ Content-Length: 0\r
427
430
  case "replace": {
428
431
  const write = message.type === "ops" ? () => this.options.store.apply(message.design_id, parseOps(message.ops), message.revision) : () => this.options.store.replace(message.design_id, asDocument(message.doc), message.revision);
429
432
  try {
430
- const { record } = this.during(ws, write);
433
+ const { record } = await this.during(ws, write);
431
434
  this.send(ws, designMessage(record, message.type === "ops" ? "applied" : "replaced"));
432
435
  } catch (e) {
433
436
  if (e instanceof RevisionConflictError) {
434
437
  this.send(ws, { type: "error", message: e.message, about: message.type });
435
- this.send(ws, designMessage(this.options.store.get(message.design_id), "requested"));
438
+ this.send(ws, designMessage(await this.options.store.get(message.design_id), "requested"));
436
439
  return;
437
440
  }
438
441
  throw e;
@@ -462,13 +465,11 @@ Content-Length: 0\r
462
465
  }
463
466
  }
464
467
  }
465
- during(ws, work) {
468
+ async during(ws, work) {
466
469
  this.acting = ws;
467
- try {
468
- return work();
469
- } finally {
470
- this.acting = undefined;
471
- }
470
+ const running = work();
471
+ this.acting = undefined;
472
+ return await running;
472
473
  }
473
474
  send(ws, message) {
474
475
  if (ws.readyState !== ws.OPEN)
@@ -551,7 +552,7 @@ import { McpServer as McpServer2 } from "@modelcontextprotocol/server";
551
552
  // packages/mcp/package.json
552
553
  var package_default = {
553
554
  name: "@tensor-cad/mcp",
554
- version: "0.1.2",
555
+ version: "0.1.4",
555
556
  description: "Model Context Protocol server for TensorCAD: design, validate, analyze and generate LLM architectures from an agent",
556
557
  mcpName: "io.github.filip-pajalic/tensorcad",
557
558
  type: "module",
@@ -826,7 +827,7 @@ class FileStore {
826
827
  }
827
828
  }
828
829
  }
829
- list() {
830
+ async list() {
830
831
  return [...this.entries.values()].map((e) => summaryOf(e.record)).sort((a, b) => b.updated_at.localeCompare(a.updated_at));
831
832
  }
832
833
  async listFiles() {
@@ -852,13 +853,13 @@ class FileStore {
852
853
  await walk(this.root, this.depth);
853
854
  return out.sort();
854
855
  }
855
- get(id) {
856
+ async get(id) {
856
857
  const entry = this.entries.get(id);
857
858
  if (!entry)
858
859
  throw new UnknownDesignError(id, [...this.entries.keys()]);
859
860
  return entry.record;
860
861
  }
861
- create(options) {
862
+ async create(options) {
862
863
  let doc;
863
864
  let source;
864
865
  if (options.preset) {
@@ -875,7 +876,7 @@ class FileStore {
875
876
  doc.meta.name = options.name;
876
877
  return this.register(doc, source, undefined, true);
877
878
  }
878
- adopt(doc) {
879
+ async adopt(doc) {
879
880
  return this.register(doc, "derived", undefined, true);
880
881
  }
881
882
  async open(path) {
@@ -917,7 +918,7 @@ class FileStore {
917
918
  this.emit({ kind: "registered", record });
918
919
  return record;
919
920
  }
920
- apply(id, ops, expectedRevision) {
921
+ async apply(id, ops, expectedRevision) {
921
922
  const entry = this.entry(id);
922
923
  const { record } = entry;
923
924
  if (expectedRevision !== undefined && expectedRevision !== record.revision) {
@@ -935,7 +936,7 @@ class FileStore {
935
936
  this.emit({ kind: "applied", record, ops });
936
937
  return { record, applied, previousRevision };
937
938
  }
938
- replace(id, doc, expectedRevision) {
939
+ async replace(id, doc, expectedRevision) {
939
940
  const entry = this.entry(id);
940
941
  const { record } = entry;
941
942
  if (expectedRevision !== undefined && expectedRevision !== record.revision) {
@@ -952,7 +953,7 @@ class FileStore {
952
953
  return { record, applied: ["replaced the document"], previousRevision };
953
954
  }
954
955
  async save(id, path) {
955
- const record = this.get(id);
956
+ const record = await this.get(id);
956
957
  const target = path ? this.resolvePath(path) : record.path ?? join2(this.root, `${slug(record.name)}.tensorcad.json`);
957
958
  const text = `${JSON.stringify(record.doc, null, 2)}
958
959
  `;
@@ -964,7 +965,7 @@ class FileStore {
964
965
  this.emit({ kind: "saved", record });
965
966
  return { record, path: target, bytes: Buffer.byteLength(text, "utf8") };
966
967
  }
967
- checkpoint(id, label) {
968
+ async checkpoint(id, label) {
968
969
  const entry = this.entry(id);
969
970
  const info = {
970
971
  checkpoint_id: `ckpt_${this.nextCheckpoint++}`,
@@ -975,11 +976,11 @@ class FileStore {
975
976
  entry.checkpoints.set(info.checkpoint_id, { ...info, doc: structuredClone(entry.record.doc) });
976
977
  return info;
977
978
  }
978
- checkpoints(id) {
979
+ async checkpoints(id) {
979
980
  const entry = this.entry(id);
980
981
  return [...entry.checkpoints.values()].map(({ doc: _doc, ...info }) => info);
981
982
  }
982
- restore(id, checkpointId) {
983
+ async restore(id, checkpointId) {
983
984
  const entry = this.entry(id);
984
985
  const { record } = entry;
985
986
  let doc;
@@ -1036,6 +1037,25 @@ function slug(name) {
1036
1037
  return name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "design";
1037
1038
  }
1038
1039
 
1040
+ // packages/mcp/src/store/disk-artifacts.ts
1041
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1042
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
1043
+
1044
+ class DiskSink {
1045
+ root;
1046
+ label = "disk";
1047
+ constructor(root) {
1048
+ this.root = root;
1049
+ }
1050
+ async write(prefix, path, contents) {
1051
+ const base = isAbsolute2(prefix) ? prefix : resolve2(this.root, prefix);
1052
+ const target = join3(base, path);
1053
+ await mkdir3(dirname3(target), { recursive: true });
1054
+ await writeFile3(target, contents, "utf8");
1055
+ return { location: target };
1056
+ }
1057
+ }
1058
+
1039
1059
  // packages/mcp/src/prompts.ts
1040
1060
  import * as z2 from "zod";
1041
1061
  import { HARDWARE, PRESET_NAMES as PRESET_NAMES2 } from "@tensor-cad/engine/node";
@@ -1502,6 +1522,7 @@ function catalogEntry(def) {
1502
1522
  type: def.type,
1503
1523
  kind: def.kind,
1504
1524
  category: def.category,
1525
+ name: def.docs.name ?? def.type,
1505
1526
  summary: def.docs.summary ?? "",
1506
1527
  refs: def.docs.refs ?? [],
1507
1528
  params: def.paramOrder.map((name) => catalogParam(name, def.params[name])),
@@ -1540,7 +1561,10 @@ function allCatalogEntries() {
1540
1561
  }
1541
1562
  function catalogText(entries) {
1542
1563
  return entries.map((e) => {
1543
- const lines = [`${e.type} (${e.kind}/${e.category})`, ` ${e.summary}`];
1564
+ const lines = [
1565
+ e.name && e.name !== e.type ? `${e.type} — ${e.name} (${e.kind}/${e.category})` : `${e.type} (${e.kind}/${e.category})`,
1566
+ ` ${e.summary}`
1567
+ ];
1544
1568
  if (e.formula)
1545
1569
  lines.push(` formula: ${e.formula}`);
1546
1570
  if (e.params.length > 0) {
@@ -1698,8 +1722,8 @@ function registerResources(server, store) {
1698
1722
  title: "Design rule report",
1699
1723
  description: "Every finding for a design: shape errors, memory fit, kernel constraints, Chinchilla sanity.",
1700
1724
  mimeType: JSON_MIME
1701
- }, (uri, { id }) => {
1702
- const record = store.get(String(id));
1725
+ }, async (uri, { id }) => {
1726
+ const record = await store.get(String(id));
1703
1727
  const report = validate(record.doc);
1704
1728
  return json(uri, {
1705
1729
  design_id: record.design_id,
@@ -1714,14 +1738,14 @@ function registerResources(server, store) {
1714
1738
  title: "Design analysis",
1715
1739
  description: "Parameters, FLOPs, KV cache, memory, throughput and cost at the document's own defaults.",
1716
1740
  mimeType: JSON_MIME
1717
- }, (uri, { id }) => {
1718
- const record = store.get(String(id));
1741
+ }, async (uri, { id }) => {
1742
+ const record = await store.get(String(id));
1719
1743
  const result = analyze(record.doc);
1720
1744
  return json(uri, { design_id: record.design_id, revision: record.revision, ...analysisJson(result) });
1721
1745
  });
1722
1746
  server.registerResource("design", new ResourceTemplate("tensorcad://designs/{id}", {
1723
- list: () => ({
1724
- resources: store.list().map((d) => ({
1747
+ list: async () => ({
1748
+ resources: (await store.list()).map((d) => ({
1725
1749
  uri: `tensorcad://designs/${d.design_id}`,
1726
1750
  name: d.name,
1727
1751
  title: `${d.name} (revision ${d.revision})`,
@@ -1734,8 +1758,8 @@ function registerResources(server, store) {
1734
1758
  title: "Design document",
1735
1759
  description: "The literal .tensorcad.json document, with a compact outline beside it.",
1736
1760
  mimeType: JSON_MIME
1737
- }, (uri, { id }) => {
1738
- const record = store.get(String(id));
1761
+ }, async (uri, { id }) => {
1762
+ const record = await store.get(String(id));
1739
1763
  return json(uri, {
1740
1764
  design_id: record.design_id,
1741
1765
  revision: record.revision,
@@ -1749,7 +1773,7 @@ function registerResources(server, store) {
1749
1773
  title: "Block catalog",
1750
1774
  description: "Every block type with its parameter schema, port patterns and documentation. " + "Primitives carry the formulas; composites expand into primitives; repeat is the only container.",
1751
1775
  mimeType: JSON_MIME
1752
- }, (uri) => {
1776
+ }, async (uri) => {
1753
1777
  const blocks = allCatalogEntries();
1754
1778
  return json(uri, {
1755
1779
  count: blocks.length,
@@ -1758,7 +1782,7 @@ function registerResources(server, store) {
1758
1782
  });
1759
1783
  });
1760
1784
  server.registerResource("catalog-block", new ResourceTemplate("tensorcad://catalog/{type}", {
1761
- list: () => ({
1785
+ list: async () => ({
1762
1786
  resources: Object.keys(CATALOG).sort().map((type) => ({
1763
1787
  uri: `tensorcad://catalog/${type}`,
1764
1788
  name: type,
@@ -1793,12 +1817,10 @@ function registerResources(server, store) {
1793
1817
  }));
1794
1818
  }
1795
1819
  function completeDesignId(store) {
1796
- return (value) => store.list().map((d) => d.design_id).filter((id) => id.startsWith(value));
1820
+ return async (value) => (await store.list()).map((d) => d.design_id).filter((id) => id.startsWith(value));
1797
1821
  }
1798
1822
 
1799
1823
  // packages/mcp/src/tools.ts
1800
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1801
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
1802
1824
  import * as z3 from "zod";
1803
1825
  import { formatBytes as formatBytes2, formatCount as formatCount2 } from "@tensor-cad/engine";
1804
1826
  import {
@@ -1875,7 +1897,7 @@ function toAnalysisOptions(input) {
1875
1897
  var READ = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
1876
1898
  var WRITE = { readOnlyHint: false, idempotentHint: false, openWorldHint: false };
1877
1899
  var DESTRUCTIVE = { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false };
1878
- function registerTools(server, store) {
1900
+ function registerTools(server, store, artifacts) {
1879
1901
  server.registerTool("tensorcad_list_designs", {
1880
1902
  title: "List designs",
1881
1903
  description: "List the designs this server has open, the built-in reference architectures you can start from, " + "and the .tensorcad.json files it can see on disk. Start here when you do not already hold a design_id.",
@@ -1894,7 +1916,7 @@ function registerTools(server, store) {
1894
1916
  }),
1895
1917
  annotations: { ...READ, title: "List designs" }
1896
1918
  }, async ({ include_files }) => guard(async () => {
1897
- const designs = store.list();
1919
+ const designs = await store.list();
1898
1920
  const presets = PRESET_NAMES3.map((name) => {
1899
1921
  const doc = getPreset2(name);
1900
1922
  const p = { name };
@@ -1935,8 +1957,8 @@ ${designs.map((d) => ` ${d.design_id} ${d.name} rev ${d.revision}${d.dirty ?
1935
1957
  outline: Outline
1936
1958
  }),
1937
1959
  annotations: { ...WRITE, title: "New design" }
1938
- }, async (args) => guard(() => {
1939
- const record = store.create(args);
1960
+ }, async (args) => guard(async () => {
1961
+ const record = await store.create(args);
1940
1962
  const outline = outlineOf(record.doc);
1941
1963
  return ok(`${record.design_id} (revision ${record.revision})
1942
1964
 
@@ -2021,8 +2043,8 @@ ${formatCount2(report.analysis.params.total)} parameters, ` + `${report.counts.e
2021
2043
  document: z3.record(z3.string(), z3.unknown()).optional().describe("The literal design document.")
2022
2044
  }),
2023
2045
  annotations: { ...READ, title: "Get design" }
2024
- }, async ({ design_id, format }) => guard(() => {
2025
- const record = store.get(design_id);
2046
+ }, async ({ design_id, format }) => guard(async () => {
2047
+ const record = await store.get(design_id);
2026
2048
  const outline = outlineOf(record.doc);
2027
2049
  const mode = format ?? "outline";
2028
2050
  const base = {
@@ -2076,8 +2098,8 @@ ${outlineText(outline)}`, {
2076
2098
  children: z3.array(z3.string())
2077
2099
  }),
2078
2100
  annotations: { ...READ, title: "Get block" }
2079
- }, async ({ design_id, path }) => guard(() => {
2080
- const record = store.get(design_id);
2101
+ }, async ({ design_id, path }) => guard(async () => {
2102
+ const record = await store.get(design_id);
2081
2103
  const detail = blockDetail(record.doc, path);
2082
2104
  return ok(blockText(detail), {
2083
2105
  design_id: record.design_id,
@@ -2101,6 +2123,7 @@ ${outlineText(outline)}`, {
2101
2123
  type: z3.string(),
2102
2124
  kind: z3.string(),
2103
2125
  category: z3.string(),
2126
+ name: z3.string().describe("What a drawing calls this block."),
2104
2127
  summary: z3.string(),
2105
2128
  formula: z3.string().optional(),
2106
2129
  refs: z3.array(z3.string()),
@@ -2117,7 +2140,7 @@ ${outlineText(outline)}`, {
2117
2140
  }))
2118
2141
  }),
2119
2142
  annotations: { ...READ, title: "Search catalog" }
2120
- }, async ({ query, category, kind, limit }) => guard(() => {
2143
+ }, async ({ query, category, kind, limit }) => guard(async () => {
2121
2144
  const all = allCatalogEntries();
2122
2145
  const q = query?.toLowerCase();
2123
2146
  const matched = all.filter((e) => {
@@ -2127,7 +2150,7 @@ ${outlineText(outline)}`, {
2127
2150
  return false;
2128
2151
  if (!q)
2129
2152
  return true;
2130
- const hay = `${e.type} ${e.category} ${e.summary} ${e.formula ?? ""}`.toLowerCase();
2153
+ const hay = `${e.type} ${e.name} ${e.category} ${e.summary} ${e.formula ?? ""}`.toLowerCase();
2131
2154
  return hay.includes(q);
2132
2155
  });
2133
2156
  const blocks = matched.slice(0, limit ?? 20);
@@ -2161,10 +2184,10 @@ ${catalogText(blocks)}`, {
2161
2184
  validation: ValidationSummary
2162
2185
  }),
2163
2186
  annotations: { ...WRITE, title: "Apply edits" }
2164
- }, async ({ design_id, expected_revision, ops }) => guard(() => {
2165
- const before = store.get(design_id);
2187
+ }, async ({ design_id, expected_revision, ops }) => guard(async () => {
2188
+ const before = await store.get(design_id);
2166
2189
  const paramsBefore = outlineOf(before.doc).params_total;
2167
- const outcome = store.apply(design_id, ops, expected_revision);
2190
+ const outcome = await store.apply(design_id, ops, expected_revision);
2168
2191
  const report = validate2(outcome.record.doc);
2169
2192
  const total = report.analysis.params.total;
2170
2193
  const delta = total - paramsBefore;
@@ -2207,8 +2230,8 @@ ${catalogText(blocks)}`, {
2207
2230
  params_total: z3.number()
2208
2231
  }),
2209
2232
  annotations: { ...READ, title: "Validate design" }
2210
- }, async ({ design_id, severity, ...rest }) => guard(() => {
2211
- const record = store.get(design_id);
2233
+ }, async ({ design_id, severity, ...rest }) => guard(async () => {
2234
+ const record = await store.get(design_id);
2212
2235
  const report = validate2(record.doc, toAnalysisOptions(rest));
2213
2236
  const rank = { error: 0, warning: 1, info: 2 };
2214
2237
  const findings = findingsJson(report).filter((f) => severity === undefined || rank[f.severity] <= rank[severity]);
@@ -2235,8 +2258,8 @@ ${catalogText(blocks)}`, {
2235
2258
  inputSchema: z3.object({ design_id: DESIGN_ID, ...analysisOptionsShape }),
2236
2259
  outputSchema: AnalysisOutput,
2237
2260
  annotations: { ...READ, title: "Analyze design" }
2238
- }, async ({ design_id, ...rest }) => guard(() => {
2239
- const record = store.get(design_id);
2261
+ }, async ({ design_id, ...rest }) => guard(async () => {
2262
+ const record = await store.get(design_id);
2240
2263
  const result = analyze2(record.doc, toAnalysisOptions(rest));
2241
2264
  return ok(analysisText(result), {
2242
2265
  design_id: record.design_id,
@@ -2269,30 +2292,36 @@ ${catalogText(blocks)}`, {
2269
2292
  }),
2270
2293
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Generate code" }
2271
2294
  }, async ({ design_id, class_name, include_smoke_test, out_dir }) => guard(async () => {
2272
- const record = store.get(design_id);
2295
+ const record = await store.get(design_id);
2273
2296
  const generated = generateTorch(record.doc, {
2274
2297
  ...class_name ? { className: class_name } : {},
2275
2298
  includeSmokeTest: include_smoke_test ?? false
2276
2299
  });
2277
- const root = out_dir ? isAbsolute2(out_dir) ? out_dir : resolve2(process.cwd(), out_dir) : undefined;
2278
2300
  const files = [];
2279
2301
  for (const file of generated.files) {
2280
- const bytes = Buffer.byteLength(file.contents, "utf8");
2302
+ const bytes = new TextEncoder().encode(file.contents).length;
2281
2303
  const lines = file.contents.split(`
2282
2304
  `).length;
2283
- if (root) {
2284
- const target = join3(root, file.path);
2285
- await mkdir3(dirname3(target), { recursive: true });
2286
- await writeFile3(target, file.contents, "utf8");
2287
- files.push({ path: file.path, bytes, lines, written_to: target });
2288
- } else {
2305
+ if (out_dir === undefined) {
2289
2306
  files.push({ path: file.path, bytes, lines, contents: file.contents });
2307
+ continue;
2290
2308
  }
2309
+ const written = await artifacts.write(out_dir, file.path, file.contents);
2310
+ files.push({
2311
+ path: file.path,
2312
+ bytes,
2313
+ lines,
2314
+ written_to: written.location,
2315
+ ...written.url ? { url: written.url } : {}
2316
+ });
2291
2317
  }
2292
- const text = root ? [`wrote ${files.length} file(s) to ${root}`, ...files.map((f) => ` ${f.path} ${f.bytes} bytes`)].join(`
2293
- `) : generated.files.map((f) => `# ${f.path}
2318
+ const text = out_dir === undefined ? generated.files.map((f) => `# ${f.path}
2294
2319
  ${f.contents}`).join(`
2295
2320
 
2321
+ `) : [
2322
+ `wrote ${files.length} file(s) to ${artifacts.label}`,
2323
+ ...files.map((f) => ` ${f.path} ${f.bytes} bytes ${f.url ?? f.written_to}`)
2324
+ ].join(`
2296
2325
  `);
2297
2326
  return ok(generated.warnings.length > 0 ? `${text}
2298
2327
 
@@ -2301,8 +2330,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2301
2330
  `)}` : text, {
2302
2331
  design_id: record.design_id,
2303
2332
  revision: record.revision,
2304
- wrote: Boolean(root),
2305
- ...root ? { out_dir: root } : {},
2333
+ wrote: out_dir !== undefined,
2334
+ ...out_dir !== undefined ? { out_dir } : {},
2306
2335
  files,
2307
2336
  warnings: generated.warnings
2308
2337
  });
@@ -2328,12 +2357,12 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2328
2357
  }))
2329
2358
  }),
2330
2359
  annotations: { ...WRITE, title: "Checkpoint design" }
2331
- }, async ({ design_id, label }) => guard(() => {
2332
- const info = store.checkpoint(design_id, label);
2360
+ }, async ({ design_id, label }) => guard(async () => {
2361
+ const info = await store.checkpoint(design_id, label);
2333
2362
  return ok(`${info.checkpoint_id} at revision ${info.revision}: ${info.label}`, {
2334
2363
  design_id,
2335
2364
  ...info,
2336
- checkpoints: store.checkpoints(design_id)
2365
+ checkpoints: await store.checkpoints(design_id)
2337
2366
  });
2338
2367
  }));
2339
2368
  server.registerTool("tensorcad_restore", {
@@ -2352,8 +2381,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2352
2381
  validation: ValidationSummary
2353
2382
  }),
2354
2383
  annotations: { ...DESTRUCTIVE, title: "Restore design" }
2355
- }, async ({ design_id, checkpoint_id }) => guard(() => {
2356
- const { record, restoredFrom } = store.restore(design_id, checkpoint_id);
2384
+ }, async ({ design_id, checkpoint_id }) => guard(async () => {
2385
+ const { record, restoredFrom } = await store.restore(design_id, checkpoint_id);
2357
2386
  const report = validate2(record.doc);
2358
2387
  return ok(`${record.design_id} restored from ${restoredFrom}; now revision ${record.revision}, ` + `${formatCount2(report.analysis.params.total)} parameters`, {
2359
2388
  design_id: record.design_id,
@@ -2396,8 +2425,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2396
2425
  })
2397
2426
  }),
2398
2427
  annotations: { ...READ, title: "Explain a block" }
2399
- }, async ({ design_id, path, ...rest }) => guard(() => {
2400
- const record = store.get(design_id);
2428
+ }, async ({ design_id, path, ...rest }) => guard(async () => {
2429
+ const record = await store.get(design_id);
2401
2430
  const e = explain(record.doc, path, toAnalysisOptions(rest));
2402
2431
  const lines = [
2403
2432
  `${path} ${e.type} (${e.kind})`,
@@ -2458,8 +2487,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2458
2487
  notes: z3.array(z3.string())
2459
2488
  }),
2460
2489
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Scale a design" }
2461
- }, async ({ design_id, target_params, target_basis, vocab, tie_head, keep_depth }) => guard(() => {
2462
- const record = store.get(design_id);
2490
+ }, async ({ design_id, target_params, target_basis, vocab, tie_head, keep_depth }) => guard(async () => {
2491
+ const record = await store.get(design_id);
2463
2492
  const result = scaleDesign(record.doc, {
2464
2493
  targetParams: target_params,
2465
2494
  ...target_basis ? { targetBasis: target_basis } : {},
@@ -2467,7 +2496,7 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2467
2496
  ...tie_head !== undefined ? { tieHead: tie_head } : {},
2468
2497
  ...keep_depth !== undefined ? { keepDepth: keep_depth } : {}
2469
2498
  });
2470
- const saved = store.adopt(result.doc);
2499
+ const saved = await store.adopt(result.doc);
2471
2500
  const changes = Object.entries(result.changes).map(([symbol, c]) => ({
2472
2501
  symbol,
2473
2502
  from: c.from,
@@ -2520,28 +2549,32 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2520
2549
  notes: z3.array(z3.string())
2521
2550
  }),
2522
2551
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Build a width ladder" }
2523
- }, async ({ design_id, widths, base_width }) => guard(() => {
2524
- const record = store.get(design_id);
2552
+ }, async ({ design_id, widths, base_width }) => guard(async () => {
2553
+ const record = await store.get(design_id);
2525
2554
  const ladder = mupLadder(record.doc, {
2526
2555
  ...widths ? { widths } : {},
2527
2556
  ...base_width !== undefined ? { baseWidth: base_width } : {}
2528
2557
  });
2529
- const rungs = ladder.rungs.map((rung) => ({
2530
- design_id: store.adopt(rung.doc).design_id,
2531
- width: rung.width,
2532
- multiplier: rung.multiplier,
2533
- heads: rung.heads,
2534
- params: rung.params,
2535
- base: rung.base,
2536
- scaling: rung.scaling.map((s) => ({
2537
- class: s.class,
2538
- init_std: s.initStd,
2539
- adam_lr: s.adamLr,
2540
- paths: s.paths,
2541
- why: s.why
2542
- })),
2543
- notes: rung.notes
2544
- }));
2558
+ const rungs = [];
2559
+ for (const rung of ladder.rungs) {
2560
+ const adopted = await store.adopt(rung.doc);
2561
+ rungs.push({
2562
+ design_id: adopted.design_id,
2563
+ width: rung.width,
2564
+ multiplier: rung.multiplier,
2565
+ heads: rung.heads,
2566
+ params: rung.params,
2567
+ base: rung.base,
2568
+ scaling: rung.scaling.map((s) => ({
2569
+ class: s.class,
2570
+ init_std: s.initStd,
2571
+ adam_lr: s.adamLr,
2572
+ paths: s.paths,
2573
+ why: s.why
2574
+ })),
2575
+ notes: rung.notes
2576
+ });
2577
+ }
2545
2578
  const text = [
2546
2579
  `${record.doc.meta.name} laddered by ${ladder.widthSymbol}, tuned at ${ladder.baseWidth}, ` + `heads of ${ladder.headDim} throughout`,
2547
2580
  ...rungs.map((r) => ` ${r.base ? "base " : " "}${r.width} wide, ${r.heads} heads, ${formatCount2(r.params)}` + ` ${r.scaling.filter((s) => s.class !== "input").map((s) => `${s.class} init x${s.init_std.toPrecision(4)} rate x${s.adam_lr.toPrecision(4)}`).join(", ")}`),
@@ -2590,8 +2623,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2590
2623
  notes: z3.array(z3.string())
2591
2624
  }),
2592
2625
  annotations: { ...READ, title: "Plan a cluster" }
2593
- }, async ({ design_id, gpus, gpus_per_node, headroom, limit, ...rest }) => guard(() => {
2594
- const record = store.get(design_id);
2626
+ }, async ({ design_id, gpus, gpus_per_node, headroom, limit, ...rest }) => guard(async () => {
2627
+ const record = await store.get(design_id);
2595
2628
  const result = planCluster(record.doc, toAnalysisOptions(rest), {
2596
2629
  gpus,
2597
2630
  ...gpus_per_node !== undefined ? { gpusPerNode: gpus_per_node } : {},
@@ -2667,9 +2700,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2667
2700
  }))
2668
2701
  }),
2669
2702
  annotations: { ...READ, title: "Compare two designs" }
2670
- }, async ({ a, b, ...rest }) => guard(() => {
2671
- const left = store.get(a);
2672
- const right = store.get(b);
2703
+ }, async ({ a, b, ...rest }) => guard(async () => {
2704
+ const left = await store.get(a);
2705
+ const right = await store.get(b);
2673
2706
  const d = diffDesigns(left.doc, right.doc, toAnalysisOptions(rest));
2674
2707
  const brief = (v) => {
2675
2708
  if (v === undefined || v === null)
@@ -2735,9 +2768,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2735
2768
  warnings: z3.array(z3.string())
2736
2769
  }),
2737
2770
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Import a config" }
2738
- }, async ({ config, name }) => guard(() => {
2771
+ }, async ({ config, name }) => guard(async () => {
2739
2772
  const result = importHfConfig(config, name);
2740
- const record = store.adopt(result.doc);
2773
+ const record = await store.adopt(result.doc);
2741
2774
  const total = analyze2(result.doc).params.total;
2742
2775
  const text = [
2743
2776
  `${result.doc.meta.name}: ${formatCount2(total)} parameters`,
@@ -2800,13 +2833,14 @@ var INSTRUCTIONS = [
2800
2833
  ].join(`
2801
2834
  `);
2802
2835
  function createServer2(options = {}) {
2803
- const { store: given, bridge, ...storeOptions } = options;
2836
+ const { store: given, bridge, artifacts: givenArtifacts, ...storeOptions } = options;
2804
2837
  const store = given ?? new FileStore(storeOptions);
2838
+ const artifacts = givenArtifacts ?? new DiskSink(storeOptions.root ?? process.cwd());
2805
2839
  const server = new McpServer2({ name: SERVER_NAME, version: SERVER_VERSION }, {
2806
2840
  capabilities: { tools: {}, resources: {}, prompts: {}, completions: {} },
2807
2841
  instructions: INSTRUCTIONS
2808
2842
  });
2809
- registerTools(server, store);
2843
+ registerTools(server, store, artifacts);
2810
2844
  registerResources(server, store);
2811
2845
  registerPrompts(server);
2812
2846
  if (bridge)
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Generated files, written to a directory.
3
+ *
4
+ * What the tool did inline until there was a second kind of destination. It is
5
+ * here rather than in `tools.ts` because it is the only part of that tool that
6
+ * cannot run in a Worker — `mkdir` and `writeFile` do not exist there — and
7
+ * keeping it in one small module is what lets the hosted build substitute
8
+ * something else rather than fork the tool.
9
+ */
10
+ import type { ArtifactSink, WrittenArtifact } from "../artifacts.js";
11
+ export declare class DiskSink implements ArtifactSink {
12
+ private readonly root;
13
+ readonly label = "disk";
14
+ constructor(root: string);
15
+ write(prefix: string, path: string, contents: string): Promise<WrittenArtifact>;
16
+ }