@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/index.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) =>
|
|
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
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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.
|
|
553
|
+
version: "0.1.4",
|
|
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";
|
|
@@ -1500,6 +1520,7 @@ function catalogEntry(def) {
|
|
|
1500
1520
|
type: def.type,
|
|
1501
1521
|
kind: def.kind,
|
|
1502
1522
|
category: def.category,
|
|
1523
|
+
name: def.docs.name ?? def.type,
|
|
1503
1524
|
summary: def.docs.summary ?? "",
|
|
1504
1525
|
refs: def.docs.refs ?? [],
|
|
1505
1526
|
params: def.paramOrder.map((name) => catalogParam(name, def.params[name])),
|
|
@@ -1538,7 +1559,10 @@ function allCatalogEntries() {
|
|
|
1538
1559
|
}
|
|
1539
1560
|
function catalogText(entries) {
|
|
1540
1561
|
return entries.map((e) => {
|
|
1541
|
-
const lines = [
|
|
1562
|
+
const lines = [
|
|
1563
|
+
e.name && e.name !== e.type ? `${e.type} — ${e.name} (${e.kind}/${e.category})` : `${e.type} (${e.kind}/${e.category})`,
|
|
1564
|
+
` ${e.summary}`
|
|
1565
|
+
];
|
|
1542
1566
|
if (e.formula)
|
|
1543
1567
|
lines.push(` formula: ${e.formula}`);
|
|
1544
1568
|
if (e.params.length > 0) {
|
|
@@ -1696,8 +1720,8 @@ function registerResources(server, store) {
|
|
|
1696
1720
|
title: "Design rule report",
|
|
1697
1721
|
description: "Every finding for a design: shape errors, memory fit, kernel constraints, Chinchilla sanity.",
|
|
1698
1722
|
mimeType: JSON_MIME
|
|
1699
|
-
}, (uri, { id }) => {
|
|
1700
|
-
const record = store.get(String(id));
|
|
1723
|
+
}, async (uri, { id }) => {
|
|
1724
|
+
const record = await store.get(String(id));
|
|
1701
1725
|
const report = validate(record.doc);
|
|
1702
1726
|
return json(uri, {
|
|
1703
1727
|
design_id: record.design_id,
|
|
@@ -1712,14 +1736,14 @@ function registerResources(server, store) {
|
|
|
1712
1736
|
title: "Design analysis",
|
|
1713
1737
|
description: "Parameters, FLOPs, KV cache, memory, throughput and cost at the document's own defaults.",
|
|
1714
1738
|
mimeType: JSON_MIME
|
|
1715
|
-
}, (uri, { id }) => {
|
|
1716
|
-
const record = store.get(String(id));
|
|
1739
|
+
}, async (uri, { id }) => {
|
|
1740
|
+
const record = await store.get(String(id));
|
|
1717
1741
|
const result = analyze(record.doc);
|
|
1718
1742
|
return json(uri, { design_id: record.design_id, revision: record.revision, ...analysisJson(result) });
|
|
1719
1743
|
});
|
|
1720
1744
|
server.registerResource("design", new ResourceTemplate("tensorcad://designs/{id}", {
|
|
1721
|
-
list: () => ({
|
|
1722
|
-
resources: store.list().map((d) => ({
|
|
1745
|
+
list: async () => ({
|
|
1746
|
+
resources: (await store.list()).map((d) => ({
|
|
1723
1747
|
uri: `tensorcad://designs/${d.design_id}`,
|
|
1724
1748
|
name: d.name,
|
|
1725
1749
|
title: `${d.name} (revision ${d.revision})`,
|
|
@@ -1732,8 +1756,8 @@ function registerResources(server, store) {
|
|
|
1732
1756
|
title: "Design document",
|
|
1733
1757
|
description: "The literal .tensorcad.json document, with a compact outline beside it.",
|
|
1734
1758
|
mimeType: JSON_MIME
|
|
1735
|
-
}, (uri, { id }) => {
|
|
1736
|
-
const record = store.get(String(id));
|
|
1759
|
+
}, async (uri, { id }) => {
|
|
1760
|
+
const record = await store.get(String(id));
|
|
1737
1761
|
return json(uri, {
|
|
1738
1762
|
design_id: record.design_id,
|
|
1739
1763
|
revision: record.revision,
|
|
@@ -1747,7 +1771,7 @@ function registerResources(server, store) {
|
|
|
1747
1771
|
title: "Block catalog",
|
|
1748
1772
|
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
1773
|
mimeType: JSON_MIME
|
|
1750
|
-
}, (uri) => {
|
|
1774
|
+
}, async (uri) => {
|
|
1751
1775
|
const blocks = allCatalogEntries();
|
|
1752
1776
|
return json(uri, {
|
|
1753
1777
|
count: blocks.length,
|
|
@@ -1756,7 +1780,7 @@ function registerResources(server, store) {
|
|
|
1756
1780
|
});
|
|
1757
1781
|
});
|
|
1758
1782
|
server.registerResource("catalog-block", new ResourceTemplate("tensorcad://catalog/{type}", {
|
|
1759
|
-
list: () => ({
|
|
1783
|
+
list: async () => ({
|
|
1760
1784
|
resources: Object.keys(CATALOG).sort().map((type) => ({
|
|
1761
1785
|
uri: `tensorcad://catalog/${type}`,
|
|
1762
1786
|
name: type,
|
|
@@ -1791,12 +1815,10 @@ function registerResources(server, store) {
|
|
|
1791
1815
|
}));
|
|
1792
1816
|
}
|
|
1793
1817
|
function completeDesignId(store) {
|
|
1794
|
-
return (value) => store.list().map((d) => d.design_id).filter((id) => id.startsWith(value));
|
|
1818
|
+
return async (value) => (await store.list()).map((d) => d.design_id).filter((id) => id.startsWith(value));
|
|
1795
1819
|
}
|
|
1796
1820
|
|
|
1797
1821
|
// 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
1822
|
import * as z3 from "zod";
|
|
1801
1823
|
import { formatBytes as formatBytes2, formatCount as formatCount2 } from "@tensor-cad/engine";
|
|
1802
1824
|
import {
|
|
@@ -1873,7 +1895,7 @@ function toAnalysisOptions(input) {
|
|
|
1873
1895
|
var READ = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
|
|
1874
1896
|
var WRITE = { readOnlyHint: false, idempotentHint: false, openWorldHint: false };
|
|
1875
1897
|
var DESTRUCTIVE = { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false };
|
|
1876
|
-
function registerTools(server, store) {
|
|
1898
|
+
function registerTools(server, store, artifacts) {
|
|
1877
1899
|
server.registerTool("tensorcad_list_designs", {
|
|
1878
1900
|
title: "List designs",
|
|
1879
1901
|
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 +1914,7 @@ function registerTools(server, store) {
|
|
|
1892
1914
|
}),
|
|
1893
1915
|
annotations: { ...READ, title: "List designs" }
|
|
1894
1916
|
}, async ({ include_files }) => guard(async () => {
|
|
1895
|
-
const designs = store.list();
|
|
1917
|
+
const designs = await store.list();
|
|
1896
1918
|
const presets = PRESET_NAMES3.map((name) => {
|
|
1897
1919
|
const doc = getPreset2(name);
|
|
1898
1920
|
const p = { name };
|
|
@@ -1933,8 +1955,8 @@ ${designs.map((d) => ` ${d.design_id} ${d.name} rev ${d.revision}${d.dirty ?
|
|
|
1933
1955
|
outline: Outline
|
|
1934
1956
|
}),
|
|
1935
1957
|
annotations: { ...WRITE, title: "New design" }
|
|
1936
|
-
}, async (args) => guard(() => {
|
|
1937
|
-
const record = store.create(args);
|
|
1958
|
+
}, async (args) => guard(async () => {
|
|
1959
|
+
const record = await store.create(args);
|
|
1938
1960
|
const outline = outlineOf(record.doc);
|
|
1939
1961
|
return ok(`${record.design_id} (revision ${record.revision})
|
|
1940
1962
|
|
|
@@ -2019,8 +2041,8 @@ ${formatCount2(report.analysis.params.total)} parameters, ` + `${report.counts.e
|
|
|
2019
2041
|
document: z3.record(z3.string(), z3.unknown()).optional().describe("The literal design document.")
|
|
2020
2042
|
}),
|
|
2021
2043
|
annotations: { ...READ, title: "Get design" }
|
|
2022
|
-
}, async ({ design_id, format }) => guard(() => {
|
|
2023
|
-
const record = store.get(design_id);
|
|
2044
|
+
}, async ({ design_id, format }) => guard(async () => {
|
|
2045
|
+
const record = await store.get(design_id);
|
|
2024
2046
|
const outline = outlineOf(record.doc);
|
|
2025
2047
|
const mode = format ?? "outline";
|
|
2026
2048
|
const base = {
|
|
@@ -2074,8 +2096,8 @@ ${outlineText(outline)}`, {
|
|
|
2074
2096
|
children: z3.array(z3.string())
|
|
2075
2097
|
}),
|
|
2076
2098
|
annotations: { ...READ, title: "Get block" }
|
|
2077
|
-
}, async ({ design_id, path }) => guard(() => {
|
|
2078
|
-
const record = store.get(design_id);
|
|
2099
|
+
}, async ({ design_id, path }) => guard(async () => {
|
|
2100
|
+
const record = await store.get(design_id);
|
|
2079
2101
|
const detail = blockDetail(record.doc, path);
|
|
2080
2102
|
return ok(blockText(detail), {
|
|
2081
2103
|
design_id: record.design_id,
|
|
@@ -2099,6 +2121,7 @@ ${outlineText(outline)}`, {
|
|
|
2099
2121
|
type: z3.string(),
|
|
2100
2122
|
kind: z3.string(),
|
|
2101
2123
|
category: z3.string(),
|
|
2124
|
+
name: z3.string().describe("What a drawing calls this block."),
|
|
2102
2125
|
summary: z3.string(),
|
|
2103
2126
|
formula: z3.string().optional(),
|
|
2104
2127
|
refs: z3.array(z3.string()),
|
|
@@ -2115,7 +2138,7 @@ ${outlineText(outline)}`, {
|
|
|
2115
2138
|
}))
|
|
2116
2139
|
}),
|
|
2117
2140
|
annotations: { ...READ, title: "Search catalog" }
|
|
2118
|
-
}, async ({ query, category, kind, limit }) => guard(() => {
|
|
2141
|
+
}, async ({ query, category, kind, limit }) => guard(async () => {
|
|
2119
2142
|
const all = allCatalogEntries();
|
|
2120
2143
|
const q = query?.toLowerCase();
|
|
2121
2144
|
const matched = all.filter((e) => {
|
|
@@ -2125,7 +2148,7 @@ ${outlineText(outline)}`, {
|
|
|
2125
2148
|
return false;
|
|
2126
2149
|
if (!q)
|
|
2127
2150
|
return true;
|
|
2128
|
-
const hay = `${e.type} ${e.category} ${e.summary} ${e.formula ?? ""}`.toLowerCase();
|
|
2151
|
+
const hay = `${e.type} ${e.name} ${e.category} ${e.summary} ${e.formula ?? ""}`.toLowerCase();
|
|
2129
2152
|
return hay.includes(q);
|
|
2130
2153
|
});
|
|
2131
2154
|
const blocks = matched.slice(0, limit ?? 20);
|
|
@@ -2159,10 +2182,10 @@ ${catalogText(blocks)}`, {
|
|
|
2159
2182
|
validation: ValidationSummary
|
|
2160
2183
|
}),
|
|
2161
2184
|
annotations: { ...WRITE, title: "Apply edits" }
|
|
2162
|
-
}, async ({ design_id, expected_revision, ops }) => guard(() => {
|
|
2163
|
-
const before = store.get(design_id);
|
|
2185
|
+
}, async ({ design_id, expected_revision, ops }) => guard(async () => {
|
|
2186
|
+
const before = await store.get(design_id);
|
|
2164
2187
|
const paramsBefore = outlineOf(before.doc).params_total;
|
|
2165
|
-
const outcome = store.apply(design_id, ops, expected_revision);
|
|
2188
|
+
const outcome = await store.apply(design_id, ops, expected_revision);
|
|
2166
2189
|
const report = validate2(outcome.record.doc);
|
|
2167
2190
|
const total = report.analysis.params.total;
|
|
2168
2191
|
const delta = total - paramsBefore;
|
|
@@ -2205,8 +2228,8 @@ ${catalogText(blocks)}`, {
|
|
|
2205
2228
|
params_total: z3.number()
|
|
2206
2229
|
}),
|
|
2207
2230
|
annotations: { ...READ, title: "Validate design" }
|
|
2208
|
-
}, async ({ design_id, severity, ...rest }) => guard(() => {
|
|
2209
|
-
const record = store.get(design_id);
|
|
2231
|
+
}, async ({ design_id, severity, ...rest }) => guard(async () => {
|
|
2232
|
+
const record = await store.get(design_id);
|
|
2210
2233
|
const report = validate2(record.doc, toAnalysisOptions(rest));
|
|
2211
2234
|
const rank = { error: 0, warning: 1, info: 2 };
|
|
2212
2235
|
const findings = findingsJson(report).filter((f) => severity === undefined || rank[f.severity] <= rank[severity]);
|
|
@@ -2233,8 +2256,8 @@ ${catalogText(blocks)}`, {
|
|
|
2233
2256
|
inputSchema: z3.object({ design_id: DESIGN_ID, ...analysisOptionsShape }),
|
|
2234
2257
|
outputSchema: AnalysisOutput,
|
|
2235
2258
|
annotations: { ...READ, title: "Analyze design" }
|
|
2236
|
-
}, async ({ design_id, ...rest }) => guard(() => {
|
|
2237
|
-
const record = store.get(design_id);
|
|
2259
|
+
}, async ({ design_id, ...rest }) => guard(async () => {
|
|
2260
|
+
const record = await store.get(design_id);
|
|
2238
2261
|
const result = analyze2(record.doc, toAnalysisOptions(rest));
|
|
2239
2262
|
return ok(analysisText(result), {
|
|
2240
2263
|
design_id: record.design_id,
|
|
@@ -2267,30 +2290,36 @@ ${catalogText(blocks)}`, {
|
|
|
2267
2290
|
}),
|
|
2268
2291
|
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Generate code" }
|
|
2269
2292
|
}, async ({ design_id, class_name, include_smoke_test, out_dir }) => guard(async () => {
|
|
2270
|
-
const record = store.get(design_id);
|
|
2293
|
+
const record = await store.get(design_id);
|
|
2271
2294
|
const generated = generateTorch(record.doc, {
|
|
2272
2295
|
...class_name ? { className: class_name } : {},
|
|
2273
2296
|
includeSmokeTest: include_smoke_test ?? false
|
|
2274
2297
|
});
|
|
2275
|
-
const root = out_dir ? isAbsolute2(out_dir) ? out_dir : resolve2(process.cwd(), out_dir) : undefined;
|
|
2276
2298
|
const files = [];
|
|
2277
2299
|
for (const file of generated.files) {
|
|
2278
|
-
const bytes =
|
|
2300
|
+
const bytes = new TextEncoder().encode(file.contents).length;
|
|
2279
2301
|
const lines = file.contents.split(`
|
|
2280
2302
|
`).length;
|
|
2281
|
-
if (
|
|
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 {
|
|
2303
|
+
if (out_dir === undefined) {
|
|
2287
2304
|
files.push({ path: file.path, bytes, lines, contents: file.contents });
|
|
2305
|
+
continue;
|
|
2288
2306
|
}
|
|
2307
|
+
const written = await artifacts.write(out_dir, file.path, file.contents);
|
|
2308
|
+
files.push({
|
|
2309
|
+
path: file.path,
|
|
2310
|
+
bytes,
|
|
2311
|
+
lines,
|
|
2312
|
+
written_to: written.location,
|
|
2313
|
+
...written.url ? { url: written.url } : {}
|
|
2314
|
+
});
|
|
2289
2315
|
}
|
|
2290
|
-
const text =
|
|
2291
|
-
`) : generated.files.map((f) => `# ${f.path}
|
|
2316
|
+
const text = out_dir === undefined ? generated.files.map((f) => `# ${f.path}
|
|
2292
2317
|
${f.contents}`).join(`
|
|
2293
2318
|
|
|
2319
|
+
`) : [
|
|
2320
|
+
`wrote ${files.length} file(s) to ${artifacts.label}`,
|
|
2321
|
+
...files.map((f) => ` ${f.path} ${f.bytes} bytes ${f.url ?? f.written_to}`)
|
|
2322
|
+
].join(`
|
|
2294
2323
|
`);
|
|
2295
2324
|
return ok(generated.warnings.length > 0 ? `${text}
|
|
2296
2325
|
|
|
@@ -2299,8 +2328,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2299
2328
|
`)}` : text, {
|
|
2300
2329
|
design_id: record.design_id,
|
|
2301
2330
|
revision: record.revision,
|
|
2302
|
-
wrote:
|
|
2303
|
-
...
|
|
2331
|
+
wrote: out_dir !== undefined,
|
|
2332
|
+
...out_dir !== undefined ? { out_dir } : {},
|
|
2304
2333
|
files,
|
|
2305
2334
|
warnings: generated.warnings
|
|
2306
2335
|
});
|
|
@@ -2326,12 +2355,12 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2326
2355
|
}))
|
|
2327
2356
|
}),
|
|
2328
2357
|
annotations: { ...WRITE, title: "Checkpoint design" }
|
|
2329
|
-
}, async ({ design_id, label }) => guard(() => {
|
|
2330
|
-
const info = store.checkpoint(design_id, label);
|
|
2358
|
+
}, async ({ design_id, label }) => guard(async () => {
|
|
2359
|
+
const info = await store.checkpoint(design_id, label);
|
|
2331
2360
|
return ok(`${info.checkpoint_id} at revision ${info.revision}: ${info.label}`, {
|
|
2332
2361
|
design_id,
|
|
2333
2362
|
...info,
|
|
2334
|
-
checkpoints: store.checkpoints(design_id)
|
|
2363
|
+
checkpoints: await store.checkpoints(design_id)
|
|
2335
2364
|
});
|
|
2336
2365
|
}));
|
|
2337
2366
|
server.registerTool("tensorcad_restore", {
|
|
@@ -2350,8 +2379,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2350
2379
|
validation: ValidationSummary
|
|
2351
2380
|
}),
|
|
2352
2381
|
annotations: { ...DESTRUCTIVE, title: "Restore design" }
|
|
2353
|
-
}, async ({ design_id, checkpoint_id }) => guard(() => {
|
|
2354
|
-
const { record, restoredFrom } = store.restore(design_id, checkpoint_id);
|
|
2382
|
+
}, async ({ design_id, checkpoint_id }) => guard(async () => {
|
|
2383
|
+
const { record, restoredFrom } = await store.restore(design_id, checkpoint_id);
|
|
2355
2384
|
const report = validate2(record.doc);
|
|
2356
2385
|
return ok(`${record.design_id} restored from ${restoredFrom}; now revision ${record.revision}, ` + `${formatCount2(report.analysis.params.total)} parameters`, {
|
|
2357
2386
|
design_id: record.design_id,
|
|
@@ -2394,8 +2423,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2394
2423
|
})
|
|
2395
2424
|
}),
|
|
2396
2425
|
annotations: { ...READ, title: "Explain a block" }
|
|
2397
|
-
}, async ({ design_id, path, ...rest }) => guard(() => {
|
|
2398
|
-
const record = store.get(design_id);
|
|
2426
|
+
}, async ({ design_id, path, ...rest }) => guard(async () => {
|
|
2427
|
+
const record = await store.get(design_id);
|
|
2399
2428
|
const e = explain(record.doc, path, toAnalysisOptions(rest));
|
|
2400
2429
|
const lines = [
|
|
2401
2430
|
`${path} ${e.type} (${e.kind})`,
|
|
@@ -2456,8 +2485,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2456
2485
|
notes: z3.array(z3.string())
|
|
2457
2486
|
}),
|
|
2458
2487
|
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);
|
|
2488
|
+
}, async ({ design_id, target_params, target_basis, vocab, tie_head, keep_depth }) => guard(async () => {
|
|
2489
|
+
const record = await store.get(design_id);
|
|
2461
2490
|
const result = scaleDesign(record.doc, {
|
|
2462
2491
|
targetParams: target_params,
|
|
2463
2492
|
...target_basis ? { targetBasis: target_basis } : {},
|
|
@@ -2465,7 +2494,7 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2465
2494
|
...tie_head !== undefined ? { tieHead: tie_head } : {},
|
|
2466
2495
|
...keep_depth !== undefined ? { keepDepth: keep_depth } : {}
|
|
2467
2496
|
});
|
|
2468
|
-
const saved = store.adopt(result.doc);
|
|
2497
|
+
const saved = await store.adopt(result.doc);
|
|
2469
2498
|
const changes = Object.entries(result.changes).map(([symbol, c]) => ({
|
|
2470
2499
|
symbol,
|
|
2471
2500
|
from: c.from,
|
|
@@ -2518,28 +2547,32 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2518
2547
|
notes: z3.array(z3.string())
|
|
2519
2548
|
}),
|
|
2520
2549
|
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);
|
|
2550
|
+
}, async ({ design_id, widths, base_width }) => guard(async () => {
|
|
2551
|
+
const record = await store.get(design_id);
|
|
2523
2552
|
const ladder = mupLadder(record.doc, {
|
|
2524
2553
|
...widths ? { widths } : {},
|
|
2525
2554
|
...base_width !== undefined ? { baseWidth: base_width } : {}
|
|
2526
2555
|
});
|
|
2527
|
-
const rungs =
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2556
|
+
const rungs = [];
|
|
2557
|
+
for (const rung of ladder.rungs) {
|
|
2558
|
+
const adopted = await store.adopt(rung.doc);
|
|
2559
|
+
rungs.push({
|
|
2560
|
+
design_id: adopted.design_id,
|
|
2561
|
+
width: rung.width,
|
|
2562
|
+
multiplier: rung.multiplier,
|
|
2563
|
+
heads: rung.heads,
|
|
2564
|
+
params: rung.params,
|
|
2565
|
+
base: rung.base,
|
|
2566
|
+
scaling: rung.scaling.map((s) => ({
|
|
2567
|
+
class: s.class,
|
|
2568
|
+
init_std: s.initStd,
|
|
2569
|
+
adam_lr: s.adamLr,
|
|
2570
|
+
paths: s.paths,
|
|
2571
|
+
why: s.why
|
|
2572
|
+
})),
|
|
2573
|
+
notes: rung.notes
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2543
2576
|
const text = [
|
|
2544
2577
|
`${record.doc.meta.name} laddered by ${ladder.widthSymbol}, tuned at ${ladder.baseWidth}, ` + `heads of ${ladder.headDim} throughout`,
|
|
2545
2578
|
...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 +2621,8 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2588
2621
|
notes: z3.array(z3.string())
|
|
2589
2622
|
}),
|
|
2590
2623
|
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);
|
|
2624
|
+
}, async ({ design_id, gpus, gpus_per_node, headroom, limit, ...rest }) => guard(async () => {
|
|
2625
|
+
const record = await store.get(design_id);
|
|
2593
2626
|
const result = planCluster(record.doc, toAnalysisOptions(rest), {
|
|
2594
2627
|
gpus,
|
|
2595
2628
|
...gpus_per_node !== undefined ? { gpusPerNode: gpus_per_node } : {},
|
|
@@ -2665,9 +2698,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2665
2698
|
}))
|
|
2666
2699
|
}),
|
|
2667
2700
|
annotations: { ...READ, title: "Compare two designs" }
|
|
2668
|
-
}, async ({ a, b, ...rest }) => guard(() => {
|
|
2669
|
-
const left = store.get(a);
|
|
2670
|
-
const right = store.get(b);
|
|
2701
|
+
}, async ({ a, b, ...rest }) => guard(async () => {
|
|
2702
|
+
const left = await store.get(a);
|
|
2703
|
+
const right = await store.get(b);
|
|
2671
2704
|
const d = diffDesigns(left.doc, right.doc, toAnalysisOptions(rest));
|
|
2672
2705
|
const brief = (v) => {
|
|
2673
2706
|
if (v === undefined || v === null)
|
|
@@ -2733,9 +2766,9 @@ ${generated.warnings.map((w) => ` ${w}`).join(`
|
|
|
2733
2766
|
warnings: z3.array(z3.string())
|
|
2734
2767
|
}),
|
|
2735
2768
|
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, title: "Import a config" }
|
|
2736
|
-
}, async ({ config, name }) => guard(() => {
|
|
2769
|
+
}, async ({ config, name }) => guard(async () => {
|
|
2737
2770
|
const result = importHfConfig(config, name);
|
|
2738
|
-
const record = store.adopt(result.doc);
|
|
2771
|
+
const record = await store.adopt(result.doc);
|
|
2739
2772
|
const total = analyze2(result.doc).params.total;
|
|
2740
2773
|
const text = [
|
|
2741
2774
|
`${result.doc.meta.name}: ${formatCount2(total)} parameters`,
|
|
@@ -2798,13 +2831,14 @@ var INSTRUCTIONS = [
|
|
|
2798
2831
|
].join(`
|
|
2799
2832
|
`);
|
|
2800
2833
|
function createServer2(options = {}) {
|
|
2801
|
-
const { store: given, bridge, ...storeOptions } = options;
|
|
2834
|
+
const { store: given, bridge, artifacts: givenArtifacts, ...storeOptions } = options;
|
|
2802
2835
|
const store = given ?? new FileStore(storeOptions);
|
|
2836
|
+
const artifacts = givenArtifacts ?? new DiskSink(storeOptions.root ?? process.cwd());
|
|
2803
2837
|
const server = new McpServer2({ name: SERVER_NAME, version: SERVER_VERSION }, {
|
|
2804
2838
|
capabilities: { tools: {}, resources: {}, prompts: {}, completions: {} },
|
|
2805
2839
|
instructions: INSTRUCTIONS
|
|
2806
2840
|
});
|
|
2807
|
-
registerTools(server, store);
|
|
2841
|
+
registerTools(server, store, artifacts);
|
|
2808
2842
|
registerResources(server, store);
|
|
2809
2843
|
registerPrompts(server);
|
|
2810
2844
|
if (bridge)
|
|
@@ -2863,11 +2897,14 @@ async function startBridge(store, root) {
|
|
|
2863
2897
|
return bridge;
|
|
2864
2898
|
}
|
|
2865
2899
|
export {
|
|
2900
|
+
DiskSink,
|
|
2866
2901
|
FileStore,
|
|
2867
2902
|
PROMPT_NAMES,
|
|
2903
|
+
RevisionConflictError,
|
|
2868
2904
|
SERVER_NAME,
|
|
2869
2905
|
SERVER_VERSION,
|
|
2870
2906
|
TOOL_NAMES,
|
|
2907
|
+
UnknownDesignError,
|
|
2871
2908
|
applyOps,
|
|
2872
2909
|
createServer2 as createServer,
|
|
2873
2910
|
serve
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tensor-cad/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Model Context Protocol server for TensorCAD: design, validate, analyze and generate LLM architectures from an agent",
|
|
5
5
|
"mcpName": "io.github.filip-pajalic/tensorcad",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
28
|
-
"@tensor-cad/engine": "0.1.
|
|
28
|
+
"@tensor-cad/engine": "0.1.4",
|
|
29
29
|
"ws": "^8.21.3",
|
|
30
30
|
"zod": "^4.2.0"
|
|
31
31
|
},
|