@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/artifacts.d.ts +46 -0
- package/bridge/server.d.ts +21 -0
- package/index.d.ts +14 -1
- package/index.js +147 -110
- package/package.json +2 -2
- package/serve.js +144 -110
- package/server.d.ts +9 -0
- package/server.json +2 -2
- package/stdio.js +144 -110
- package/store/disk-artifacts.d.ts +16 -0
- package/store/file-store.d.ts +10 -10
- package/store/types.d.ts +32 -14
- package/summarize.d.ts +9 -0
- package/tools.d.ts +2 -1
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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.
|
|
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 = [
|
|
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 =
|
|
2302
|
+
const bytes = new TextEncoder().encode(file.contents).length;
|
|
2281
2303
|
const lines = file.contents.split(`
|
|
2282
2304
|
`).length;
|
|
2283
|
-
if (
|
|
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 =
|
|
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:
|
|
2305
|
-
...
|
|
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 =
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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
|
+
}
|