@tensor-cad/mcp 0.1.1 → 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/serve.js CHANGED
@@ -310,7 +310,12 @@ Content-Length: 0\r
310
310
  setTimeout(() => socket.destroy(), 50).unref();
311
311
  return;
312
312
  }
313
- this.wss.handleUpgrade(req, socket, head, (ws) => this.attach(ws));
313
+ this.wss.handleUpgrade(req, socket, head, (ws) => {
314
+ this.attach(ws).catch((e) => {
315
+ this.log(`tensorcad bridge: could not greet a client: ${e.message}`);
316
+ this.send(ws, { type: "error", message: e.message });
317
+ });
318
+ });
314
319
  });
315
320
  }
316
321
  get port() {
@@ -384,14 +389,14 @@ Content-Length: 0\r
384
389
  }
385
390
  return;
386
391
  }
387
- attach(ws) {
392
+ async attach(ws) {
388
393
  this.send(ws, {
389
394
  type: "hello",
390
395
  protocol: BRIDGE_PROTOCOL,
391
396
  server: this.options.name,
392
397
  version: this.options.version,
393
398
  root: this.options.root,
394
- designs: this.options.store.list()
399
+ designs: await this.options.store.list()
395
400
  });
396
401
  ws.on("message", (raw) => {
397
402
  let message;
@@ -401,23 +406,21 @@ Content-Length: 0\r
401
406
  this.send(ws, { type: "error", message: `not JSON: ${e.message}` });
402
407
  return;
403
408
  }
404
- try {
405
- this.handle(ws, message);
406
- } catch (e) {
409
+ this.handle(ws, message).catch((e) => {
407
410
  this.send(ws, { type: "error", message: e.message, about: message?.type });
408
- }
411
+ });
409
412
  });
410
413
  }
