@tensor-cad/mcp 0.1.2 → 0.1.3

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.3",
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";
@@ -1698,8 +1718,8 @@ function registerResources(server, store) {
1698
1718
  title: "Design rule report",
1699
1719
  description: "Every finding for a design: shape errors, memory fit, kernel constraints, Chinchilla sanity.",
1700
1720
  mimeType: JSON_MIME
1701
- }, (uri, { id }) => {
1702
- const record = store.get(String(id));
1721
+ }, async (uri, { id }) => {
1722
+ const record = await store.get(String(id));
1703
1723
  const report = validate(record.doc);
1704
1724
  return json(uri, {
1705
1725
  design_id: record.design_id,
@@ -1714,14 +1734,14 @@ function registerResources(server, store) {
1714
1734
  title: "Design analysis",
1715
1735
  description: "Parameters, FLOPs, KV cache, memory, throughput and cost at the document's own defaults.",
1716
1736
  mimeType: JSON_MIME
1717
- }, (uri, { id }) => {
1718
- const record = store.get(String(id));
1737
+ }, async (uri, { id }) => {
1738
+ const record = await store.get(String(id));
1719
1739
  const result = analyze(record.doc);
1720
1740
  return json(uri, { design_id: record.design_id, revision: record.revision, ...analysisJson(result) });
1721
1741
  });
1722
1742
  server.registerResource("design", new ResourceTemplate("tensorcad://designs/{id}", {
1723
- list: () => ({
1724
- resources: store.list().map((d) => ({
1743
+ list: async () => ({
1744
+ resources: (await store.list()).map((d) => ({
1725
1745
  uri: `tensorcad://designs/${d.design_id}`,
1726
1746
  name: d.name,
1727
1747
  title: `${d.name} (revision ${d.revision})`,
@@ -1734,8 +1754,8 @@ function registerResources(server, store) {
1734
1754
  title: "Design document",
1735
1755
  description: "The literal .tensorcad.json document, with a compact outline beside it.",
1736
1756
  mimeType: JSON_MIME
1737
- }, (uri, { id }) => {
1738
- const record = store.get(String(id));
1757
+ }, async (uri, { id }) => {
1758
+ const record = await store.get(String(id));
1739
1759
  return json(uri, {
1740
1760
  design_id: record.design_id,
1741
1761
  revision: record.revision,
@@ -1749,7 +1769,7 @@ function registerResources(server, store) {
1749
1769
  title: "Block catalog",
1750
1770
  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
1771
  mimeType: JSON_MIME
1752
- }, (uri) => {
1772
+ }, async (uri) => {
1753
1773
  const blocks = allCatalogEntries();
1754
1774
  return json(uri, {
1755
1775
  count: blocks.length,
@@ -1758,7 +1778,7 @@ function registerResources(server, store) {
1758
1778
  });
1759
1779
  });
1760
1780
  server.registerResource("catalog-block", new ResourceTemplate("tensorcad://catalog/{type}", {
1761
- list: () => ({
1781
+ list: async () => ({
1762
1782
  resources: Object.keys(CATALOG).sort().map((type) => ({
1763
1783
  uri: `tensorcad://catalog/${type}`,
1764
1784
  name: type,
@@ -1793,12 +1813,10 @@ function registerResources(server, store) {
1793
1813
  }));
1794
1814
  }
1795
1815
  function completeDesignId(store) {
1796
- return (value) => store.list().map((d) => d.design_id).filter((id) => id.startsWith(value));
1816
+ return async (value) => (await store.list()).map((d) => d.design_id).filter((id) => id.startsWith(value));
1797
1817
  }
1798
1818
 
1799
1819
  // 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
1820
  import * as z3 from "zod";
1803
1821
  import { formatBytes as formatBytes2, formatCount as formatCount2 } from "@tensor-cad/engine";
1804
1822
  import {
@@ -1875,7 +1893,7 @@ function toAnalysisOptions(input) {
1875
1893
  var READ = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
1876
1894
  var WRITE = { readOnlyHint: false, idempotentHint: false, openWorldHint: false };
1877
1895
  var DESTRUCTIVE = { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false };
1878
- function registerTools(server, store) {
1896
+ function registerTools(server, store, artifacts) {
1879
1897
  server.registerTool("tensorcad_list_designs", {
1880
1898
  title: "List designs",
1881
1899
  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 +1912,7 @@ function registerTools(server, store) {
1894
1912
  }),
1895
1913
  annotations: { ...READ, title: "List designs" }
1896
1914
  }, async ({ include_files }) => guard(async () => {
1897
- const designs = store.list();
1915
+ const designs = await store.list();
1898
1916
  const presets = PRESET_NAMES3.map((name) => {
1899
1917
  const doc = getPreset2(name);
1900
1918
  const p = { name };
@@ -1935,8 +1953,8 @@ ${designs.map((d) => ` ${d.design_id} ${d.name} rev ${d.revision}${d.dirty ?
1935
1953
  outline: Outline
1936
1954
  }),
1937
1955
  annotations: { ...WRITE, title: "New design" }
1938
- }, async (args) => guard(() => {
1939
- const record = store.create(args);
1956
+ }, async (args) => guard(async () => {
1957
+ const record = await store.create(args);
1940
1958
  const outline = outlineOf(record.doc);
1941
1959
  return ok(`${record.design_id} (revision ${record.revision})
1942
1960
 
@@ -2021,8 +2039,8 @@ ${formatCount2(report.analysis.params.total)} parameters, ` + `${report.counts.e
2021
2039
  document: z3.record(z3.string(), z3.unknown()).optional().describe("The literal design document.")
2022
2040
  }),
2023
2041
  annotations: { ...READ, title: "Get design" }
2024
- }, async ({ design_id, format }) => guard(() => {
2025
- const record = store.get(design_id);
2042
+ }, async ({ design_id, format }) => guard(async () => {
2043
+ const record = await store.get(design_id);
2026
2044
  const outline = outlineOf(record.doc);
2027
2045
  const mode = format ?? "outline";
2028
2046
  const base = {
@@ -2076,8 +2094,8 @@ ${outlineText(outline)}`, {
2076
2094
  children: z3.array(z3.string())
2077
2095
  }),
2078
2096
  annotations: { ...READ, title: "Get block" }
2079
- }, async ({ design_id, path }) => guard(() => {
2080
- const record = store.get(design_id);
2097
+ }, async ({ design_id, path }) => guard(async () => {
2098
+ const record = await store.get(design_id);
2081
2099
  const detail = blockDetail(record.doc, path);
2082
2100
  return ok(blockText(detail), {
2083
2101
  design_id: record.design_id,
@@ -2117,7 +2135,7 @@ ${outlineText(outline)}`, {
2117
2135
  }))
2118
2136
  }),
2119
2137
  annotations: { ...READ, title: "Search catalog" }
2120
- }, async ({ query, category, kind, limit }) => guard(() => {
2138
+ }, async ({ query, category, kind, limit }) => guard(async () => {
2121
2139
  const all = allCatalogEntries();
2122
2140
  const q = query?.toLowerCase();
2123
2141
  const matched = all.filter((e) => {
@@ -2161,10 +2179,10 @@ ${catalogText(blocks)}`, {
2161
2179
  validation: ValidationSummary
2162
2180
  }),
2163
2181
  annotations: { ...WRITE, title: "Apply edits" }
2164
- }, async ({ design_id, expected_revision, ops }) => guard(() => {
2165
- const before = store.get(design_id);
2182
+ }, async ({ design_id, expected_revision, ops }) => guard(async () => {
2183
+ const before = await store.get(design_id);
2166
2184
  const paramsBefore = outlineOf(before.doc).params_total;
2167
- const outcome = store.apply(design_id, ops, expected_revision);
2185
+ const outcome = await store.apply(design_id, ops, expected_revision);
2168
2186
  const report = validate2(outcome.record.doc);
2169
2187
  const total = report.analysis.params.total;
2170
2188
  const delta = total - paramsBefore;
@@ -2207,8 +2225,8 @@ ${catalogText(blocks)}`, {
2207
2225
  params_total: z3.number()
2208
2226
  }),
2209
2227
  annotations: { ...READ, title: "Validate design" }
2210
- }, async ({ design_id, severity, ...rest }) => guard(() => {
2211
- const record = store.get(design_id);
2228
+ }, async ({ design_id, severity, ...rest }) => guard(async () => {
2229
+ const record = await store.get(design_id);
2212
2230
  const report = validate2(record.doc, toAnalysisOptions(rest));
2213
2231
  const rank = { error: 0, warning: 1, info: 2 };
2214
2232
  const findings = findingsJson(report).filter((f) => severity === undefined || rank[f.severity] <= rank[severity]);
@@ -2235,8 +2253,8 @@ ${catalogText(blocks)}`, {
2235
2253
  inputSchema: z3.object({ design_id: DESIGN_ID, ...analysisOptionsShape }),
2236
2254
  outputSchema: AnalysisOutput,
2237
2255
  annotations: { ...READ, title: "Analyze design" }
2238
- }, async ({ design_id, ...rest }) => guard(() => {
2239
- const record = store.get(design_id);
2256
+ }, async ({ design_id, ...rest }) => guard(async () => {
2257
+ const record = await store.get(design_id);
2240
2258
  const result = analyze2(record.doc, toAnalysisOptions(rest));
2241
2259
  return ok(analysisText(result), {
2242
2260
  design_id: record.design_id,
@@ -2269,30 +2287,36 @@ ${catalogText(blocks)}`, {
2269
2287
  }),
2270
2288
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Generate code" }
2271
2289
  }, async ({ design_id, class_name, include_smoke_test, out_dir }) => guard(async () => {
2272
- const record = store.get(design_id);
2290
+ const record = await store.get(design_id);
2273
2291
  const generated = generateTorch(record.doc, {
2274
2292
  ...class_name ? { className: class_name } : {},
2275
2293
  includeSmokeTest: include_smoke_test ?? false
2276
2294
  });
2277
- const root = out_dir ? isAbsolute2(out_dir) ? out_dir : resolve2(process.cwd(), out_dir) : undefined;
2278
2295
  const files = [];
2279
2296
  for (const file of generated.files) {
2280
- const bytes = Buffer.byteLength(file.contents, "utf8");
2297
+ const bytes = new TextEncoder().encode(file.contents).length;
2281
2298
  const lines = file.contents.split(`
2282
2299
  `).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 {
2300
+ if (out_dir === undefined) {
2289
2301
  files.push({ path: file.path, bytes, lines, contents: file.contents });
2302
+ continue;
2290
2303
  }
2304
+ const written = await artifacts.write(out_dir, file.path, file.contents);
2305
+ files.push({
2306
+ path: file.path,
2307
+ bytes,
2308
+ lines,
2309
+ written_to: written.location,
2310
+ ...written.url ? { url: written.url } : {}
2311
+ });
2291
2312
  }
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}
2313
+ const text = out_dir === undefined ? generated.files.map((f) => `# ${f.path}
2294
2314
  ${f.contents}`).join(`
2295
2315
 
2316
+ `) : [
2317
+ `wrote ${files.length} file(s) to ${artifacts.label}`,
2318
+ ...files.map((f) => ` ${f.path} ${f.bytes} bytes ${f.url ?? f.written_to}`)
2319
+ ].join(`
2296
2320
  `);
2297
2321
  return ok(generated.warnings.length > 0 ? `${text}
2298
2322
 
@@ -2301,8 +2325,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2301
2325
  `)}` : text, {
2302
2326
  design_id: record.design_id,
2303
2327
  revision: record.revision,
2304
- wrote: Boolean(root),
2305
- ...root ? { out_dir: root } : {},
2328
+ wrote: out_dir !== undefined,
2329
+ ...out_dir !== undefined ? { out_dir } : {},
2306
2330
  files,
2307
2331
  warnings: generated.warnings
2308
2332
  });
@@ -2328,12 +2352,12 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2328
2352
  }))
2329
2353
  }),
2330
2354
  annotations: { ...WRITE, title: "Checkpoint design" }
2331
- }, async ({ design_id, label }) => guard(() => {
2332
- const info = store.checkpoint(design_id, label);
2355
+ }, async ({ design_id, label }) => guard(async () => {
2356
+ const info = await store.checkpoint(design_id, label);
2333
2357
  return ok(`${info.checkpoint_id} at revision ${info.revision}: ${info.label}`, {
2334
2358
  design_id,
2335
2359
  ...info,
2336
- checkpoints: store.checkpoints(design_id)
2360
+ checkpoints: await store.checkpoints(design_id)
2337
2361
  });
2338
2362
  }));
2339
2363
  server.registerTool("tensorcad_restore", {
@@ -2352,8 +2376,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2352
2376
  validation: ValidationSummary
2353
2377
  }),
2354
2378
  annotations: { ...DESTRUCTIVE, title: "Restore design" }
2355
- }, async ({ design_id, checkpoint_id }) => guard(() => {
2356
- const { record, restoredFrom } = store.restore(design_id, checkpoint_id);
2379
+ }, async ({ design_id, checkpoint_id }) => guard(async () => {
2380
+ const { record, restoredFrom } = await store.restore(design_id, checkpoint_id);
2357
2381
  const report = validate2(record.doc);
2358
2382
  return ok(`${record.design_id} restored from ${restoredFrom}; now revision ${record.revision}, ` + `${formatCount2(report.analysis.params.total)} parameters`, {
2359
2383
  design_id: record.design_id,
@@ -2396,8 +2420,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2396
2420
  })
2397
2421
  }),
2398
2422
  annotations: { ...READ, title: "Explain a block" }
2399
- }, async ({ design_id, path, ...rest }) => guard(() => {
2400
- const record = store.get(design_id);
2423
+ }, async ({ design_id, path, ...rest }) => guard(async () => {
2424
+ const record = await store.get(design_id);
2401
2425
  const e = explain(record.doc, path, toAnalysisOptions(rest));
2402
2426
  const lines = [
2403
2427
  `${path} ${e.type} (${e.kind})`,
@@ -2458,8 +2482,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2458
2482
  notes: z3.array(z3.string())
2459
2483
  }),
2460
2484
  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);
2485
+ }, async ({ design_id, target_params, target_basis, vocab, tie_head, keep_depth }) => guard(async () => {
2486
+ const record = await store.get(design_id);
2463
2487
  const result = scaleDesign(record.doc, {
2464
2488
  targetParams: target_params,
2465
2489
  ...target_basis ? { targetBasis: target_basis } : {},
@@ -2467,7 +2491,7 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2467
2491
  ...tie_head !== undefined ? { tieHead: tie_head } : {},
2468
2492
  ...keep_depth !== undefined ? { keepDepth: keep_depth } : {}
2469
2493
  });
2470
- const saved = store.adopt(result.doc);
2494
+ const saved = await store.adopt(result.doc);
2471
2495
  const changes = Object.entries(result.changes).map(([symbol, c]) => ({
2472
2496
  symbol,
2473
2497
  from: c.from,
@@ -2520,28 +2544,32 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2520
2544
  notes: z3.array(z3.string())
2521
2545
  }),
2522
2546
  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);
2547
+ }, async ({ design_id, widths, base_width }) => guard(async () => {
2548
+ const record = await store.get(design_id);
2525
2549
  const ladder = mupLadder(record.doc, {
2526
2550
  ...widths ? { widths } : {},
2527
2551
  ...base_width !== undefined ? { baseWidth: base_width } : {}
2528
2552
  });
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
- }));
2553
+ const rungs = [];
2554
+ for (const rung of ladder.rungs) {
2555
+ const adopted = await store.adopt(rung.doc);
2556
+ rungs.push({
2557
+ design_id: adopted.design_id,
2558
+ width: rung.width,
2559
+ multiplier: rung.multiplier,
2560
+ heads: rung.heads,
2561
+ params: rung.params,
2562
+ base: rung.base,
2563
+ scaling: rung.scaling.map((s) => ({
2564
+ class: s.class,
2565
+ init_std: s.initStd,
2566
+ adam_lr: s.adamLr,
2567
+ paths: s.paths,
2568
+ why: s.why
2569
+ })),
2570
+ notes: rung.notes
2571
+ });
2572
+ }
2545
2573
  const text = [
2546
2574
  `${record.doc.meta.name} laddered by ${ladder.widthSymbol}, tuned at ${ladder.baseWidth}, ` + `heads of ${ladder.headDim} throughout`,
2547
2575
  ...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 +2618,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2590
2618
  notes: z3.array(z3.string())
2591
2619
  }),
2592
2620
  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);
2621
+ }, async ({ design_id, gpus, gpus_per_node, headroom, limit, ...rest }) => guard(async () => {
2622
+ const record = await store.get(design_id);
2595
2623
  const result = planCluster(record.doc, toAnalysisOptions(rest), {
2596
2624
  gpus,
2597
2625
  ...gpus_per_node !== undefined ? { gpusPerNode: gpus_per_node } : {},
@@ -2667,9 +2695,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2667
2695
  }))
2668
2696
  }),
2669
2697
  annotations: { ...READ, title: "Compare two designs" }
2670
- }, async ({ a, b, ...rest }) => guard(() => {
2671
- const left = store.get(a);
2672
- const right = store.get(b);
2698
+ }, async ({ a, b, ...rest }) => guard(async () => {
2699
+ const left = await store.get(a);
2700
+ const right = await store.get(b);
2673
2701
  const d = diffDesigns(left.doc, right.doc, toAnalysisOptions(rest));
2674
2702
  const brief = (v) => {
2675
2703
  if (v === undefined || v === null)
@@ -2735,9 +2763,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2735
2763
  warnings: z3.array(z3.string())
2736
2764
  }),
2737
2765
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Import a config" }
2738
- }, async ({ config, name }) => guard(() => {
2766
+ }, async ({ config, name }) => guard(async () => {
2739
2767
  const result = importHfConfig(config, name);
2740
- const record = store.adopt(result.doc);
2768
+ const record = await store.adopt(result.doc);
2741
2769
  const total = analyze2(result.doc).params.total;
2742
2770
  const text = [
2743
2771
  `${result.doc.meta.name}: ${formatCount2(total)} parameters`,
@@ -2800,13 +2828,14 @@ var INSTRUCTIONS = [
2800
2828
  ].join(`
2801
2829
  `);
2802
2830
  function createServer2(options = {}) {
2803
- const { store: given, bridge, ...storeOptions } = options;
2831
+ const { store: given, bridge, artifacts: givenArtifacts, ...storeOptions } = options;
2804
2832
  const store = given ?? new FileStore(storeOptions);
2833
+ const artifacts = givenArtifacts ?? new DiskSink(storeOptions.root ?? process.cwd());
2805
2834
  const server = new McpServer2({ name: SERVER_NAME, version: SERVER_VERSION }, {
2806
2835
  capabilities: { tools: {}, resources: {}, prompts: {}, completions: {} },
2807
2836
  instructions: INSTRUCTIONS
2808
2837
  });
2809
- registerTools(server, store);
2838
+ registerTools(server, store, artifacts);
2810
2839
  registerResources(server, store);
2811
2840
  registerPrompts(server);
2812
2841
  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
+ }
@@ -31,26 +31,26 @@ export declare class FileStore implements DocumentStore {
31
31
  * and it is reported where a server's diagnostics go.
32
32
  */
33
33
  private emit;
34
- list(): DesignSummary[];
34
+ list(): Promise<DesignSummary[]>;
35
35
  listFiles(): Promise<string[]>;
36
- get(id: string): DesignRecord;
37
- create(options: NewDesignOptions): DesignRecord;
38
- adopt(doc: Doc): DesignRecord;
36
+ get(id: string): Promise<DesignRecord>;
37
+ create(options: NewDesignOptions): Promise<DesignRecord>;
38
+ adopt(doc: Doc): Promise<DesignRecord>;
39
39
  open(path: string): Promise<DesignRecord>;
40
40
  private register;
41
- apply(id: string, ops: Op[], expectedRevision?: number): ApplyOutcome;
42
- replace(id: string, doc: Doc, expectedRevision?: number): ApplyOutcome;
41
+ apply(id: string, ops: Op[], expectedRevision?: number): Promise<ApplyOutcome>;
42
+ replace(id: string, doc: Doc, expectedRevision?: number): Promise<ApplyOutcome>;
43
43
  save(id: string, path?: string): Promise<{
44
44
  record: DesignRecord;
45
45
  path: string;
46
46
  bytes: number;
47
47
  }>;
48
- checkpoint(id: string, label?: string): CheckpointInfo;
49
- checkpoints(id: string): CheckpointInfo[];
50
- restore(id: string, checkpointId?: string): {
48
+ checkpoint(id: string, label?: string): Promise<CheckpointInfo>;
49
+ checkpoints(id: string): Promise<CheckpointInfo[]>;
50
+ restore(id: string, checkpointId?: string): Promise<{
51
51
  record: DesignRecord;
52
52
  restoredFrom: string;
53
- };
53
+ }>;
54
54
  private entry;
55
55
  private resolvePath;
56
56
  /** Whether a path exists, used by tools that want a friendlier message. */