@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/artifacts.d.ts +46 -0
- package/bridge/server.d.ts +21 -0
- package/index.d.ts +14 -1
- package/index.js +140 -108
- package/package.json +2 -2
- package/serve.js +137 -108
- package/server.d.ts +9 -0
- package/server.json +2 -2
- package/stdio.js +137 -108
- package/store/disk-artifacts.d.ts +16 -0
- package/store/file-store.d.ts +10 -10
- package/store/types.d.ts +32 -14
- package/tools.d.ts +2 -1
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) =>
|
|
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.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 =
|
|
2295
|
+
const bytes = new TextEncoder().encode(file.contents).length;
|
|
2279
2296
|
const lines = file.contents.split(`
|
|
2280
2297
|
`).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 {
|
|
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 =
|
|
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:
|
|
2303
|
-
...
|
|
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 =
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
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.
|
|
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.
|
|
17
|
+
"version": "0.1.3",
|
|
18
18
|
"runtimeHint": "npx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|