411
- handle(ws, message) {
414
+ async handle(ws, message) {
412
415
  switch (message?.type) {
413
416
  case "publish": {
414
417
  const doc = asDocument(message.doc);
415
- const record = this.during(ws, () => this.options.store.adopt(doc));
418
+ const record = await this.during(ws, () => this.options.store.adopt(doc));
416
419
  this.send(ws, designMessage(record, "published"));
417
420
  return;
418
421
  }
419
422
  case "attach": {
420
- const record = this.options.store.get(message.design_id);
423
+ const record = await this.options.store.get(message.design_id);
421
424
  this.send(ws, designMessage(record, "requested"));
422
425
  return;
423
426
  }
@@ -425,12 +428,12 @@ Content-Length: 0\r
425
428
  case "replace": {
426
429
  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);
427
430
  try {
428
- const { record } = this.during(ws, write);
431
+ const { record } = await this.during(ws, write);
429
432
  this.send(ws, designMessage(record, message.type === "ops" ? "applied" : "replaced"));
430
433
  } catch (e) {
431
434
  if (e instanceof RevisionConflictError) {
432
435
  this.send(ws, { type: "error", message: e.message, about: message.type });
433
- this.send(ws, designMessage(this.options.store.get(message.design_id), "requested"));
436
+ this.send(ws, designMessage(await this.options.store.get(message.design_id), "requested"));
434
437
  return;
435
438
  }
436
439
  throw e;
@@ -460,13 +463,11 @@ Content-Length: 0\r
460
463
  }
461
464
  }
462
465
  }
463
- during(ws, work) {
466
+ async during(ws, work) {
464
467
  this.acting = ws;
465
- try {
466
- return work();
467
- } finally {
468
- this.acting = undefined;
469
- }
468
+ const running = work();
469
+ this.acting = undefined;
470
+ return await running;
470
471
  }
471
472
  send(ws, message) {
472
473
  if (ws.readyState !== ws.OPEN)
@@ -549,7 +550,7 @@ import { McpServer as McpServer2 } from "@modelcontextprotocol/server";
549
550
  // packages/mcp/package.json
550
551
  var package_default = {
551
552
  name: "@tensor-cad/mcp",
552
- version: "0.1.1",
553
+ version: "0.1.3",
553
554
  description: "Model Context Protocol server for TensorCAD: design, validate, analyze and generate LLM architectures from an agent",
554
555
  mcpName: "io.github.filip-pajalic/tensorcad",
555
556
  type: "module",
@@ -824,7 +825,7 @@ class FileStore {
824
825
  }
825
826
  }
826
827
  }
827
- list() {
828
+ async list() {
828
829
  return [...this.entries.values()].map((e) => summaryOf(e.record)).sort((a, b) => b.updated_at.localeCompare(a.updated_at));
829
830
  }
830
831
  async listFiles() {
@@ -850,13 +851,13 @@ class FileStore {
850
851
  await walk(this.root, this.depth);
851
852
  return out.sort();
852
853
  }
853
- get(id) {
854
+ async get(id) {
854
855
  const entry = this.entries.get(id);
855
856
  if (!entry)
856
857
  throw new UnknownDesignError(id, [...this.entries.keys()]);
857
858
  return entry.record;
858
859
  }
859
- create(options) {
860
+ async create(options) {
860
861
  let doc;
861
862
  let source;
862
863
  if (options.preset) {
@@ -873,7 +874,7 @@ class FileStore {
873
874
  doc.meta.name = options.name;
874
875
  return this.register(doc, source, undefined, true);
875
876
  }
876
- adopt(doc) {
877
+ async adopt(doc) {
877
878
  return this.register(doc, "derived", undefined, true);
878
879
  }
879
880
  async open(path) {
@@ -915,7 +916,7 @@ class FileStore {
915
916
  this.emit({ kind: "registered", record });
916
917
  return record;
917
918
  }
918
- apply(id, ops, expectedRevision) {
919
+ async apply(id, ops, expectedRevision) {
919
920
  const entry = this.entry(id);
920
921
  const { record } = entry;
921
922
  if (expectedRevision !== undefined && expectedRevision !== record.revision) {
@@ -933,7 +934,7 @@ class FileStore {
933
934
  this.emit({ kind: "applied", record, ops });
934
935
  return { record, applied, previousRevision };
935
936
  }
936
- replace(id, doc, expectedRevision) {
937
+ async replace(id, doc, expectedRevision) {
937
938
  const entry = this.entry(id);
938
939
  const { record } = entry;
939
940
  if (expectedRevision !== undefined && expectedRevision !== record.revision) {
@@ -950,7 +951,7 @@ class FileStore {
950
951
  return { record, applied: ["replaced the document"], previousRevision };
951
952
  }
952
953
  async save(id, path) {
953
- const record = this.get(id);
954
+ const record = await this.get(id);
954
955
  const target = path ? this.resolvePath(path) : record.path ?? join2(this.root, `${slug(record.name)}.tensorcad.json`);
955
956
  const text = `${JSON.stringify(record.doc, null, 2)}
956
957
  `;
@@ -962,7 +963,7 @@ class FileStore {
962
963
  this.emit({ kind: "saved", record });
963
964
  return { record, path: target, bytes: Buffer.byteLength(text, "utf8") };
964
965
  }
965
- checkpoint(id, label) {
966
+ async checkpoint(id, label) {
966
967
  const entry = this.entry(id);
967
968
  const info = {
968
969
  checkpoint_id: `ckpt_${this.nextCheckpoint++}`,
@@ -973,11 +974,11 @@ class FileStore {
973
974
  entry.checkpoints.set(info.checkpoint_id, { ...info, doc: structuredClone(entry.record.doc) });
974
975
  return info;
975
976
  }
976
- checkpoints(id) {
977
+ async checkpoints(id) {
977
978
  const entry = this.entry(id);
978
979
  return [...entry.checkpoints.values()].map(({ doc: _doc, ...info }) => info);
979
980
  }
980
- restore(id, checkpointId) {
981
+ async restore(id, checkpointId) {
981
982
  const entry = this.entry(id);
982
983
  const { record } = entry;
983
984
  let doc;
@@ -1034,6 +1035,25 @@ function slug(name) {
1034
1035
  return name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "design";
1035
1036
  }
1036
1037
 
1038
+ // packages/mcp/src/store/disk-artifacts.ts
1039
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1040
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
1041
+
1042
+ class DiskSink {
1043
+ root;
1044
+ label = "disk";
1045
+ constructor(root) {
1046
+ this.root = root;
1047
+ }
1048
+ async write(prefix, path, contents) {
1049
+ const base = isAbsolute2(prefix) ? prefix : resolve2(this.root, prefix);
1050
+ const target = join3(base, path);
1051
+ await mkdir3(dirname3(target), { recursive: true });
1052
+ await writeFile3(target, contents, "utf8");
1053
+ return { location: target };
1054
+ }
1055
+ }
1056
+
1037
1057
  // packages/mcp/src/prompts.ts
1038
1058
  import * as z2 from "zod";
1039
1059
  import { HARDWARE, PRESET_NAMES as PRESET_NAMES2 } from "@tensor-cad/engine/node";
@@ -1696,8 +1716,8 @@ function registerResources(server, store) {
1696
1716
  title: "Design rule report",
1697
1717
  description: "Every finding for a design: shape errors, memory fit, kernel constraints, Chinchilla sanity.",
1698
1718
  mimeType: JSON_MIME
1699
- }, (uri, { id }) => {
1700
- const record = store.get(String(id));
1719
+ }, async (uri, { id }) => {
1720
+ const record = await store.get(String(id));
1701
1721
  const report = validate(record.doc);
1702
1722
  return json(uri, {
1703
1723
  design_id: record.design_id,
@@ -1712,14 +1732,14 @@ function registerResources(server, store) {
1712
1732
  title: "Design analysis",
1713
1733
  description: "Parameters, FLOPs, KV cache, memory, throughput and cost at the document's own defaults.",
1714
1734
  mimeType: JSON_MIME
1715
- }, (uri, { id }) => {
1716
- const record = store.get(String(id));
1735
+ }, async (uri, { id }) => {
1736
+ const record = await store.get(String(id));
1717
1737
  const result = analyze(record.doc);
1718
1738
  return json(uri, { design_id: record.design_id, revision: record.revision, ...analysisJson(result) });
1719
1739
  });
1720
1740
  server.registerResource("design", new ResourceTemplate("tensorcad://designs/{id}", {
1721
- list: () => ({
1722
- resources: store.list().map((d) => ({
1741
+ list: async () => ({
1742
+ resources: (await store.list()).map((d) => ({
1723
1743
  uri: `tensorcad://designs/${d.design_id}`,
1724
1744
  name: d.name,
1725
1745
  title: `${d.name} (revision ${d.revision})`,
@@ -1732,8 +1752,8 @@ function registerResources(server, store) {
1732
1752
  title: "Design document",
1733
1753
  description: "The literal .tensorcad.json document, with a compact outline beside it.",
1734
1754
  mimeType: JSON_MIME
1735
- }, (uri, { id }) => {
1736
- const record = store.get(String(id));
1755
+ }, async (uri, { id }) => {
1756
+ const record = await store.get(String(id));
1737
1757
  return json(uri, {
1738
1758
  design_id: record.design_id,
1739
1759
  revision: record.revision,
@@ -1747,7 +1767,7 @@ function registerResources(server, store) {
1747
1767
  title: "Block catalog",
1748
1768
  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.",
1749
1769
  mimeType: JSON_MIME
1750
- }, (uri) => {
1770
+ }, async (uri) => {
1751
1771
  const blocks = allCatalogEntries();
1752
1772
  return json(uri, {
1753
1773
  count: blocks.length,
@@ -1756,7 +1776,7 @@ function registerResources(server, store) {
1756
1776
  });
1757
1777
  });
1758
1778
  server.registerResource("catalog-block", new ResourceTemplate("tensorcad://catalog/{type}", {
1759
- list: () => ({
1779
+ list: async () => ({
1760
1780
  resources: Object.keys(CATALOG).sort().map((type) => ({
1761
1781
  uri: `tensorcad://catalog/${type}`,
1762
1782
  name: type,
@@ -1791,12 +1811,10 @@ function registerResources(server, store) {
1791
1811
  }));
1792
1812
  }
1793
1813
  function completeDesignId(store) {
1794
- return (value) => store.list().map((d) => d.design_id).filter((id) => id.startsWith(value));
1814
+ return async (value) => (await store.list()).map((d) => d.design_id).filter((id) => id.startsWith(value));
1795
1815
  }
1796
1816
 
1797
1817
  // packages/mcp/src/tools.ts
1798
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1799
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
1800
1818
  import * as z3 from "zod";
1801
1819
  import { formatBytes as formatBytes2, formatCount as formatCount2 } from "@tensor-cad/engine";
1802
1820
  import {
@@ -1873,7 +1891,7 @@ function toAnalysisOptions(input) {
1873
1891
  var READ = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
1874
1892
  var WRITE = { readOnlyHint: false, idempotentHint: false, openWorldHint: false };
1875
1893
  var DESTRUCTIVE = { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false };
1876
- function registerTools(server, store) {
1894
+ function registerTools(server, store, artifacts) {
1877
1895
  server.registerTool("tensorcad_list_designs", {
1878
1896
  title: "List designs",
1879
1897
  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.",
@@ -1892,7 +1910,7 @@ function registerTools(server, store) {
1892
1910
  }),
1893
1911
  annotations: { ...READ, title: "List designs" }
1894
1912
  }, async ({ include_files }) => guard(async () => {
1895
- const designs = store.list();
1913
+ const designs = await store.list();
1896
1914
  const presets = PRESET_NAMES3.map((name) => {
1897
1915
  const doc = getPreset2(name);
1898
1916
  const p = { name };
@@ -1933,8 +1951,8 @@ ${designs.map((d) => ` ${d.design_id} ${d.name} rev ${d.revision}${d.dirty ?
1933
1951
  outline: Outline
1934
1952
  }),
1935
1953
  annotations: { ...WRITE, title: "New design" }
1936
- }, async (args) => guard(() => {
1937
- const record = store.create(args);
1954
+ }, async (args) => guard(async () => {
1955
+ const record = await store.create(args);
1938
1956
  const outline = outlineOf(record.doc);
1939
1957
  return ok(`${record.design_id} (revision ${record.revision})
1940
1958
 
@@ -2019,8 +2037,8 @@ ${formatCount2(report.analysis.params.total)} parameters, ` + `${report.counts.e
2019
2037
  document: z3.record(z3.string(), z3.unknown()).optional().describe("The literal design document.")
2020
2038
  }),
2021
2039
  annotations: { ...READ, title: "Get design" }
2022
- }, async ({ design_id, format }) => guard(() => {
2023
- const record = store.get(design_id);
2040
+ }, async ({ design_id, format }) => guard(async () => {
2041
+ const record = await store.get(design_id);
2024
2042
  const outline = outlineOf(record.doc);
2025
2043
  const mode = format ?? "outline";
2026
2044
  const base = {
@@ -2074,8 +2092,8 @@ ${outlineText(outline)}`, {
2074
2092
  children: z3.array(z3.string())
2075
2093
  }),
2076
2094
  annotations: { ...READ, title: "Get block" }
2077
- }, async ({ design_id, path }) => guard(() => {
2078
- const record = store.get(design_id);
2095
+ }, async ({ design_id, path }) => guard(async () => {
2096
+ const record = await store.get(design_id);
2079
2097
  const detail = blockDetail(record.doc, path);
2080
2098
  return ok(blockText(detail), {
2081
2099
  design_id: record.design_id,
@@ -2115,7 +2133,7 @@ ${outlineText(outline)}`, {
2115
2133
  }))
2116
2134
  }),
2117
2135
  annotations: { ...READ, title: "Search catalog" }
2118
- }, async ({ query, category, kind, limit }) => guard(() => {
2136
+ }, async ({ query, category, kind, limit }) => guard(async () => {
2119
2137
  const all = allCatalogEntries();
2120
2138
  const q = query?.toLowerCase();
2121
2139
  const matched = all.filter((e) => {
@@ -2159,10 +2177,10 @@ ${catalogText(blocks)}`, {
2159
2177
  validation: ValidationSummary
2160
2178
  }),
2161
2179
  annotations: { ...WRITE, title: "Apply edits" }
2162
- }, async ({ design_id, expected_revision, ops }) => guard(() => {
2163
- const before = store.get(design_id);
2180
+ }, async ({ design_id, expected_revision, ops }) => guard(async () => {
2181
+ const before = await store.get(design_id);
2164
2182
  const paramsBefore = outlineOf(before.doc).params_total;
2165
- const outcome = store.apply(design_id, ops, expected_revision);
2183
+ const outcome = await store.apply(design_id, ops, expected_revision);
2166
2184
  const report = validate2(outcome.record.doc);
2167
2185
  const total = report.analysis.params.total;
2168
2186
  const delta = total - paramsBefore;
@@ -2205,8 +2223,8 @@ ${catalogText(blocks)}`, {
2205
2223
  params_total: z3.number()
2206
2224
  }),
2207
2225
  annotations: { ...READ, title: "Validate design" }
2208
- }, async ({ design_id, severity, ...rest }) => guard(() => {
2209
- const record = store.get(design_id);
2226
+ }, async ({ design_id, severity, ...rest }) => guard(async () => {
2227
+ const record = await store.get(design_id);
2210
2228
  const report = validate2(record.doc, toAnalysisOptions(rest));
2211
2229
  const rank = { error: 0, warning: 1, info: 2 };
2212
2230
  const findings = findingsJson(report).filter((f) => severity === undefined || rank[f.severity] <= rank[severity]);
@@ -2233,8 +2251,8 @@ ${catalogText(blocks)}`, {
2233
2251
  inputSchema: z3.object({ design_id: DESIGN_ID, ...analysisOptionsShape }),
2234
2252
  outputSchema: AnalysisOutput,
2235
2253
  annotations: { ...READ, title: "Analyze design" }
2236
- }, async ({ design_id, ...rest }) => guard(() => {
2237
- const record = store.get(design_id);
2254
+ }, async ({ design_id, ...rest }) => guard(async () => {
2255
+ const record = await store.get(design_id);
2238
2256
  const result = analyze2(record.doc, toAnalysisOptions(rest));
2239
2257
  return ok(analysisText(result), {
2240
2258
  design_id: record.design_id,
@@ -2267,30 +2285,36 @@ ${catalogText(blocks)}`, {
2267
2285
  }),
2268
2286
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Generate code" }
2269
2287
  }, async ({ design_id, class_name, include_smoke_test, out_dir }) => guard(async () => {
2270
- const record = store.get(design_id);
2288
+ const record = await store.get(design_id);
2271
2289
  const generated = generateTorch(record.doc, {
2272
2290
  ...class_name ? { className: class_name } : {},
2273
2291
  includeSmokeTest: include_smoke_test ?? false
2274
2292
  });
2275
- const root = out_dir ? isAbsolute2(out_dir) ? out_dir : resolve2(process.cwd(), out_dir) : undefined;
2276
2293
  const files = [];
2277
2294
  for (const file of generated.files) {
2278
- const bytes = Buffer.byteLength(file.contents, "utf8");
2295
+ const bytes = new TextEncoder().encode(file.contents).length;
2279
2296
  const lines = file.contents.split(`
2280
2297
  `).length;
2281
- if (root) {
2282
- const target = join3(root, file.path);
2283
- await mkdir3(dirname3(target), { recursive: true });
2284
- await writeFile3(target, file.contents, "utf8");
2285
- files.push({ path: file.path, bytes, lines, written_to: target });
2286
- } else {
2298
+ if (out_dir === undefined) {
2287
2299
  files.push({ path: file.path, bytes, lines, contents: file.contents });
2300
+ continue;
2288
2301
  }
2302
+ const written = await artifacts.write(out_dir, file.path, file.contents);
2303
+ files.push({
2304
+ path: file.path,
2305
+ bytes,
2306
+ lines,
2307
+ written_to: written.location,
2308
+ ...written.url ? { url: written.url } : {}
2309
+ });
2289
2310
  }
2290
- const text = root ? [`wrote ${files.length} file(s) to ${root}`, ...files.map((f) => ` ${f.path} ${f.bytes} bytes`)].join(`
2291
- `) : generated.files.map((f) => `# ${f.path}
2311
+ const text = out_dir === undefined ? generated.files.map((f) => `# ${f.path}
2292
2312
  ${f.contents}`).join(`
2293
2313
 
2314
+ `) : [
2315
+ `wrote ${files.length} file(s) to ${artifacts.label}`,
2316
+ ...files.map((f) => ` ${f.path} ${f.bytes} bytes ${f.url ?? f.written_to}`)
2317
+ ].join(`
2294
2318
  `);
2295
2319
  return ok(generated.warnings.length > 0 ? `${text}
2296
2320
 
@@ -2299,8 +2323,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2299
2323
  `)}` : text, {
2300
2324
  design_id: record.design_id,
2301
2325
  revision: record.revision,
2302
- wrote: Boolean(root),
2303
- ...root ? { out_dir: root } : {},
2326
+ wrote: out_dir !== undefined,
2327
+ ...out_dir !== undefined ? { out_dir } : {},
2304
2328
  files,
2305
2329
  warnings: generated.warnings
2306
2330
  });
@@ -2326,12 +2350,12 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2326
2350
  }))
2327
2351
  }),
2328
2352
  annotations: { ...WRITE, title: "Checkpoint design" }
2329
- }, async ({ design_id, label }) => guard(() => {
2330
- const info = store.checkpoint(design_id, label);
2353
+ }, async ({ design_id, label }) => guard(async () => {
2354
+ const info = await store.checkpoint(design_id, label);
2331
2355
  return ok(`${info.checkpoint_id} at revision ${info.revision}: ${info.label}`, {
2332
2356
  design_id,
2333
2357
  ...info,
2334
- checkpoints: store.checkpoints(design_id)
2358
+ checkpoints: await store.checkpoints(design_id)
2335
2359
  });
2336
2360
  }));
2337
2361
  server.registerTool("tensorcad_restore", {
@@ -2350,8 +2374,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2350
2374
  validation: ValidationSummary
2351
2375
  }),
2352
2376
  annotations: { ...DESTRUCTIVE, title: "Restore design" }
2353
- }, async ({ design_id, checkpoint_id }) => guard(() => {
2354
- const { record, restoredFrom } = store.restore(design_id, checkpoint_id);
2377
+ }, async ({ design_id, checkpoint_id }) => guard(async () => {
2378
+ const { record, restoredFrom } = await store.restore(design_id, checkpoint_id);
2355
2379
  const report = validate2(record.doc);
2356
2380
  return ok(`${record.design_id} restored from ${restoredFrom}; now revision ${record.revision}, ` + `${formatCount2(report.analysis.params.total)} parameters`, {
2357
2381
  design_id: record.design_id,
@@ -2394,8 +2418,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2394
2418
  })
2395
2419
  }),
2396
2420
  annotations: { ...READ, title: "Explain a block" }
2397
- }, async ({ design_id, path, ...rest }) => guard(() => {
2398
- const record = store.get(design_id);
2421
+ }, async ({ design_id, path, ...rest }) => guard(async () => {
2422
+ const record = await store.get(design_id);
2399
2423
  const e = explain(record.doc, path, toAnalysisOptions(rest));
2400
2424
  const lines = [
2401
2425
  `${path} ${e.type} (${e.kind})`,
@@ -2456,8 +2480,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2456
2480
  notes: z3.array(z3.string())
2457
2481
  }),
2458
2482
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Scale a design" }
2459
- }, async ({ design_id, target_params, target_basis, vocab, tie_head, keep_depth }) => guard(() => {
2460
- const record = store.get(design_id);
2483
+ }, async ({ design_id, target_params, target_basis, vocab, tie_head, keep_depth }) => guard(async () => {
2484
+ const record = await store.get(design_id);
2461
2485
  const result = scaleDesign(record.doc, {
2462
2486
  targetParams: target_params,
2463
2487
  ...target_basis ? { targetBasis: target_basis } : {},
@@ -2465,7 +2489,7 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2465
2489
  ...tie_head !== undefined ? { tieHead: tie_head } : {},
2466
2490
  ...keep_depth !== undefined ? { keepDepth: keep_depth } : {}
2467
2491
  });
2468
- const saved = store.adopt(result.doc);
2492
+ const saved = await store.adopt(result.doc);
2469
2493
  const changes = Object.entries(result.changes).map(([symbol, c]) => ({
2470
2494
  symbol,
2471
2495
  from: c.from,
@@ -2518,28 +2542,32 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2518
2542
  notes: z3.array(z3.string())
2519
2543
  }),
2520
2544
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Build a width ladder" }
2521
- }, async ({ design_id, widths, base_width }) => guard(() => {
2522
- const record = store.get(design_id);
2545
+ }, async ({ design_id, widths, base_width }) => guard(async () => {
2546
+ const record = await store.get(design_id);
2523
2547
  const ladder = mupLadder(record.doc, {
2524
2548
  ...widths ? { widths } : {},
2525
2549
  ...base_width !== undefined ? { baseWidth: base_width } : {}
2526
2550
  });
2527
- const rungs = ladder.rungs.map((rung) => ({
2528
- design_id: store.adopt(rung.doc).design_id,
2529
- width: rung.width,
2530
- multiplier: rung.multiplier,
2531
- heads: rung.heads,
2532
- params: rung.params,
2533
- base: rung.base,
2534
- scaling: rung.scaling.map((s) => ({
2535
- class: s.class,
2536
- init_std: s.initStd,
2537
- adam_lr: s.adamLr,
2538
- paths: s.paths,
2539
- why: s.why
2540
- })),
2541
- notes: rung.notes
2542
- }));
2551
+ const rungs = [];
2552
+ for (const rung of ladder.rungs) {
2553
+ const adopted = await store.adopt(rung.doc);
2554
+ rungs.push({
2555
+ design_id: adopted.design_id,
2556
+ width: rung.width,
2557
+ multiplier: rung.multiplier,
2558
+ heads: rung.heads,
2559
+ params: rung.params,
2560
+ base: rung.base,
2561
+ scaling: rung.scaling.map((s) => ({
2562
+ class: s.class,
2563
+ init_std: s.initStd,
2564
+ adam_lr: s.adamLr,
2565
+ paths: s.paths,
2566
+ why: s.why
2567
+ })),
2568
+ notes: rung.notes
2569
+ });
2570
+ }
2543
2571
  const text = [
2544
2572
  `${record.doc.meta.name} laddered by ${ladder.widthSymbol}, tuned at ${ladder.baseWidth}, ` + `heads of ${ladder.headDim} throughout`,
2545
2573
  ...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(", ")}`),
@@ -2588,8 +2616,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2588
2616
  notes: z3.array(z3.string())
2589
2617
  }),
2590
2618
  annotations: { ...READ, title: "Plan a cluster" }
2591
- }, async ({ design_id, gpus, gpus_per_node, headroom, limit, ...rest }) => guard(() => {
2592
- const record = store.get(design_id);
2619
+ }, async ({ design_id, gpus, gpus_per_node, headroom, limit, ...rest }) => guard(async () => {
2620
+ const record = await store.get(design_id);
2593
2621
  const result = planCluster(record.doc, toAnalysisOptions(rest), {
2594
2622
  gpus,
2595
2623
  ...gpus_per_node !== undefined ? { gpusPerNode: gpus_per_node } : {},
@@ -2665,9 +2693,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2665
2693
  }))
2666
2694
  }),
2667
2695
  annotations: { ...READ, title: "Compare two designs" }
2668
- }, async ({ a, b, ...rest }) => guard(() => {
2669
- const left = store.get(a);
2670
- const right = store.get(b);
2696
+ }, async ({ a, b, ...rest }) => guard(async () => {
2697
+ const left = await store.get(a);
2698
+ const right = await store.get(b);
2671
2699
  const d = diffDesigns(left.doc, right.doc, toAnalysisOptions(rest));
2672
2700
  const brief = (v) => {
2673
2701
  if (v === undefined || v === null)
@@ -2733,9 +2761,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
2733
2761
  warnings: z3.array(z3.string())
2734
2762
  }),
2735
2763
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Import a config" }
2736
- }, async ({ config, name }) => guard(() => {
2764
+ }, async ({ config, name }) => guard(async () => {
2737
2765
  const result = importHfConfig(config, name);
2738
- const record = store.adopt(result.doc);
2766
+ const record = await store.adopt(result.doc);
2739
2767
  const total = analyze2(result.doc).params.total;
2740
2768
  const text = [
2741
2769
  `${result.doc.meta.name}: ${formatCount2(total)} parameters`,
@@ -2798,13 +2826,14 @@ var INSTRUCTIONS = [
2798
2826
  ].join(`
2799
2827
  `);
2800
2828
  function createServer2(options = {}) {
2801
- const { store: given, bridge, ...storeOptions } = options;
2829
+ const { store: given, bridge, artifacts: givenArtifacts, ...storeOptions } = options;
2802
2830
  const store = given ?? new FileStore(storeOptions);
2831
+ const artifacts = givenArtifacts ?? new DiskSink(storeOptions.root ?? process.cwd());
2803
2832
  const server = new McpServer2({ name: SERVER_NAME, version: SERVER_VERSION }, {
2804
2833
  capabilities: { tools: {}, resources: {}, prompts: {}, completions: {} },
2805
2834
  instructions: INSTRUCTIONS
2806
2835
  });
2807
- registerTools(server, store);
2836
+ registerTools(server, store, artifacts);
2808
2837
  registerResources(server, store);
2809
2838
  registerPrompts(server);
2810
2839
  if (bridge)
package/server.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  import { McpServer } from "@modelcontextprotocol/server";
10
10
  import { type FileStoreOptions } from "./store/file-store.js";
11
11
  import type { DocumentStore } from "./store/types.js";
12
+ import type { ArtifactSink } from "./artifacts.js";
12
13
  import type { BridgeServer } from "./bridge/server.js";
13
14
  export declare const SERVER_NAME = "tensorcad";
14
15
  /**
@@ -21,6 +22,14 @@ export declare const SERVER_VERSION: string;
21
22
  export interface ServerOptions extends FileStoreOptions {
22
23
  /** Defaults to a `FileStore` rooted at the working directory. */
23
24
  store?: DocumentStore;
25
+ /**
26
+ * Where `tensorcad_generate_code` puts what it emits.
27
+ *
28
+ * Defaults to writing into a directory. A hosted server has no filesystem
29
+ * and supplies object storage instead, which is the difference between an
30
+ * agent being handed a model it can run and one it can only read.
31
+ */
32
+ artifacts?: ArtifactSink;
24
33
  /**
25
34
  * The live editor bridge, when one is running. Given one, the server tells
26
35
  * its client that a design's resources changed whenever the *human* changed
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.filip-pajalic/tensorcad",
4
4
  "description": "Design transformer LLM architectures and report their parameters, FLOPs, memory and cost",
5
- "version": "0.1.1",
5
+ "version": "0.1.3",
6
6
  "repository": {
7
7
  "url": "https://github.com/Filip-Pajalic/TensorCAD",
8
8
  "source": "github",
@@ -14,7 +14,7 @@
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@tensor-cad/mcp",
17
- "version": "0.1.1",
17
+ "version": "0.1.3",
18
18
  "runtimeHint": "npx",
19
19
  "transport": {
20
20
  "type": "stdio"