@likec4/language-server 1.48.0 → 1.49.0
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/browser/package.json +2 -2
- package/browser-worker/package.json +2 -2
- package/dist/THIRD-PARTY-LICENSES.md +178 -0
- package/dist/_chunks/ConfigurableLayouter.mjs +1 -1956
- package/dist/_chunks/LikeC4FileSystem.mjs +3 -0
- package/dist/_chunks/LikeC4Views.mjs +34 -0
- package/dist/_chunks/ProjectsManager.mjs +1 -0
- package/dist/_chunks/WithMCPServer.mjs +481 -0
- package/dist/_chunks/icons.mjs +2 -5211
- package/dist/_chunks/{LikeC4LanguageServices.d.mts → index.d.mts} +1836 -707
- package/dist/_chunks/libs/@msgpack/msgpack.mjs +1 -805
- package/dist/_chunks/libs/eventemitter3.mjs +1 -243
- package/dist/_chunks/libs/fast-equals.mjs +1 -446
- package/dist/_chunks/libs/p-queue.mjs +1 -449
- package/dist/_chunks/libs/parse-ms.mjs +1 -36
- package/dist/_chunks/libs/picomatch.mjs +1 -1673
- package/dist/_chunks/libs/pretty-ms.mjs +1 -80
- package/dist/_chunks/libs/remeda.mjs +1 -482
- package/dist/_chunks/libs/strip-indent.mjs +1 -15
- package/dist/_chunks/libs/ufo.mjs +1 -166
- package/dist/_chunks/logger.mjs +1 -0
- package/dist/_chunks/rolldown-runtime.mjs +1 -42
- package/dist/_chunks/utils.mjs +1 -0
- package/dist/browser/index.d.mts +10 -0
- package/dist/browser/index.mjs +1 -0
- package/dist/browser/worker.mjs +1 -0
- package/dist/bundled.d.mts +2 -3
- package/dist/bundled.mjs +1 -51
- package/dist/filesystem/index.d.mts +2 -4
- package/dist/filesystem/index.mjs +1 -3
- package/dist/index.d.mts +38 -3
- package/dist/index.mjs +1 -48
- package/dist/likec4lib.d.mts +10 -3
- package/dist/likec4lib.mjs +1 -4
- package/dist/mcp/index.d.mts +2 -4
- package/dist/mcp/index.mjs +1 -3
- package/dist/module.d.mts +126 -4
- package/dist/module.mjs +1 -3
- package/dist/protocol.d.mts +314 -1
- package/dist/protocol.mjs +1 -3
- package/filesystem/package.json +4 -0
- package/mcp/package.json +4 -0
- package/module/package.json +4 -0
- package/package.json +79 -56
- package/LICENSE +0 -21
- package/dist/LikeC4LanguageServices.d.mts +0 -4
- package/dist/LikeC4LanguageServices.mjs +0 -3
- package/dist/_chunks/LikeC4LanguageServices.mjs +0 -725
- package/dist/_chunks/ast.d.mts +0 -1444
- package/dist/_chunks/ast.mjs +0 -2375
- package/dist/_chunks/ast2.mjs +0 -176
- package/dist/_chunks/common-exports.mjs +0 -0
- package/dist/_chunks/filesystem.mjs +0 -58
- package/dist/_chunks/grammar.mjs +0 -8
- package/dist/_chunks/libs/@hono/node-server.mjs +0 -436
- package/dist/_chunks/libs/hono.mjs +0 -1829
- package/dist/_chunks/likec4lib.mjs +0 -9
- package/dist/_chunks/mcp.mjs +0 -33
- package/dist/_chunks/module.mjs +0 -28
- package/dist/_chunks/module2.mjs +0 -6576
- package/dist/_chunks/protocol.d.mts +0 -311
- package/dist/_chunks/protocol.mjs +0 -78
- package/dist/ast.d.mts +0 -4
- package/dist/ast.mjs +0 -4
- package/dist/browser-worker.mjs +0 -6
- package/dist/browser.d.mts +0 -11
- package/dist/browser.mjs +0 -27
- package/dist/common-exports.d.mts +0 -4
- package/dist/common-exports.mjs +0 -5
- package/dist/generated/ast.d.mts +0 -2
- package/dist/generated/ast.mjs +0 -3
- package/dist/generated/grammar.d.mts +0 -6
- package/dist/generated/grammar.mjs +0 -3
- package/dist/generated/module.d.mts +0 -14
- package/dist/generated/module.mjs +0 -3
- package/dist/generated-lib/icons.d.mts +0 -4
- package/dist/generated-lib/icons.mjs +0 -3
- /package/dist/{browser-worker.d.mts → browser/worker.d.mts} +0 -0
|
@@ -1,1956 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as isLikeC4Builtin } from "./likec4lib.mjs";
|
|
3
|
-
import { n as ProjectsManager, o as logger$4 } from "./LikeC4LanguageServices.mjs";
|
|
4
|
-
import { t as PQueue } from "./libs/p-queue.mjs";
|
|
5
|
-
import { C as t, f as t$1, o as e, y as e$1 } from "./libs/remeda.mjs";
|
|
6
|
-
import { r as LikeC4LanguageMetaData } from "./module.mjs";
|
|
7
|
-
import { n as NoFileSystemWatcher } from "./filesystem.mjs";
|
|
8
|
-
import { t as serve } from "./libs/@hono/node-server.mjs";
|
|
9
|
-
import { n as Hono, t as cors } from "./libs/hono.mjs";
|
|
10
|
-
import { loggable } from "@likec4/log";
|
|
11
|
-
import { isLikeC4Config, loadConfig } from "@likec4/config/node";
|
|
12
|
-
import { fdir } from "fdir";
|
|
13
|
-
import { DocumentState, URI, UriUtils, WorkspaceCache } from "langium";
|
|
14
|
-
import { NodeFileSystemProvider } from "langium/node";
|
|
15
|
-
import { mkdirSync } from "node:fs";
|
|
16
|
-
import { stat, unlink, writeFile } from "node:fs/promises";
|
|
17
|
-
import { dirname } from "node:path";
|
|
18
|
-
import { compareNaturalHierarchically, ifilter, invariant, isSameHierarchy } from "@likec4/core/utils";
|
|
19
|
-
import chokidar from "chokidar";
|
|
20
|
-
import JSON5 from "json5";
|
|
21
|
-
import { Position, Range } from "vscode-languageserver-types";
|
|
22
|
-
import { isLikeC4Config as isLikeC4Config$1 } from "@likec4/config";
|
|
23
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
|
-
import { modelConnection } from "@likec4/core/model";
|
|
25
|
-
import * as z from "zod/v3";
|
|
26
|
-
import { URI as URI$2 } from "vscode-uri";
|
|
27
|
-
import { invariant as invariant$1 } from "@likec4/core";
|
|
28
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
29
|
-
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
30
|
-
import { nanoid } from "nanoid";
|
|
31
|
-
import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
|
|
32
|
-
import { GraphvizBinaryAdapter } from "@likec4/layouts/graphviz/binary";
|
|
33
|
-
import which from "which";
|
|
34
|
-
|
|
35
|
-
//#region src/filesystem/LikeC4ManualLayouts.ts
|
|
36
|
-
const layoutsLogger = logger$4.getChild("manual-layouts");
|
|
37
|
-
/**
|
|
38
|
-
* @todo sync with vscode extension watchers
|
|
39
|
-
* (search for ".likec4.snap" references)
|
|
40
|
-
*/
|
|
41
|
-
const isManualLayoutFile = (path) => path.endsWith(".likec4.snap");
|
|
42
|
-
function fileName(view) {
|
|
43
|
-
return `${view}.likec4.snap`;
|
|
44
|
-
}
|
|
45
|
-
function getManualLayoutsOutDir(project) {
|
|
46
|
-
return UriUtils.resolvePath(project.folderUri, project.config.manualLayouts?.outDir ?? ".likec4");
|
|
47
|
-
}
|
|
48
|
-
const WithLikeC4ManualLayouts = { manualLayouts: (services) => new DefaultLikeC4ManualLayouts(services) };
|
|
49
|
-
const RELATIVE_PATH_PREFIX = "file://./";
|
|
50
|
-
var DefaultLikeC4ManualLayouts = class {
|
|
51
|
-
cache;
|
|
52
|
-
constructor(services) {
|
|
53
|
-
this.services = services;
|
|
54
|
-
this.cache = new WorkspaceCache(services, DocumentState.Validated);
|
|
55
|
-
}
|
|
56
|
-
async read(project) {
|
|
57
|
-
return await this.cache.get(project.id, async () => {
|
|
58
|
-
const logger = layoutsLogger.getChild(project.id);
|
|
59
|
-
const fs = this.services.workspace.FileSystemProvider;
|
|
60
|
-
const outDir = getManualLayoutsOutDir(project);
|
|
61
|
-
const manualLayouts = [];
|
|
62
|
-
try {
|
|
63
|
-
const files = await fs.scanDirectory(outDir, isManualLayoutFile);
|
|
64
|
-
if (files.length === 0) return null;
|
|
65
|
-
for (const file of files) if (file.isFile) try {
|
|
66
|
-
const content = await fs.readFile(file.uri);
|
|
67
|
-
const parsed = JSON5.parse(content);
|
|
68
|
-
const resolved = this.resolveIconPathsAfterRead(parsed, project.folderUri);
|
|
69
|
-
manualLayouts.push({
|
|
70
|
-
...resolved,
|
|
71
|
-
_layout: "manual"
|
|
72
|
-
});
|
|
73
|
-
} catch (err) {
|
|
74
|
-
logger.warn(`Failed to read view snapshot ${file.uri.fsPath}`, { err });
|
|
75
|
-
}
|
|
76
|
-
if (manualLayouts.length) logger.debug`read manual layouts for ${project.id}, found ${manualLayouts.length}`;
|
|
77
|
-
} catch (err) {
|
|
78
|
-
logger.warn(`Failed to read manual layouts for ${project.folderUri.fsPath}`, { err });
|
|
79
|
-
}
|
|
80
|
-
if (manualLayouts.length === 0) return null;
|
|
81
|
-
return t(manualLayouts, e("id"));
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
async write(project, layouted) {
|
|
85
|
-
const logger = layoutsLogger.getChild(project.id);
|
|
86
|
-
const outDir = getManualLayoutsOutDir(project);
|
|
87
|
-
const file = UriUtils.joinPath(outDir, fileName(layouted.id));
|
|
88
|
-
if ("manualLayout" in layouted) {
|
|
89
|
-
const { manualLayout: _, ...rest } = layouted;
|
|
90
|
-
layouted = rest;
|
|
91
|
-
}
|
|
92
|
-
const content = JSON5.stringify(this.normalizeIconPathsForWrite(layouted, project.folderUri), {
|
|
93
|
-
space: 2,
|
|
94
|
-
quote: "'"
|
|
95
|
-
});
|
|
96
|
-
const location = {
|
|
97
|
-
uri: file.toString(),
|
|
98
|
-
range: Range.create(Position.create(0, 0), Position.create(content.split("\n").length - 1, 1))
|
|
99
|
-
};
|
|
100
|
-
logger.debug`write snapshot of ${layouted.id} in project ${project.id} to ${file.fsPath}`;
|
|
101
|
-
const fs = this.services.workspace.FileSystemProvider;
|
|
102
|
-
try {
|
|
103
|
-
await fs.writeFile(file, content + "\n");
|
|
104
|
-
} catch (err) {
|
|
105
|
-
logger.warn(`Failed to write snapshot ${layouted.id} to ${file.fsPath}`, { err });
|
|
106
|
-
}
|
|
107
|
-
const projectCachesPromise = this.cache.get(project.id);
|
|
108
|
-
if (projectCachesPromise) {
|
|
109
|
-
const projectCaches = await projectCachesPromise;
|
|
110
|
-
if (projectCaches) {
|
|
111
|
-
logger.debug`update snapshot cache of ${layouted.id} in project ${project.id}`;
|
|
112
|
-
projectCaches[layouted.id] = layouted;
|
|
113
|
-
} else {
|
|
114
|
-
logger.debug`clean cache of project ${project.id}`;
|
|
115
|
-
this.cache.delete(project.id);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return location;
|
|
119
|
-
}
|
|
120
|
-
async remove(project, view) {
|
|
121
|
-
const logger = layoutsLogger.getChild(project.id);
|
|
122
|
-
const outDir = getManualLayoutsOutDir(project);
|
|
123
|
-
const file = UriUtils.joinPath(outDir, fileName(view));
|
|
124
|
-
logger.debug`delete snapshot of ${view} in project ${project.id}. File: ${file.fsPath}`;
|
|
125
|
-
const location = {
|
|
126
|
-
uri: file.toString(),
|
|
127
|
-
range: Range.create(0, 0, 0, 0)
|
|
128
|
-
};
|
|
129
|
-
try {
|
|
130
|
-
if (!await this.services.workspace.FileSystemProvider.deleteFile(file)) {
|
|
131
|
-
logger.warn`Snapshot ${view} did not exist at ${file.fsPath}`;
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
} catch (err) {
|
|
135
|
-
logger.warn(`Failed to delete snapshot ${view} from ${file.fsPath}`, { err });
|
|
136
|
-
}
|
|
137
|
-
const projectCachesPromise = this.cache.get(project.id);
|
|
138
|
-
if (projectCachesPromise) {
|
|
139
|
-
const projectCaches = await projectCachesPromise;
|
|
140
|
-
if (projectCaches) {
|
|
141
|
-
logger.debug`clean cached view ${view} in project ${project.id}`;
|
|
142
|
-
delete projectCaches[view];
|
|
143
|
-
} else {
|
|
144
|
-
logger.debug`reset empty cache of project ${project.id}`;
|
|
145
|
-
this.cache.delete(project.id);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return location;
|
|
149
|
-
}
|
|
150
|
-
clearCaches() {
|
|
151
|
-
layoutsLogger.debug`clear caches`;
|
|
152
|
-
this.cache.clear();
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* When we save snapshot - it may contain fullpath to icons on the machine it was created,
|
|
156
|
-
* that is wrong when opened on another.
|
|
157
|
-
*
|
|
158
|
-
* Prepares a snapshot for writing by converting absolute icon paths to relative paths.
|
|
159
|
-
* Absolute paths starting with 'file://' are converted to relative paths prefixed with 'file://./'
|
|
160
|
-
*/
|
|
161
|
-
normalizeIconPathsForWrite(layouted, projectUri) {
|
|
162
|
-
const nodes = layouted.nodes.map((node) => {
|
|
163
|
-
if (!node.icon || typeof node.icon !== "string") return node;
|
|
164
|
-
if (node.icon.startsWith("file://")) {
|
|
165
|
-
const iconUri = URI.parse(node.icon);
|
|
166
|
-
const relativePath = UriUtils.relative(projectUri, iconUri);
|
|
167
|
-
if (relativePath.startsWith("..")) return node;
|
|
168
|
-
return {
|
|
169
|
-
...node,
|
|
170
|
-
icon: `${RELATIVE_PATH_PREFIX}${relativePath}`
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
return node;
|
|
174
|
-
});
|
|
175
|
-
return {
|
|
176
|
-
...layouted,
|
|
177
|
-
nodes
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Postprocesses a snapshot after reading by converting relative icon paths back to absolute paths.
|
|
182
|
-
* Relative paths prefixed with 'file://./' are converted to absolute paths based on project folder.
|
|
183
|
-
*/
|
|
184
|
-
resolveIconPathsAfterRead(layouted, projectUri) {
|
|
185
|
-
const nodes = layouted.nodes.map((node) => {
|
|
186
|
-
if (!node.icon || typeof node.icon !== "string") return node;
|
|
187
|
-
if (node.icon.startsWith(RELATIVE_PATH_PREFIX)) {
|
|
188
|
-
const relativePath = node.icon.substring(9);
|
|
189
|
-
const absoluteUri = UriUtils.joinPath(projectUri, relativePath);
|
|
190
|
-
return {
|
|
191
|
-
...node,
|
|
192
|
-
icon: absoluteUri.toString()
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
return node;
|
|
196
|
-
});
|
|
197
|
-
return {
|
|
198
|
-
...layouted,
|
|
199
|
-
nodes
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
//#endregion
|
|
205
|
-
//#region src/filesystem/utils.ts
|
|
206
|
-
function isLikeC4File(path, isDirectory = false) {
|
|
207
|
-
return !isDirectory && LikeC4LanguageMetaData.fileExtensions.some((ext) => path.endsWith(ext));
|
|
208
|
-
}
|
|
209
|
-
function isLikeC4ConfigFile(path, isDirectory) {
|
|
210
|
-
return !isDirectory && isLikeC4Config$1(path);
|
|
211
|
-
}
|
|
212
|
-
function excludeNodeModules(dirName) {
|
|
213
|
-
return [
|
|
214
|
-
"node_modules",
|
|
215
|
-
".git",
|
|
216
|
-
".svn",
|
|
217
|
-
".yarn",
|
|
218
|
-
".pnpm"
|
|
219
|
-
].includes(dirName);
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Compare function for document paths to ensure consistent order
|
|
223
|
-
*/
|
|
224
|
-
const compare = compareNaturalHierarchically("/");
|
|
225
|
-
function ensureOrder(a, b) {
|
|
226
|
-
return compare(a.uri.path, b.uri.path);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
//#endregion
|
|
230
|
-
//#region src/filesystem/ChokidarWatcher.ts
|
|
231
|
-
const logger$3 = logger$4.getChild("chokidar");
|
|
232
|
-
const WithChokidarWatcher = { fileSystemWatcher: (services) => new ChokidarFileSystemWatcher(services) };
|
|
233
|
-
const isAnyLikeC4File = (path) => isLikeC4File(path) || isLikeC4Config(path) || isManualLayoutFile(path);
|
|
234
|
-
/**
|
|
235
|
-
* A no-op file system watcher.
|
|
236
|
-
*/
|
|
237
|
-
var ChokidarFileSystemWatcher = class {
|
|
238
|
-
watcher;
|
|
239
|
-
queue = new PQueue({
|
|
240
|
-
concurrency: 1,
|
|
241
|
-
timeout: 5e3
|
|
242
|
-
});
|
|
243
|
-
constructor(services) {
|
|
244
|
-
this.services = services;
|
|
245
|
-
}
|
|
246
|
-
watch(folder) {
|
|
247
|
-
if (this.watcher) {
|
|
248
|
-
logger$3.debug`add watching folder: ${folder}`;
|
|
249
|
-
this.watcher.add(folder);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
this.watcher = this.createWatcher(folder);
|
|
253
|
-
}
|
|
254
|
-
async dispose() {
|
|
255
|
-
if (this.watcher) {
|
|
256
|
-
const watcher = this.watcher;
|
|
257
|
-
this.watcher = void 0;
|
|
258
|
-
await watcher.close();
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
createWatcher(folder) {
|
|
262
|
-
logger$3.debug`create watcher for folder: ${folder}`;
|
|
263
|
-
let watcher = chokidar.watch(folder, {
|
|
264
|
-
ignored: [(path) => path.includes("node_modules") || path.includes(".git"), (path, stats) => !!stats && stats.isFile() && !isAnyLikeC4File(path)],
|
|
265
|
-
followSymlinks: true,
|
|
266
|
-
ignoreInitial: true
|
|
267
|
-
});
|
|
268
|
-
const onAddOrChange = (path, stats) => {
|
|
269
|
-
if (stats?.isDirectory()) return;
|
|
270
|
-
this.enqueueFileOp("addOrChange: " + path, async () => {
|
|
271
|
-
await this.onAddOrChange(path);
|
|
272
|
-
});
|
|
273
|
-
};
|
|
274
|
-
const onRemove = (path, stats) => {
|
|
275
|
-
if (stats?.isDirectory()) return;
|
|
276
|
-
this.enqueueFileOp("remove: " + path, async () => {
|
|
277
|
-
await this.onRemove(path);
|
|
278
|
-
});
|
|
279
|
-
};
|
|
280
|
-
watcher.on("add", onAddOrChange).on("change", onAddOrChange).on("unlink", onRemove).on("unlinkDir", (path) => {
|
|
281
|
-
this.enqueueFileOp("removeDir: " + path, async () => {
|
|
282
|
-
await this.onRemoveDir(path);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
return watcher;
|
|
286
|
-
}
|
|
287
|
-
enqueueFileOp(fileop, fn) {
|
|
288
|
-
this.queue.add(async () => {
|
|
289
|
-
try {
|
|
290
|
-
await fn();
|
|
291
|
-
} catch (error) {
|
|
292
|
-
logger$3.warn(`Failed on ${fileop}`, { error });
|
|
293
|
-
}
|
|
294
|
-
}).catch((error) => {
|
|
295
|
-
logger$3.error(`Error on ${fileop}`, { error });
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
async onAddOrChange(path) {
|
|
299
|
-
const workspace = this.services.workspace;
|
|
300
|
-
switch (true) {
|
|
301
|
-
case isLikeC4Config(path):
|
|
302
|
-
logger$3.debug`project file changed: ${path}`;
|
|
303
|
-
await workspace.ProjectsManager.registerConfigFile(URI.file(path));
|
|
304
|
-
break;
|
|
305
|
-
case isLikeC4File(path):
|
|
306
|
-
logger$3.debug`file changed: ${path}`;
|
|
307
|
-
await workspace.DocumentBuilder.update([URI.file(path)], []);
|
|
308
|
-
break;
|
|
309
|
-
case isManualLayoutFile(path): {
|
|
310
|
-
logger$3.debug`manual layout file changed: ${path}`;
|
|
311
|
-
workspace.ManualLayouts.clearCaches();
|
|
312
|
-
const projectId = workspace.ProjectsManager.belongsTo(URI.file(path));
|
|
313
|
-
await workspace.ProjectsManager.rebuidProject(projectId);
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
default: logger$3.warn`Unknown file change: ${path}`;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
async onRemove(path) {
|
|
320
|
-
const workspace = this.services.workspace;
|
|
321
|
-
switch (true) {
|
|
322
|
-
case isLikeC4Config(path):
|
|
323
|
-
logger$3.debug`project file removed: ${path}`;
|
|
324
|
-
await workspace.ProjectsManager.reloadProjects();
|
|
325
|
-
break;
|
|
326
|
-
case isLikeC4File(path):
|
|
327
|
-
logger$3.debug`file removed: ${path}`;
|
|
328
|
-
await workspace.DocumentBuilder.update([], [URI.file(path)]);
|
|
329
|
-
break;
|
|
330
|
-
case isManualLayoutFile(path): {
|
|
331
|
-
logger$3.debug`manual layout file removed: ${path}`;
|
|
332
|
-
const project = workspace.ProjectsManager.belongsTo(path);
|
|
333
|
-
workspace.ManualLayouts.clearCaches();
|
|
334
|
-
await workspace.ProjectsManager.rebuidProject(project);
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
default: logger$3.warn`Unknown file removal: ${path}`;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
async onRemoveDir(path) {
|
|
341
|
-
logger$3.debug`directory removed: ${path}`;
|
|
342
|
-
const workspace = this.services.workspace;
|
|
343
|
-
if (workspace.ProjectsManager.findAllProjectsByFolder(path).length > 0) {
|
|
344
|
-
workspace.ManualLayouts.clearCaches();
|
|
345
|
-
await workspace.ProjectsManager.reloadProjects();
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
//#endregion
|
|
351
|
-
//#region src/filesystem/LikeC4FileSystem.ts
|
|
352
|
-
const logger$2 = logger$4.getChild("filesystem");
|
|
353
|
-
const WithFileSystem = (ehableWatcher = true) => ({
|
|
354
|
-
fileSystemProvider: () => new SymLinkTraversingFileSystemProvider(),
|
|
355
|
-
...ehableWatcher ? WithChokidarWatcher : NoFileSystemWatcher
|
|
356
|
-
});
|
|
357
|
-
/**
|
|
358
|
-
* A file system provider that follows symbolic links.
|
|
359
|
-
* @see https://github.com/likec4/likec4/pull/1213
|
|
360
|
-
*/
|
|
361
|
-
var SymLinkTraversingFileSystemProvider = class extends NodeFileSystemProvider {
|
|
362
|
-
async readFile(uri) {
|
|
363
|
-
if (isLikeC4Builtin(uri)) return Promise.resolve(LibIcons);
|
|
364
|
-
try {
|
|
365
|
-
return await super.readFile(uri);
|
|
366
|
-
} catch (error) {
|
|
367
|
-
logger$2.warn(`Failed to read file ${uri.fsPath}`, { error });
|
|
368
|
-
return "";
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
async readDirectory(folderPath, opts) {
|
|
372
|
-
const recursive = opts?.recursive ?? true;
|
|
373
|
-
const maxDepth = opts?.maxDepth ?? Infinity;
|
|
374
|
-
const entries = [];
|
|
375
|
-
try {
|
|
376
|
-
let crawler = new fdir().withSymlinks({ resolvePaths: false }).exclude(excludeNodeModules).filter(isLikeC4File).withFullPaths();
|
|
377
|
-
if (!recursive) crawler = crawler.withMaxDepth(1);
|
|
378
|
-
else if (maxDepth !== Infinity) crawler = crawler.withMaxDepth(maxDepth);
|
|
379
|
-
const crawled = await crawler.crawl(folderPath.fsPath).withPromise();
|
|
380
|
-
for (const path of crawled) entries.push({
|
|
381
|
-
isFile: true,
|
|
382
|
-
isDirectory: false,
|
|
383
|
-
uri: URI.file(path)
|
|
384
|
-
});
|
|
385
|
-
} catch (error) {
|
|
386
|
-
logger$2.warn(`Failed to read directory ${folderPath.fsPath}`, { error });
|
|
387
|
-
}
|
|
388
|
-
return entries.sort(ensureOrder);
|
|
389
|
-
}
|
|
390
|
-
async scanProjectFiles(folderUri) {
|
|
391
|
-
return await this.scanDirectory(folderUri, isLikeC4ConfigFile);
|
|
392
|
-
}
|
|
393
|
-
async scanDirectory(directory, filter) {
|
|
394
|
-
const entries = [];
|
|
395
|
-
try {
|
|
396
|
-
const crawled = await new fdir().withSymlinks({ resolvePaths: false }).withFullPaths().filter(filter).exclude(excludeNodeModules).crawl(directory.fsPath).withPromise();
|
|
397
|
-
for (const path of crawled) entries.push({
|
|
398
|
-
isFile: true,
|
|
399
|
-
isDirectory: false,
|
|
400
|
-
uri: URI.file(path)
|
|
401
|
-
});
|
|
402
|
-
} catch (error) {
|
|
403
|
-
logger$2.warn(`Failed to scan directory ${directory.fsPath}`, { error });
|
|
404
|
-
}
|
|
405
|
-
return entries.sort(ensureOrder);
|
|
406
|
-
}
|
|
407
|
-
async loadProjectConfig(filepath) {
|
|
408
|
-
return await loadConfig(filepath);
|
|
409
|
-
}
|
|
410
|
-
async writeFile(uri, content) {
|
|
411
|
-
const dir = dirname(uri.fsPath);
|
|
412
|
-
const exists = await stat(dir).catch(() => null);
|
|
413
|
-
if (exists?.isFile()) throw new Error(`Cannot create directory ${dir} because a file with the same name exists.`);
|
|
414
|
-
if (!exists) {
|
|
415
|
-
logger$2.debug("creating directory {path}", { path: dir });
|
|
416
|
-
mkdirSync(dir, { recursive: true });
|
|
417
|
-
}
|
|
418
|
-
logger$2.debug("writing file {path}", { path: uri.fsPath });
|
|
419
|
-
return await writeFile(uri.fsPath, content, { encoding: "utf-8" });
|
|
420
|
-
}
|
|
421
|
-
async deleteFile(uri) {
|
|
422
|
-
try {
|
|
423
|
-
const path = uri.fsPath;
|
|
424
|
-
const exists = await stat(path);
|
|
425
|
-
if (exists.isFile() || exists.isSymbolicLink()) {
|
|
426
|
-
await unlink(path);
|
|
427
|
-
logger$2.debug("deleted file {path}", { path });
|
|
428
|
-
return true;
|
|
429
|
-
} else logger$2.warn("deleteFile failed: {path} does not exist, or is not a file", { path });
|
|
430
|
-
} catch (error) {
|
|
431
|
-
logger$2.warn(`Failed to delete file ${uri.fsPath}`, { error });
|
|
432
|
-
}
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
//#endregion
|
|
438
|
-
//#region src/mcp/utils.ts
|
|
439
|
-
const logger$1 = logger$4.getChild("mcp");
|
|
440
|
-
function likec4Tool(config, cb) {
|
|
441
|
-
const { name, description, ...rest } = config;
|
|
442
|
-
return (languageServices) => [
|
|
443
|
-
name,
|
|
444
|
-
{
|
|
445
|
-
description: description?.trim() ?? "",
|
|
446
|
-
...rest
|
|
447
|
-
},
|
|
448
|
-
mkcallTool(name, languageServices, cb)
|
|
449
|
-
];
|
|
450
|
-
}
|
|
451
|
-
function mkcallTool(name, languageServices, cb) {
|
|
452
|
-
const tool = cb.bind(null, languageServices);
|
|
453
|
-
return (async function callTool(args, extra) {
|
|
454
|
-
logger$1.debug("Calling tool {name}, args: {args}", {
|
|
455
|
-
name,
|
|
456
|
-
args
|
|
457
|
-
});
|
|
458
|
-
try {
|
|
459
|
-
const result = await tool.call(null, args, extra);
|
|
460
|
-
if (typeof result === "string") return { content: [{
|
|
461
|
-
type: "text",
|
|
462
|
-
text: result
|
|
463
|
-
}] };
|
|
464
|
-
return {
|
|
465
|
-
content: [{
|
|
466
|
-
type: "text",
|
|
467
|
-
text: JSON.stringify(result)
|
|
468
|
-
}],
|
|
469
|
-
structuredContent: result
|
|
470
|
-
};
|
|
471
|
-
} catch (err) {
|
|
472
|
-
logger$1.error(`Tool ${name} failed`, { err });
|
|
473
|
-
return {
|
|
474
|
-
content: [{
|
|
475
|
-
type: "text",
|
|
476
|
-
text: loggable(err)
|
|
477
|
-
}],
|
|
478
|
-
isError: true
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
//#endregion
|
|
485
|
-
//#region package.json
|
|
486
|
-
var version = "1.48.0";
|
|
487
|
-
|
|
488
|
-
//#endregion
|
|
489
|
-
//#region src/mcp/tools/_common.ts
|
|
490
|
-
const locationSchema = z.object({
|
|
491
|
-
path: z.string().describe("Path to the file"),
|
|
492
|
-
range: z.object({
|
|
493
|
-
start: z.object({
|
|
494
|
-
line: z.number(),
|
|
495
|
-
character: z.number()
|
|
496
|
-
}),
|
|
497
|
-
end: z.object({
|
|
498
|
-
line: z.number(),
|
|
499
|
-
character: z.number()
|
|
500
|
-
})
|
|
501
|
-
}).describe("Range in the file")
|
|
502
|
-
}).nullable();
|
|
503
|
-
const projectIdSchema = z.string().refine((_v) => true).optional().default(ProjectsManager.DefaultProjectId).describe("Project id (optional, will use \"default\" if not specified)");
|
|
504
|
-
const includedInViewsSchema = z.array(z.object({
|
|
505
|
-
id: z.string().describe("View id"),
|
|
506
|
-
title: z.string().describe("View title"),
|
|
507
|
-
type: z.enum([
|
|
508
|
-
"element",
|
|
509
|
-
"deployment",
|
|
510
|
-
"dynamic"
|
|
511
|
-
]).describe("View type")
|
|
512
|
-
}));
|
|
513
|
-
const includedInViews = (views) => {
|
|
514
|
-
return [...views].map((v) => ({
|
|
515
|
-
id: v.id,
|
|
516
|
-
title: v.titleOrId,
|
|
517
|
-
type: v.$view._type
|
|
518
|
-
}));
|
|
519
|
-
};
|
|
520
|
-
const mkLocate = (languageServices, projectId) => (params) => {
|
|
521
|
-
try {
|
|
522
|
-
const loc = languageServices.locate({
|
|
523
|
-
projectId,
|
|
524
|
-
...params
|
|
525
|
-
});
|
|
526
|
-
return loc ? {
|
|
527
|
-
path: URI$2.parse(loc.uri).fsPath,
|
|
528
|
-
range: loc.range
|
|
529
|
-
} : null;
|
|
530
|
-
} catch (e) {
|
|
531
|
-
logger$1.debug(`Failed to locate {params}`, {
|
|
532
|
-
error: e,
|
|
533
|
-
params
|
|
534
|
-
});
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
//#endregion
|
|
540
|
-
//#region src/mcp/tools/find-relationships.ts
|
|
541
|
-
const endpointSchema = z.object({
|
|
542
|
-
id: z.string(),
|
|
543
|
-
title: z.string(),
|
|
544
|
-
kind: z.string()
|
|
545
|
-
});
|
|
546
|
-
const searchResultSchema$1 = z.object({
|
|
547
|
-
type: z.enum(["direct", "indirect"]).describe("Type of relationship, \"direct\" for direct relationships, \"indirect\" for relationships through nested elements"),
|
|
548
|
-
source: endpointSchema,
|
|
549
|
-
target: endpointSchema,
|
|
550
|
-
kind: z.string().nullable().describe("Relationship kind"),
|
|
551
|
-
title: z.string().nullable().describe("Relationship title"),
|
|
552
|
-
description: z.string().nullable().describe("Relationship description"),
|
|
553
|
-
technology: z.string().nullable().describe("Relationship technology"),
|
|
554
|
-
tags: z.array(z.string()).describe("Relationship tags"),
|
|
555
|
-
includedInViews: includedInViewsSchema.describe("Views that include this relationship"),
|
|
556
|
-
sourceLocation: locationSchema
|
|
557
|
-
});
|
|
558
|
-
const findRelationships = likec4Tool({
|
|
559
|
-
name: "find-relationships",
|
|
560
|
-
annotations: {
|
|
561
|
-
readOnlyHint: true,
|
|
562
|
-
idempotentHint: true,
|
|
563
|
-
title: "Find relationships between two elements"
|
|
564
|
-
},
|
|
565
|
-
description: `
|
|
566
|
-
Find relationships between two LikeC4 elements within a project.
|
|
567
|
-
|
|
568
|
-
What it does:
|
|
569
|
-
- Finds both direct relationships (element1 ↔ element2) and indirect ones that arise via containment (e.g. via nested elements).
|
|
570
|
-
- Returns rich metadata for each relationship and where it appears in views.
|
|
571
|
-
|
|
572
|
-
Inputs:
|
|
573
|
-
- element1: string — Element ID (FQN)
|
|
574
|
-
- element2: string — Element ID (FQN)
|
|
575
|
-
- project: string (optional, defaults to "default") — Project id
|
|
576
|
-
|
|
577
|
-
Output:
|
|
578
|
-
- found: Relationship[]
|
|
579
|
-
|
|
580
|
-
Relationship (object) fields:
|
|
581
|
-
- type: "direct" | "indirect" — direct is between the specified endpoints; indirect is via nested elements
|
|
582
|
-
- source: Endpoint
|
|
583
|
-
- target: Endpoint
|
|
584
|
-
- kind: string|null — relationship kind from the model
|
|
585
|
-
- title: string|null — relationship title if provided
|
|
586
|
-
- description: string|null — relationship description text
|
|
587
|
-
- technology: string|null — relationship technology
|
|
588
|
-
- tags: string[] — relationship tags
|
|
589
|
-
- includedInViews: View[] — views where this relationship appears
|
|
590
|
-
- sourceLocation: { path: string, range: { start: { line: number, character: number }, end: { line: number, character: number } } } | null
|
|
591
|
-
|
|
592
|
-
Endpoint (object) fields:
|
|
593
|
-
- id: string — Element ID (FQN)
|
|
594
|
-
- title: string — element title
|
|
595
|
-
- kind: string — element kind
|
|
596
|
-
|
|
597
|
-
View (object) fields:
|
|
598
|
-
- id: string — view identifier
|
|
599
|
-
- title: string — view title
|
|
600
|
-
- type: "element" | "deployment" | "dynamic"
|
|
601
|
-
|
|
602
|
-
Notes:
|
|
603
|
-
- Read-only, idempotent; does not mutate the model. May trigger UI navigation in supporting clients.
|
|
604
|
-
- The order of results is not guaranteed.
|
|
605
|
-
|
|
606
|
-
Example:
|
|
607
|
-
Request:
|
|
608
|
-
{
|
|
609
|
-
"element1": "shop.frontend",
|
|
610
|
-
"element2": "shop.backend",
|
|
611
|
-
"project": "default"
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
Response:
|
|
615
|
-
{
|
|
616
|
-
"found": [
|
|
617
|
-
{
|
|
618
|
-
"type": "direct",
|
|
619
|
-
"source": { "id": "shop.frontend", "title": "Frontend", "kind": "component" },
|
|
620
|
-
"target": { "id": "shop.backend", "title": "Backend", "kind": "component" },
|
|
621
|
-
"kind": "sync",
|
|
622
|
-
"title": "Calls",
|
|
623
|
-
"description": "Frontend calls Backend",
|
|
624
|
-
"technology": "HTTP",
|
|
625
|
-
"tags": ["public"],
|
|
626
|
-
"includedInViews": [
|
|
627
|
-
{ "id": "system-overview", "title": "System Overview", "type": "element" }
|
|
628
|
-
],
|
|
629
|
-
"sourceLocation": {
|
|
630
|
-
"path": "/abs/path/project/model.c4",
|
|
631
|
-
"range": { "start": { "line": 12, "character": 0 }, "end": { "line": 14, "character": 0 } }
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
]
|
|
635
|
-
}
|
|
636
|
-
`,
|
|
637
|
-
inputSchema: {
|
|
638
|
-
element1: z.string().describe("Element ID (FQN)"),
|
|
639
|
-
element2: z.string().describe("Element ID (FQN)"),
|
|
640
|
-
project: projectIdSchema
|
|
641
|
-
},
|
|
642
|
-
outputSchema: { found: z.array(searchResultSchema$1) }
|
|
643
|
-
}, async (languageServices, args) => {
|
|
644
|
-
const projectId = languageServices.projectsManager.ensureProjectId(args.project);
|
|
645
|
-
if (isSameHierarchy(args.element1, args.element2)) throw new Error("No relationships possible between parent-child");
|
|
646
|
-
const found = [];
|
|
647
|
-
const model = await languageServices.computedModel(projectId);
|
|
648
|
-
const el1 = model.findElement(args.element1);
|
|
649
|
-
invariant(el1, `Element "${args.element1}" not found in project "${projectId}"`);
|
|
650
|
-
const el2 = model.findElement(args.element2);
|
|
651
|
-
invariant(el2, `Element "${args.element2}" not found in project "${projectId}"`);
|
|
652
|
-
const locate = mkLocate(languageServices, projectId);
|
|
653
|
-
const relationships = modelConnection.findConnection(el1, el2, "both").flatMap((c) => [...c.relations]);
|
|
654
|
-
for (const relationship of relationships) {
|
|
655
|
-
const isDirect = relationship.source === el1 && relationship.target === el2 || relationship.source === el2 && relationship.target === el1;
|
|
656
|
-
found.push({
|
|
657
|
-
type: isDirect ? "direct" : "indirect",
|
|
658
|
-
source: {
|
|
659
|
-
id: relationship.source.id,
|
|
660
|
-
title: relationship.source.title,
|
|
661
|
-
kind: relationship.source.kind
|
|
662
|
-
},
|
|
663
|
-
target: {
|
|
664
|
-
id: relationship.target.id,
|
|
665
|
-
title: relationship.target.title,
|
|
666
|
-
kind: relationship.target.kind
|
|
667
|
-
},
|
|
668
|
-
kind: relationship.kind,
|
|
669
|
-
title: relationship.title,
|
|
670
|
-
description: relationship.description.text,
|
|
671
|
-
technology: relationship.technology,
|
|
672
|
-
tags: [...relationship.tags],
|
|
673
|
-
includedInViews: includedInViews(relationship.views()),
|
|
674
|
-
sourceLocation: locate({ relation: relationship.id })
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
return { found };
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
//#endregion
|
|
681
|
-
//#region src/mcp/tools/list-projects.ts
|
|
682
|
-
const listProjects = likec4Tool({
|
|
683
|
-
name: "list-projects",
|
|
684
|
-
description: `
|
|
685
|
-
List LikeC4 projects discoverable in the current workspace.
|
|
686
|
-
|
|
687
|
-
Request:
|
|
688
|
-
- No input parameters.
|
|
689
|
-
|
|
690
|
-
Response (JSON object):
|
|
691
|
-
- projects: Project[]
|
|
692
|
-
|
|
693
|
-
Project (object) fields:
|
|
694
|
-
- id: string — stable project identifier
|
|
695
|
-
- title: string — human-readable project title
|
|
696
|
-
- folder: string — absolute path to the project root
|
|
697
|
-
- sources: string[] — absolute file paths of related documents
|
|
698
|
-
|
|
699
|
-
Notes:
|
|
700
|
-
- Read-only, idempotent, no side effects.
|
|
701
|
-
- Safe to call repeatedly.
|
|
702
|
-
|
|
703
|
-
Example response:
|
|
704
|
-
{
|
|
705
|
-
"projects": [
|
|
706
|
-
{
|
|
707
|
-
"id": "docs",
|
|
708
|
-
"title": "Documentation",
|
|
709
|
-
"folder": "/abs/path/to/workspace/docs",
|
|
710
|
-
"sources": [
|
|
711
|
-
"/abs/path/to/workspace/docs/model/contexts.likec4",
|
|
712
|
-
"/abs/path/to/workspace/docs/model/relations.likec4"
|
|
713
|
-
]
|
|
714
|
-
}
|
|
715
|
-
]
|
|
716
|
-
}
|
|
717
|
-
`,
|
|
718
|
-
annotations: {
|
|
719
|
-
readOnlyHint: true,
|
|
720
|
-
idempotentHint: true,
|
|
721
|
-
title: "List projects"
|
|
722
|
-
},
|
|
723
|
-
outputSchema: { projects: z.array(z.object({
|
|
724
|
-
id: z.string(),
|
|
725
|
-
title: z.string(),
|
|
726
|
-
folder: z.string(),
|
|
727
|
-
sources: z.array(z.string())
|
|
728
|
-
})) }
|
|
729
|
-
}, async (languageServices) => {
|
|
730
|
-
return { projects: languageServices.projects().map((p) => ({
|
|
731
|
-
id: p.id,
|
|
732
|
-
title: p.title,
|
|
733
|
-
folder: p.folder.fsPath,
|
|
734
|
-
sources: p.documents.map((d) => d.fsPath)
|
|
735
|
-
})) };
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
//#endregion
|
|
739
|
-
//#region src/mcp/tools/open-view.ts
|
|
740
|
-
const openView = likec4Tool({
|
|
741
|
-
name: "open-view",
|
|
742
|
-
description: `
|
|
743
|
-
Open a LikeC4 view in the editor's preview panel.
|
|
744
|
-
|
|
745
|
-
Request:
|
|
746
|
-
- viewId: string — view id (name)
|
|
747
|
-
- project: string (optional) — project id. Defaults to "default" if omitted.
|
|
748
|
-
|
|
749
|
-
Response (JSON object):
|
|
750
|
-
- location: { path: string, range: { start: { line: number, character: number }, end: { line: number, character: number } } } | null — source location of the view if available
|
|
751
|
-
|
|
752
|
-
Notes:
|
|
753
|
-
- Read-only and idempotent with respect to the project model. Triggers a UI action in the editor.
|
|
754
|
-
- Only one preview panel can be open at a time.
|
|
755
|
-
|
|
756
|
-
Example response:
|
|
757
|
-
{
|
|
758
|
-
"location": {
|
|
759
|
-
"path": "/abs/path/project/model.c4",
|
|
760
|
-
"range": { "start": { "line": 10, "character": 0 }, "end": { "line": 30, "character": 0 } }
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
`,
|
|
764
|
-
annotations: {
|
|
765
|
-
readOnlyHint: true,
|
|
766
|
-
idempotentHint: true,
|
|
767
|
-
title: "Open view in preview panel"
|
|
768
|
-
},
|
|
769
|
-
inputSchema: {
|
|
770
|
-
viewId: z.string().describe("View id (name)"),
|
|
771
|
-
project: projectIdSchema
|
|
772
|
-
},
|
|
773
|
-
outputSchema: { location: locationSchema }
|
|
774
|
-
}, async (languageServices, args) => {
|
|
775
|
-
const projectId = languageServices.projectsManager.ensureProjectId(args.project);
|
|
776
|
-
const view = (await languageServices.computedModel(projectId)).findView(args.viewId);
|
|
777
|
-
if (!view) throw new Error(`View with ID '${args.viewId}' not found in project ${projectId}`);
|
|
778
|
-
await languageServices.views.openView(view.id, projectId);
|
|
779
|
-
return { location: mkLocate(languageServices, projectId)({ view: view.id }) };
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
//#endregion
|
|
783
|
-
//#region src/mcp/tools/read-deployment.ts
|
|
784
|
-
const readDeployment = likec4Tool({
|
|
785
|
-
name: "read-deployment",
|
|
786
|
-
description: `
|
|
787
|
-
Read details about a deployment node or a deployed instance in a LikeC4 project.
|
|
788
|
-
|
|
789
|
-
What it does:
|
|
790
|
-
- Returns metadata about a deployment entity (node or instance), including kind, tags, color/shape, children, which views include it, and its source location.
|
|
791
|
-
|
|
792
|
-
Inputs:
|
|
793
|
-
- id: string — Deployment id (FQN)
|
|
794
|
-
- project: string (optional, defaults to "default") — Project id
|
|
795
|
-
|
|
796
|
-
Output fields:
|
|
797
|
-
- type: "deployment-node" | "deployed-instance"
|
|
798
|
-
- id: string — Deployment id (FQN)
|
|
799
|
-
- kind: string — Deployment node kind, or element kind for deployed instances
|
|
800
|
-
- name: string — Name of the deployment entity
|
|
801
|
-
- title: string — Title of the deployment entity
|
|
802
|
-
- description: string|null — Description text
|
|
803
|
-
- technology: string|null — Technology info, if any
|
|
804
|
-
- tags: string[] — Tags assigned to this entity
|
|
805
|
-
- project: string — Project id
|
|
806
|
-
- metadata: Record<string, string>
|
|
807
|
-
- links: Array<{ title: string|null, url: string, relative: string|null }> — external links associated with this deployment entity
|
|
808
|
-
- shape: string — Rendered shape
|
|
809
|
-
- color: string — Rendered color
|
|
810
|
-
- children: string[] — Child deployment ids (empty for instances)
|
|
811
|
-
- includedInViews: View[] — Views that include this entity
|
|
812
|
-
- instanceof: { id: string, title: string, kind: string } | null — If type is "deployed-instance", the referenced element
|
|
813
|
-
- sourceLocation: { path: string, range: { start: { line: number, character: number }, end: { line: number, character: number } } } | null
|
|
814
|
-
|
|
815
|
-
View (object) fields:
|
|
816
|
-
- id: string — view identifier
|
|
817
|
-
- title: string — view title
|
|
818
|
-
- type: "element" | "deployment" | "dynamic"
|
|
819
|
-
|
|
820
|
-
Notes:
|
|
821
|
-
- Read-only, idempotent; does not mutate the model.
|
|
822
|
-
|
|
823
|
-
Example request:
|
|
824
|
-
{ "id": "k8s.cluster.frontend", "project": "default" }
|
|
825
|
-
|
|
826
|
-
Example response (deployed instance):
|
|
827
|
-
{
|
|
828
|
-
"type": "deployed-instance",
|
|
829
|
-
"id": "k8s.cluster.frontend",
|
|
830
|
-
"kind": "k8s.pod",
|
|
831
|
-
"name": "frontend",
|
|
832
|
-
"title": "Frontend Pod",
|
|
833
|
-
"description": null,
|
|
834
|
-
"technology": "Kubernetes",
|
|
835
|
-
"tags": ["prod"],
|
|
836
|
-
"project": "default",
|
|
837
|
-
"metadata": {},
|
|
838
|
-
"links": [],
|
|
839
|
-
"shape": "rectangle",
|
|
840
|
-
"color": "#2F80ED",
|
|
841
|
-
"children": [],
|
|
842
|
-
"includedInViews": [
|
|
843
|
-
{ "id": "runtime-overview", "title": "Runtime Overview", "type": "deployment" }
|
|
844
|
-
],
|
|
845
|
-
"instanceof": { "id": "shop.frontend", "title": "Frontend", "kind": "component" },
|
|
846
|
-
"sourceLocation": {
|
|
847
|
-
"path": "/abs/path/project/model.c4",
|
|
848
|
-
"range": { "start": { "line": 10, "character": 0 }, "end": { "line": 25, "character": 0 } }
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
`,
|
|
852
|
-
annotations: {
|
|
853
|
-
readOnlyHint: true,
|
|
854
|
-
idempotentHint: true,
|
|
855
|
-
title: "Read deployment entity"
|
|
856
|
-
},
|
|
857
|
-
inputSchema: {
|
|
858
|
-
id: z.string().describe("Deployment id (FQN)"),
|
|
859
|
-
project: projectIdSchema
|
|
860
|
-
},
|
|
861
|
-
outputSchema: {
|
|
862
|
-
type: z.enum(["deployment-node", "deployed-instance"]),
|
|
863
|
-
id: z.string().describe("Deployment id (FQN)"),
|
|
864
|
-
kind: z.string().describe("Deployment node kind, or element kind for deployed instances"),
|
|
865
|
-
name: z.string(),
|
|
866
|
-
title: z.string(),
|
|
867
|
-
description: z.string().nullable(),
|
|
868
|
-
technology: z.string().nullable(),
|
|
869
|
-
tags: z.array(z.string()),
|
|
870
|
-
project: z.string(),
|
|
871
|
-
metadata: z.record(z.union([z.string(), z.array(z.string())])),
|
|
872
|
-
links: z.array(z.object({
|
|
873
|
-
title: z.string().nullable().describe("Optional link title"),
|
|
874
|
-
url: z.string().describe("Link URL"),
|
|
875
|
-
relative: z.string().nullable().describe("Relative path (if URL is relative to workspace root)")
|
|
876
|
-
})).describe("External links associated with this deployment entity"),
|
|
877
|
-
shape: z.string(),
|
|
878
|
-
color: z.string(),
|
|
879
|
-
children: z.array(z.string()).describe("Children of this deployment node (Array of Deployment ids)"),
|
|
880
|
-
includedInViews: includedInViewsSchema.describe("Views that include this deployment node"),
|
|
881
|
-
instanceof: z.object({
|
|
882
|
-
id: z.string().describe("Element ID (FQN)"),
|
|
883
|
-
title: z.string(),
|
|
884
|
-
kind: z.string()
|
|
885
|
-
}).nullable().describe("If type is \"deployed-instance\", the referenced element"),
|
|
886
|
-
sourceLocation: locationSchema
|
|
887
|
-
}
|
|
888
|
-
}, async (languageServices, args) => {
|
|
889
|
-
const projectId = languageServices.projectsManager.ensureProjectId(args.project);
|
|
890
|
-
const element = (await languageServices.computedModel(projectId)).deployment.findElement(args.id);
|
|
891
|
-
invariant$1(element, `Deployment entity "${args.id}" not found in project "${projectId}"`);
|
|
892
|
-
const locate = mkLocate(languageServices, projectId);
|
|
893
|
-
return {
|
|
894
|
-
type: element.isInstance() ? "deployed-instance" : "deployment-node",
|
|
895
|
-
id: element.id,
|
|
896
|
-
name: element.name,
|
|
897
|
-
kind: element.kind,
|
|
898
|
-
title: element.title,
|
|
899
|
-
description: element.description.text,
|
|
900
|
-
technology: element.technology,
|
|
901
|
-
tags: [...element.tags],
|
|
902
|
-
project: projectId,
|
|
903
|
-
metadata: element.getMetadata(),
|
|
904
|
-
links: (element.links ?? []).map((link) => ({
|
|
905
|
-
title: link.title ?? null,
|
|
906
|
-
url: link.url,
|
|
907
|
-
relative: link.relative ?? null
|
|
908
|
-
})),
|
|
909
|
-
shape: element.shape,
|
|
910
|
-
color: element.color,
|
|
911
|
-
children: element.isInstance() ? [] : [...element.children()].map((c) => c.id),
|
|
912
|
-
includedInViews: includedInViews(element.views()),
|
|
913
|
-
instanceof: element.isInstance() ? {
|
|
914
|
-
id: element.element.id,
|
|
915
|
-
title: element.element.title,
|
|
916
|
-
kind: element.element.kind
|
|
917
|
-
} : null,
|
|
918
|
-
sourceLocation: locate({ deployment: element.id })
|
|
919
|
-
};
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
//#endregion
|
|
923
|
-
//#region src/mcp/tools/read-element.ts
|
|
924
|
-
const readElement = likec4Tool({
|
|
925
|
-
name: "read-element",
|
|
926
|
-
description: `
|
|
927
|
-
Read detailed information about a LikeC4 element.
|
|
928
|
-
|
|
929
|
-
Request:
|
|
930
|
-
- id: string — element id (FQN)
|
|
931
|
-
- project: string (optional) — project id. Defaults to "default" if omitted.
|
|
932
|
-
|
|
933
|
-
Response (JSON object):
|
|
934
|
-
- id: string — element id (FQN)
|
|
935
|
-
- name: string — element name
|
|
936
|
-
- kind: string — element kind
|
|
937
|
-
- title: string — human-readable title
|
|
938
|
-
- description: string|null — optional description
|
|
939
|
-
- technology: string|null — optional technology
|
|
940
|
-
- tags: string[] — assigned tags
|
|
941
|
-
- project: string — project id this element belongs to
|
|
942
|
-
- metadata: Record<string, string> — element metadata
|
|
943
|
-
- links: Array<{ title: string|null, url: string, relative: string|null }> — external links associated with this element
|
|
944
|
-
- shape: string — rendered shape
|
|
945
|
-
- color: string — rendered color
|
|
946
|
-
- children: string[] — ids (FQNs) of direct child elements
|
|
947
|
-
- defaultView: string|null — default view name if set
|
|
948
|
-
- includedInViews: View[] — views that include this element
|
|
949
|
-
- relationships: object — relationships of this element (direct and indirect)
|
|
950
|
-
- incoming: Array<{ source: { id: string, title: string, kind: string }, kind: string|null, target: string, title: string|null, description: string|null, technology: string|null, tags: string[] }>
|
|
951
|
-
- outgoing: Array<{ source: string, target: { id: string, title: string, kind: string }, kind: string|null, title: string|null, description: string|null, technology: string|null, tags: string[] }>
|
|
952
|
-
- deployedInstances: string[] — deployed instance ids (Deployment FQNs)
|
|
953
|
-
- sourceLocation: { path: string, range: { start: { line: number, character: number }, end: { line: number, character: number } } } | null — source location if available
|
|
954
|
-
|
|
955
|
-
View (object) fields:
|
|
956
|
-
- id: string — view identifier
|
|
957
|
-
- title: string — view title
|
|
958
|
-
- type: "element" | "deployment" | "dynamic"
|
|
959
|
-
|
|
960
|
-
Notes:
|
|
961
|
-
- Read-only, idempotent, no side effects.
|
|
962
|
-
- Safe to call repeatedly.
|
|
963
|
-
|
|
964
|
-
Example response:
|
|
965
|
-
{
|
|
966
|
-
"id": "shop.frontend",
|
|
967
|
-
"name": "frontend",
|
|
968
|
-
"kind": "container",
|
|
969
|
-
"title": "Frontend",
|
|
970
|
-
"description": "User-facing web app",
|
|
971
|
-
"technology": "React",
|
|
972
|
-
"tags": ["public"],
|
|
973
|
-
"project": "default",
|
|
974
|
-
"metadata": { "owner": "web" },
|
|
975
|
-
"links": [
|
|
976
|
-
{
|
|
977
|
-
"title": "Documentation",
|
|
978
|
-
"url": "https://docs.example.com/frontend",
|
|
979
|
-
"relative": null
|
|
980
|
-
}
|
|
981
|
-
],
|
|
982
|
-
"shape": "rounded-rectangle",
|
|
983
|
-
"color": "#2F80ED",
|
|
984
|
-
"children": ["shop.frontend.auth"],
|
|
985
|
-
"defaultView": "frontend-overview",
|
|
986
|
-
"includedInViews": [
|
|
987
|
-
{
|
|
988
|
-
"id": "frontend-overview",
|
|
989
|
-
"title": "Frontend Overview",
|
|
990
|
-
"type": "element"
|
|
991
|
-
}
|
|
992
|
-
],
|
|
993
|
-
"relationships": {
|
|
994
|
-
"incoming": [
|
|
995
|
-
{
|
|
996
|
-
"source": { "id": "shop.api", "title": "API", "kind": "container" },
|
|
997
|
-
"kind": "uses",
|
|
998
|
-
"target": "shop.frontend",
|
|
999
|
-
"title": "Calls",
|
|
1000
|
-
"description": null,
|
|
1001
|
-
"technology": "HTTPS",
|
|
1002
|
-
"tags": []
|
|
1003
|
-
}
|
|
1004
|
-
],
|
|
1005
|
-
"outgoing": []
|
|
1006
|
-
},
|
|
1007
|
-
"deployedInstances": ["k8s.cluster.frontend"],
|
|
1008
|
-
"sourceLocation": {
|
|
1009
|
-
"path": "/abs/path/project/model.c4",
|
|
1010
|
-
"range": { "start": { "line": 10, "character": 0 }, "end": { "line": 25, "character": 0 } }
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
`,
|
|
1014
|
-
annotations: {
|
|
1015
|
-
readOnlyHint: true,
|
|
1016
|
-
idempotentHint: true,
|
|
1017
|
-
title: "Read element"
|
|
1018
|
-
},
|
|
1019
|
-
inputSchema: {
|
|
1020
|
-
id: z.string().describe("Element id (FQN)"),
|
|
1021
|
-
project: projectIdSchema
|
|
1022
|
-
},
|
|
1023
|
-
outputSchema: {
|
|
1024
|
-
id: z.string().describe("Element id (FQN)"),
|
|
1025
|
-
kind: z.string().describe("Element kind"),
|
|
1026
|
-
name: z.string().describe("Element name"),
|
|
1027
|
-
title: z.string(),
|
|
1028
|
-
description: z.string().nullable(),
|
|
1029
|
-
technology: z.string().nullable(),
|
|
1030
|
-
tags: z.array(z.string()),
|
|
1031
|
-
project: z.string(),
|
|
1032
|
-
metadata: z.record(z.union([z.string(), z.array(z.string())])),
|
|
1033
|
-
links: z.array(z.object({
|
|
1034
|
-
title: z.string().nullable().describe("Optional link title"),
|
|
1035
|
-
url: z.string().describe("Link URL"),
|
|
1036
|
-
relative: z.string().nullable().describe("Relative path (if URL is relative to workspace root)")
|
|
1037
|
-
})).describe("External links associated with this element"),
|
|
1038
|
-
shape: z.string(),
|
|
1039
|
-
color: z.string(),
|
|
1040
|
-
children: z.array(z.string()).describe("Children of this element (Array of FQNs)"),
|
|
1041
|
-
defaultView: z.string().nullable().describe("Name of the default view of this element"),
|
|
1042
|
-
includedInViews: includedInViewsSchema.describe("Views that include this element"),
|
|
1043
|
-
relationships: z.object({
|
|
1044
|
-
incoming: z.array(z.object({
|
|
1045
|
-
source: z.object({
|
|
1046
|
-
id: z.string(),
|
|
1047
|
-
title: z.string(),
|
|
1048
|
-
kind: z.string()
|
|
1049
|
-
}).describe("Source element of this relationship"),
|
|
1050
|
-
kind: z.string().nullable().describe("Relationship kind"),
|
|
1051
|
-
target: z.string().describe("Target element id (FQN), either this element or nested element, if relationship is indirect"),
|
|
1052
|
-
title: z.string().nullable().describe("Relationship title"),
|
|
1053
|
-
description: z.string().nullable().describe("Relationship description"),
|
|
1054
|
-
technology: z.string().nullable().describe("Relationship technology"),
|
|
1055
|
-
tags: z.array(z.string()).describe("Relationship tags")
|
|
1056
|
-
})).describe("Incoming relationships of this element (direct and indirect, incoming to nested elements)"),
|
|
1057
|
-
outgoing: z.array(z.object({
|
|
1058
|
-
source: z.string().describe("Source element id (FQN), either this element or nested element, if relationship is indirect"),
|
|
1059
|
-
target: z.object({
|
|
1060
|
-
id: z.string(),
|
|
1061
|
-
title: z.string(),
|
|
1062
|
-
kind: z.string()
|
|
1063
|
-
}).describe("Target element of this relationship"),
|
|
1064
|
-
kind: z.string().nullable().describe("Relationship kind"),
|
|
1065
|
-
title: z.string().nullable().describe("Relationship title"),
|
|
1066
|
-
description: z.string().nullable().describe("Relationship description"),
|
|
1067
|
-
technology: z.string().nullable().describe("Relationship technology"),
|
|
1068
|
-
tags: z.array(z.string()).describe("Relationship tags")
|
|
1069
|
-
})).describe("Outgoing relationships of this element (direct and indirect, outgoing from nested elements)")
|
|
1070
|
-
}).describe("Relationships of this element"),
|
|
1071
|
-
deployedInstances: z.array(z.string()).describe("Deployed instances of this element (Array of Deployment FQNs)"),
|
|
1072
|
-
sourceLocation: locationSchema
|
|
1073
|
-
}
|
|
1074
|
-
}, async (languageServices, args) => {
|
|
1075
|
-
const projectId = languageServices.projectsManager.ensureProjectId(args.project);
|
|
1076
|
-
const element = (await languageServices.computedModel(projectId)).findElement(args.id);
|
|
1077
|
-
invariant$1(element, `Element "${args.id}" not found in project "${projectId}"`);
|
|
1078
|
-
const locate = mkLocate(languageServices, projectId);
|
|
1079
|
-
return {
|
|
1080
|
-
id: element.id,
|
|
1081
|
-
name: element.name,
|
|
1082
|
-
kind: element.kind,
|
|
1083
|
-
title: element.title,
|
|
1084
|
-
description: element.description.text,
|
|
1085
|
-
technology: element.technology,
|
|
1086
|
-
tags: [...element.tags],
|
|
1087
|
-
project: projectId,
|
|
1088
|
-
metadata: element.getMetadata(),
|
|
1089
|
-
links: (element.links ?? []).map((link) => ({
|
|
1090
|
-
title: link.title ?? null,
|
|
1091
|
-
url: link.url,
|
|
1092
|
-
relative: link.relative ?? null
|
|
1093
|
-
})),
|
|
1094
|
-
shape: element.shape,
|
|
1095
|
-
color: element.color,
|
|
1096
|
-
children: [...element.children()].map((c) => c.id),
|
|
1097
|
-
defaultView: element.defaultView?.id || null,
|
|
1098
|
-
includedInViews: includedInViews(element.views()),
|
|
1099
|
-
relationships: {
|
|
1100
|
-
incoming: [...element.incoming()].map((r) => ({
|
|
1101
|
-
source: {
|
|
1102
|
-
id: r.source.id,
|
|
1103
|
-
title: r.source.title,
|
|
1104
|
-
kind: r.source.kind
|
|
1105
|
-
},
|
|
1106
|
-
kind: r.kind,
|
|
1107
|
-
target: r.target.id,
|
|
1108
|
-
title: r.title,
|
|
1109
|
-
description: r.description.text,
|
|
1110
|
-
technology: r.technology,
|
|
1111
|
-
tags: [...r.tags]
|
|
1112
|
-
})),
|
|
1113
|
-
outgoing: [...element.outgoing()].map((r) => ({
|
|
1114
|
-
source: r.source.id,
|
|
1115
|
-
target: {
|
|
1116
|
-
id: r.target.id,
|
|
1117
|
-
title: r.target.title,
|
|
1118
|
-
kind: r.target.kind
|
|
1119
|
-
},
|
|
1120
|
-
kind: r.kind,
|
|
1121
|
-
title: r.title,
|
|
1122
|
-
description: r.description.text,
|
|
1123
|
-
technology: r.technology,
|
|
1124
|
-
tags: [...r.tags]
|
|
1125
|
-
}))
|
|
1126
|
-
},
|
|
1127
|
-
deployedInstances: [...element.deployments()].map((i) => i.id),
|
|
1128
|
-
sourceLocation: locate({ element: element.id })
|
|
1129
|
-
};
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
//#endregion
|
|
1133
|
-
//#region src/mcp/tools/read-project-summary.ts
|
|
1134
|
-
const readProjectSummary = likec4Tool({
|
|
1135
|
-
name: "read-project-summary",
|
|
1136
|
-
annotations: {
|
|
1137
|
-
readOnlyHint: true,
|
|
1138
|
-
idempotentHint: true,
|
|
1139
|
-
title: "Read project summary"
|
|
1140
|
-
},
|
|
1141
|
-
description: `
|
|
1142
|
-
Request:
|
|
1143
|
-
- project: string (optional) — project id. Defaults to "default" if omitted.
|
|
1144
|
-
|
|
1145
|
-
Response (JSON object):
|
|
1146
|
-
- title: string — human-readable project title
|
|
1147
|
-
- folder: string — absolute path to the project root
|
|
1148
|
-
- sources: string[] — absolute file paths of model documents
|
|
1149
|
-
- specification: object
|
|
1150
|
-
- elementKinds: string[] — all element kinds
|
|
1151
|
-
- relationshipKinds: string[] — all relationship kinds
|
|
1152
|
-
- deploymentKinds: string[] — all deployment kinds
|
|
1153
|
-
- tags: string[] — all tags
|
|
1154
|
-
- metadataKeys: string[] — used metadata keys
|
|
1155
|
-
- elements: Element[] — list of elements
|
|
1156
|
-
- deployments: Deployment[] — list of deployment entities
|
|
1157
|
-
- views: View[] — list of views defined in the model
|
|
1158
|
-
|
|
1159
|
-
Element (object) fields:
|
|
1160
|
-
- id: string — element id (FQN)
|
|
1161
|
-
- kind: string — element kind
|
|
1162
|
-
- title: string — element title
|
|
1163
|
-
- tags: string[] — element tags
|
|
1164
|
-
|
|
1165
|
-
Deployment (object) fields:
|
|
1166
|
-
- type = "deployment-node": { id: string, kind: string, title: string, tags: string[] }
|
|
1167
|
-
- type = "deployed-instance": { id: string, title: string, tags: string[], referencedElementId: string }
|
|
1168
|
-
|
|
1169
|
-
View (object) fields:
|
|
1170
|
-
- id: string — view identifier
|
|
1171
|
-
- title: string — view title
|
|
1172
|
-
- type: "element" | "deployment" | "dynamic"
|
|
1173
|
-
|
|
1174
|
-
Notes:
|
|
1175
|
-
- Read-only, idempotent, no side effects.
|
|
1176
|
-
- Safe to call repeatedly.
|
|
1177
|
-
|
|
1178
|
-
Example response:
|
|
1179
|
-
{
|
|
1180
|
-
"title": "Cloud Boutique",
|
|
1181
|
-
"folder": "/abs/path/to/workspace/examples/cloud-system",
|
|
1182
|
-
"sources": [
|
|
1183
|
-
"/abs/path/to/workspace/examples/cloud-system/model.c4"
|
|
1184
|
-
],
|
|
1185
|
-
"specification": {
|
|
1186
|
-
"elementKinds": ["system", "container", "component"],
|
|
1187
|
-
"relationshipKinds": ["uses", "depends-on"],
|
|
1188
|
-
"deploymentKinds": ["node", "cluster"],
|
|
1189
|
-
"tags": ["public", "internal"],
|
|
1190
|
-
"metadataKeys": ["owner", "tier"]
|
|
1191
|
-
},
|
|
1192
|
-
"elements": [
|
|
1193
|
-
{
|
|
1194
|
-
"id": "shop.frontend",
|
|
1195
|
-
"kind": "component",
|
|
1196
|
-
"title": "Frontend",
|
|
1197
|
-
"tags": ["public"]
|
|
1198
|
-
}
|
|
1199
|
-
],
|
|
1200
|
-
"deployments": [
|
|
1201
|
-
{
|
|
1202
|
-
"type": "deployment-node",
|
|
1203
|
-
"id": "k8s.shop.frontend",
|
|
1204
|
-
"kind": "cluster",
|
|
1205
|
-
"title": "Frontend",
|
|
1206
|
-
"tags": []
|
|
1207
|
-
}
|
|
1208
|
-
],
|
|
1209
|
-
"views": [
|
|
1210
|
-
{
|
|
1211
|
-
"name": "system-overview",
|
|
1212
|
-
"title": "System Overview",
|
|
1213
|
-
"type": "element"
|
|
1214
|
-
}
|
|
1215
|
-
]
|
|
1216
|
-
}
|
|
1217
|
-
`,
|
|
1218
|
-
inputSchema: { project: projectIdSchema },
|
|
1219
|
-
outputSchema: {
|
|
1220
|
-
title: z.string(),
|
|
1221
|
-
folder: z.string(),
|
|
1222
|
-
sources: z.array(z.string()),
|
|
1223
|
-
specification: z.object({
|
|
1224
|
-
elementKinds: z.array(z.string()),
|
|
1225
|
-
relationshipKinds: z.array(z.string()),
|
|
1226
|
-
deploymentKinds: z.array(z.string()),
|
|
1227
|
-
tags: z.array(z.string()),
|
|
1228
|
-
metadataKeys: z.array(z.string())
|
|
1229
|
-
}),
|
|
1230
|
-
elements: z.array(z.object({
|
|
1231
|
-
id: z.string(),
|
|
1232
|
-
kind: z.string(),
|
|
1233
|
-
title: z.string(),
|
|
1234
|
-
tags: z.array(z.string())
|
|
1235
|
-
})).describe("List of elements in the project"),
|
|
1236
|
-
deployments: z.array(z.discriminatedUnion("type", [z.object({
|
|
1237
|
-
type: z.literal("deployment-node"),
|
|
1238
|
-
id: z.string().describe("Node ID"),
|
|
1239
|
-
kind: z.string().describe("Deployment node kind"),
|
|
1240
|
-
title: z.string().describe("Node title"),
|
|
1241
|
-
tags: z.array(z.string())
|
|
1242
|
-
}), z.object({
|
|
1243
|
-
type: z.literal("deployed-instance"),
|
|
1244
|
-
id: z.string().describe("Node ID"),
|
|
1245
|
-
title: z.string().describe("Node title"),
|
|
1246
|
-
tags: z.array(z.string()),
|
|
1247
|
-
referencedElementId: z.string().describe("Element ID (FQN)")
|
|
1248
|
-
})])).describe("List of deployment nodes and deployed instances in the project"),
|
|
1249
|
-
views: z.array(z.object({
|
|
1250
|
-
id: z.string(),
|
|
1251
|
-
title: z.string(),
|
|
1252
|
-
type: z.enum([
|
|
1253
|
-
"element",
|
|
1254
|
-
"deployment",
|
|
1255
|
-
"dynamic"
|
|
1256
|
-
])
|
|
1257
|
-
}))
|
|
1258
|
-
}
|
|
1259
|
-
}, async (languageServices, args) => {
|
|
1260
|
-
const projectId = languageServices.projectsManager.ensureProjectId(args.project);
|
|
1261
|
-
const project = languageServices.project(projectId);
|
|
1262
|
-
const model = await languageServices.computedModel(projectId);
|
|
1263
|
-
return {
|
|
1264
|
-
title: project.title,
|
|
1265
|
-
folder: project.folder.fsPath,
|
|
1266
|
-
specification: {
|
|
1267
|
-
elementKinds: t$1(model.specification.elements),
|
|
1268
|
-
relationshipKinds: t$1(model.specification.relationships),
|
|
1269
|
-
deploymentKinds: t$1(model.specification.deployments),
|
|
1270
|
-
tags: [...model.tags],
|
|
1271
|
-
metadataKeys: model.specification.metadataKeys ?? []
|
|
1272
|
-
},
|
|
1273
|
-
elements: [...model.elements()].filter((e) => !e.imported).map((e) => ({
|
|
1274
|
-
id: e.id,
|
|
1275
|
-
kind: e.kind,
|
|
1276
|
-
title: e.title,
|
|
1277
|
-
tags: [...e.tags]
|
|
1278
|
-
})),
|
|
1279
|
-
deployments: [...model.deployment.elements()].map((d) => {
|
|
1280
|
-
if (d.isInstance()) return {
|
|
1281
|
-
type: "deployed-instance",
|
|
1282
|
-
id: d.id,
|
|
1283
|
-
title: d.title,
|
|
1284
|
-
tags: [...d.tags],
|
|
1285
|
-
referencedElementId: d.element.id
|
|
1286
|
-
};
|
|
1287
|
-
return {
|
|
1288
|
-
type: "deployment-node",
|
|
1289
|
-
id: d.id,
|
|
1290
|
-
kind: d.kind,
|
|
1291
|
-
title: d.title,
|
|
1292
|
-
tags: [...d.tags]
|
|
1293
|
-
};
|
|
1294
|
-
}),
|
|
1295
|
-
views: [...model.views()].map((v) => ({
|
|
1296
|
-
id: v.id,
|
|
1297
|
-
title: v.titleOrId,
|
|
1298
|
-
type: v.$view._type
|
|
1299
|
-
})),
|
|
1300
|
-
sources: project.documents?.map((d) => d.fsPath) ?? []
|
|
1301
|
-
};
|
|
1302
|
-
});
|
|
1303
|
-
|
|
1304
|
-
//#endregion
|
|
1305
|
-
//#region src/mcp/tools/read-view.ts
|
|
1306
|
-
const modelRef = (node) => {
|
|
1307
|
-
if (node.hasElement()) return node.element.id;
|
|
1308
|
-
if (node.hasDeployment()) return node.deployment.id;
|
|
1309
|
-
return null;
|
|
1310
|
-
};
|
|
1311
|
-
const nodeSchema = z.discriminatedUnion("type", [
|
|
1312
|
-
z.object({
|
|
1313
|
-
type: z.literal("element"),
|
|
1314
|
-
id: z.string().describe("Node ID"),
|
|
1315
|
-
elementId: z.string().describe("Element ID (FQN)"),
|
|
1316
|
-
kind: z.string().describe("Element kind"),
|
|
1317
|
-
title: z.string().describe("Node title"),
|
|
1318
|
-
description: z.string().nullable(),
|
|
1319
|
-
technology: z.string().nullable(),
|
|
1320
|
-
children: z.array(z.string()).describe("Children nodes, array of node IDs"),
|
|
1321
|
-
shape: z.string().describe("Rendered shape"),
|
|
1322
|
-
color: z.string().describe("Rendered color"),
|
|
1323
|
-
tags: z.array(z.string())
|
|
1324
|
-
}),
|
|
1325
|
-
z.object({
|
|
1326
|
-
type: z.literal("deployment-node"),
|
|
1327
|
-
id: z.string().describe("Node ID"),
|
|
1328
|
-
deploymentId: z.string().describe("Deployment entity ID (FQN)"),
|
|
1329
|
-
kind: z.string().describe("Deployment kind"),
|
|
1330
|
-
title: z.string().describe("Node title"),
|
|
1331
|
-
description: z.string().nullable(),
|
|
1332
|
-
technology: z.string().nullable(),
|
|
1333
|
-
children: z.array(z.string()).describe("Children nodes, array of node IDs"),
|
|
1334
|
-
shape: z.string().describe("Rendered shape"),
|
|
1335
|
-
color: z.string().describe("Rendered color"),
|
|
1336
|
-
tags: z.array(z.string())
|
|
1337
|
-
}),
|
|
1338
|
-
z.object({
|
|
1339
|
-
type: z.literal("deployed-instance"),
|
|
1340
|
-
id: z.string().describe("Node ID"),
|
|
1341
|
-
deploymentId: z.string().describe("Deployment entity ID (FQN)"),
|
|
1342
|
-
title: z.string().describe("Node title"),
|
|
1343
|
-
description: z.string().nullable(),
|
|
1344
|
-
technology: z.string().nullable(),
|
|
1345
|
-
referencedElement: z.object({
|
|
1346
|
-
id: z.string().describe("Element ID (FQN)"),
|
|
1347
|
-
kind: z.string().describe("Element kind"),
|
|
1348
|
-
title: z.string().describe("Element title")
|
|
1349
|
-
}),
|
|
1350
|
-
shape: z.string().describe("Rendered shape"),
|
|
1351
|
-
color: z.string().describe("Rendered color"),
|
|
1352
|
-
tags: z.array(z.string())
|
|
1353
|
-
})
|
|
1354
|
-
]);
|
|
1355
|
-
const readView = likec4Tool({
|
|
1356
|
-
name: "read-view",
|
|
1357
|
-
description: `
|
|
1358
|
-
Read detailed information about a LikeC4 view.
|
|
1359
|
-
|
|
1360
|
-
Request:
|
|
1361
|
-
- viewId: string — view id (name)
|
|
1362
|
-
- project: string (optional) — project id. Defaults to "default" if omitted.
|
|
1363
|
-
|
|
1364
|
-
Response (JSON object):
|
|
1365
|
-
- id: string — view id
|
|
1366
|
-
- type: "element" | "deployment" | "dynamic" — view type
|
|
1367
|
-
- title: string — view title (falls back to id if not set)
|
|
1368
|
-
- description: string|null — optional description
|
|
1369
|
-
- tags: string[] — view tags
|
|
1370
|
-
- project: string — project id this view belongs to
|
|
1371
|
-
- nodes: Node[] — nodes included in the view
|
|
1372
|
-
- edges: Edge[] — relationships between nodes
|
|
1373
|
-
- sourceLocation: { path: string, range: { start: { line: number, character: number }, end: { line: number, character: number } } } | null — source location if available
|
|
1374
|
-
|
|
1375
|
-
Node (discriminated union by "type"):
|
|
1376
|
-
- type = "element": { id: string, elementId: string, kind: string, title: string, description: string|null, technology: string|null, children: string[], shape: string, color: string, tags: string[] }
|
|
1377
|
-
- type = "deployment-node": { id: string, deploymentId: string, kind: string, title: string, description: string|null, technology: string|null, children: string[], shape: string, color: string, tags: string[] }
|
|
1378
|
-
- type = "deployed-instance": { id: string, deploymentId: string, title: string, description: string|null, technology: string|null, referencedElement: { id: string, kind: string, title: string }, shape: string, color: string, tags: string[] }
|
|
1379
|
-
|
|
1380
|
-
Edge object:
|
|
1381
|
-
- { source: string, target: string, label: string|null, description: string|null, technology: string|null, tags: string[] }
|
|
1382
|
-
|
|
1383
|
-
Notes:
|
|
1384
|
-
- Read-only, idempotent, no side effects.
|
|
1385
|
-
|
|
1386
|
-
Example response:
|
|
1387
|
-
{
|
|
1388
|
-
"id": "system-overview",
|
|
1389
|
-
"type": "element",
|
|
1390
|
-
"title": "System Overview",
|
|
1391
|
-
"description": null,
|
|
1392
|
-
"tags": [],
|
|
1393
|
-
"project": "default",
|
|
1394
|
-
"nodes": [
|
|
1395
|
-
{ "type": "logical", "id": "n1", "elementId": "shop.frontend", "kind": "container", "title": "Frontend", "description": null, "technology": "React", "children": [], "shape": "rounded-rectangle", "color": "#2F80ED", "tags": [] }
|
|
1396
|
-
],
|
|
1397
|
-
"edges": [
|
|
1398
|
-
{ "source": "n1", "target": "n2", "label": "calls", "description": null, "technology": "HTTPS", "tags": [] }
|
|
1399
|
-
],
|
|
1400
|
-
"sourceLocation": {
|
|
1401
|
-
"path": "/abs/path/project/model.c4",
|
|
1402
|
-
"range": { "start": { "line": 10, "character": 0 }, "end": { "line": 30, "character": 0 } }
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
`,
|
|
1406
|
-
annotations: {
|
|
1407
|
-
readOnlyHint: true,
|
|
1408
|
-
idempotentHint: true,
|
|
1409
|
-
title: "Read view"
|
|
1410
|
-
},
|
|
1411
|
-
inputSchema: {
|
|
1412
|
-
viewId: z.string().describe("View id (name)"),
|
|
1413
|
-
project: projectIdSchema
|
|
1414
|
-
},
|
|
1415
|
-
outputSchema: {
|
|
1416
|
-
id: z.string(),
|
|
1417
|
-
type: z.enum([
|
|
1418
|
-
"element",
|
|
1419
|
-
"deployment",
|
|
1420
|
-
"dynamic"
|
|
1421
|
-
]).describe("View type"),
|
|
1422
|
-
title: z.string(),
|
|
1423
|
-
description: z.string().nullable(),
|
|
1424
|
-
tags: z.array(z.string()),
|
|
1425
|
-
project: z.string(),
|
|
1426
|
-
nodes: z.array(nodeSchema),
|
|
1427
|
-
edges: z.array(z.object({
|
|
1428
|
-
source: z.string().describe("Source node"),
|
|
1429
|
-
target: z.string().describe("Target node"),
|
|
1430
|
-
label: z.string().nullable(),
|
|
1431
|
-
description: z.string().nullable(),
|
|
1432
|
-
technology: z.string().nullable(),
|
|
1433
|
-
tags: z.array(z.string())
|
|
1434
|
-
})).describe("Edge represents relationship between nodes"),
|
|
1435
|
-
sourceLocation: locationSchema
|
|
1436
|
-
}
|
|
1437
|
-
}, async (languageServices, args) => {
|
|
1438
|
-
const projectId = languageServices.projectsManager.ensureProjectId(args.project);
|
|
1439
|
-
const project = languageServices.project(projectId);
|
|
1440
|
-
const view = (await languageServices.computedModel(projectId)).findView(args.viewId);
|
|
1441
|
-
if (!view) throw new Error(`View with ID '${args.viewId}' not found in project ${project.id}`);
|
|
1442
|
-
const locate = mkLocate(languageServices, project.id);
|
|
1443
|
-
return {
|
|
1444
|
-
id: view.id,
|
|
1445
|
-
type: view.$view._type,
|
|
1446
|
-
title: view.title ?? view.id,
|
|
1447
|
-
description: view.description.text,
|
|
1448
|
-
tags: [...view.tags],
|
|
1449
|
-
project: project.id,
|
|
1450
|
-
nodes: [...view.nodes()].flatMap((node) => {
|
|
1451
|
-
const base = {
|
|
1452
|
-
id: node.id,
|
|
1453
|
-
title: node.title,
|
|
1454
|
-
description: node.description.text,
|
|
1455
|
-
technology: node.technology,
|
|
1456
|
-
shape: node.shape,
|
|
1457
|
-
color: node.color,
|
|
1458
|
-
tags: [...node.tags]
|
|
1459
|
-
};
|
|
1460
|
-
if (node.hasDeployedInstance()) return {
|
|
1461
|
-
...base,
|
|
1462
|
-
type: "deployed-instance",
|
|
1463
|
-
deploymentId: node.deployment.id,
|
|
1464
|
-
referencedElement: {
|
|
1465
|
-
id: node.deployment.element.id,
|
|
1466
|
-
kind: node.deployment.element.kind,
|
|
1467
|
-
title: node.deployment.element.title
|
|
1468
|
-
}
|
|
1469
|
-
};
|
|
1470
|
-
if (node.hasDeployment()) return {
|
|
1471
|
-
...base,
|
|
1472
|
-
type: "deployment-node",
|
|
1473
|
-
kind: node.deployment.kind,
|
|
1474
|
-
deploymentId: node.deployment.id,
|
|
1475
|
-
children: [...node.children()].map((c) => c.id)
|
|
1476
|
-
};
|
|
1477
|
-
if (node.hasElement()) return {
|
|
1478
|
-
...base,
|
|
1479
|
-
type: "element",
|
|
1480
|
-
elementId: node.element.id,
|
|
1481
|
-
kind: node.element.kind,
|
|
1482
|
-
children: [...node.children()].flatMap((c) => modelRef(c) ?? [])
|
|
1483
|
-
};
|
|
1484
|
-
return [];
|
|
1485
|
-
}),
|
|
1486
|
-
edges: [...view.edges()].map((r) => ({
|
|
1487
|
-
source: r.source.id,
|
|
1488
|
-
target: r.target.id,
|
|
1489
|
-
label: r.label,
|
|
1490
|
-
description: r.description.text,
|
|
1491
|
-
technology: r.technology,
|
|
1492
|
-
tags: [...r.tags]
|
|
1493
|
-
})),
|
|
1494
|
-
sourceLocation: locate({ view: view.id })
|
|
1495
|
-
};
|
|
1496
|
-
});
|
|
1497
|
-
|
|
1498
|
-
//#endregion
|
|
1499
|
-
//#region src/mcp/tools/search-element.ts
|
|
1500
|
-
const searchResultSchema = z.array(z.discriminatedUnion("type", [z.object({
|
|
1501
|
-
type: z.literal("element"),
|
|
1502
|
-
project: z.string().describe("Project ID"),
|
|
1503
|
-
id: z.string().describe("Element ID (FQN)"),
|
|
1504
|
-
name: z.string().describe("Element name"),
|
|
1505
|
-
kind: z.string(),
|
|
1506
|
-
title: z.string(),
|
|
1507
|
-
technology: z.string().nullable(),
|
|
1508
|
-
shape: z.string(),
|
|
1509
|
-
includedInViews: includedInViewsSchema,
|
|
1510
|
-
metadata: z.record(z.union([z.string(), z.array(z.string())])),
|
|
1511
|
-
tags: z.array(z.string())
|
|
1512
|
-
}), z.object({
|
|
1513
|
-
type: z.literal("deployment-node"),
|
|
1514
|
-
project: z.string().describe("Project ID"),
|
|
1515
|
-
id: z.string().describe("Deployment ID (FQN)"),
|
|
1516
|
-
name: z.string().describe("Deployment name"),
|
|
1517
|
-
kind: z.string(),
|
|
1518
|
-
title: z.string(),
|
|
1519
|
-
technology: z.string().nullable(),
|
|
1520
|
-
shape: z.string(),
|
|
1521
|
-
includedInViews: includedInViewsSchema,
|
|
1522
|
-
metadata: z.record(z.union([z.string(), z.array(z.string())])),
|
|
1523
|
-
tags: z.array(z.string())
|
|
1524
|
-
})]));
|
|
1525
|
-
const searchElement = likec4Tool({
|
|
1526
|
-
name: "search-element",
|
|
1527
|
-
annotations: {
|
|
1528
|
-
readOnlyHint: true,
|
|
1529
|
-
idempotentHint: true,
|
|
1530
|
-
title: "Search elements"
|
|
1531
|
-
},
|
|
1532
|
-
description: `
|
|
1533
|
-
Search LikeC4 elements and deployment nodes across all projects.
|
|
1534
|
-
|
|
1535
|
-
Query syntax (case-insensitive):
|
|
1536
|
-
- kind:<value> filters by kind
|
|
1537
|
-
- shape:<value> filters by shape
|
|
1538
|
-
- meta:<key> filters by having metadata with the given key
|
|
1539
|
-
- #<value> matches assigned tags
|
|
1540
|
-
- <value> matches id (FQN) or title
|
|
1541
|
-
|
|
1542
|
-
Request:
|
|
1543
|
-
- search: string — at least 2 characters
|
|
1544
|
-
|
|
1545
|
-
Response (JSON object):
|
|
1546
|
-
- total: number - total number of results
|
|
1547
|
-
- found: Result[] - returns top 20 results
|
|
1548
|
-
|
|
1549
|
-
Result (discriminated union by "type"):
|
|
1550
|
-
- type = "element": { id: string, name: string, kind: string, title: string, technology: string|null, shape: string, project: string, includedInViews: View[], tags: string[], metadata: Record<string, string> }
|
|
1551
|
-
- type = "deployment-node": { id: string, name: string, kind: string, title: string, technology: string|null, shape: string, project: string, includedInViews: View[], tags: string[], metadata: Record<string, string> }
|
|
1552
|
-
|
|
1553
|
-
View (object) fields:
|
|
1554
|
-
- id: string — view identifier
|
|
1555
|
-
- title: string — view title
|
|
1556
|
-
- type: "element" | "deployment" | "dynamic"
|
|
1557
|
-
|
|
1558
|
-
Notes:
|
|
1559
|
-
- Read-only, idempotent.
|
|
1560
|
-
- Use results as input to other tools (e.g., read-element, read-view).
|
|
1561
|
-
|
|
1562
|
-
Example response:
|
|
1563
|
-
{
|
|
1564
|
-
"total": 1,
|
|
1565
|
-
"found": [
|
|
1566
|
-
{
|
|
1567
|
-
"type": "logical",
|
|
1568
|
-
"project": "default",
|
|
1569
|
-
"id": "shop.frontend",
|
|
1570
|
-
"name": "frontend",
|
|
1571
|
-
"kind": "container",
|
|
1572
|
-
"title": "Frontend",
|
|
1573
|
-
"technology": "React",
|
|
1574
|
-
"shape": "rectangle",
|
|
1575
|
-
"includedInViews": [
|
|
1576
|
-
{
|
|
1577
|
-
"id": "system-overview",
|
|
1578
|
-
"title": "System Overview",
|
|
1579
|
-
"type": "element"
|
|
1580
|
-
}
|
|
1581
|
-
],
|
|
1582
|
-
"tags": ["public"],
|
|
1583
|
-
"metadata": {}
|
|
1584
|
-
}
|
|
1585
|
-
]
|
|
1586
|
-
}
|
|
1587
|
-
`,
|
|
1588
|
-
inputSchema: { search: z.string().min(2, "Search must be at least 2 characters long") },
|
|
1589
|
-
outputSchema: {
|
|
1590
|
-
total: z.number(),
|
|
1591
|
-
found: searchResultSchema
|
|
1592
|
-
}
|
|
1593
|
-
}, async (languageServices, args) => {
|
|
1594
|
-
const projects = languageServices.projects();
|
|
1595
|
-
const found = [];
|
|
1596
|
-
let search = args.search.toLowerCase();
|
|
1597
|
-
let predicate;
|
|
1598
|
-
if (search.startsWith("kind:")) {
|
|
1599
|
-
search = search.slice(5);
|
|
1600
|
-
logger$1.debug("search by kind: {search}", { search });
|
|
1601
|
-
predicate = (el) => el.kind.toLowerCase() === search;
|
|
1602
|
-
} else if (search.startsWith("shape:")) {
|
|
1603
|
-
search = search.slice(6);
|
|
1604
|
-
logger$1.debug("search by shape: {search}", { search });
|
|
1605
|
-
predicate = (el) => el.shape.toLowerCase() === search;
|
|
1606
|
-
} else if (search.startsWith("meta:")) {
|
|
1607
|
-
search = search.slice(5);
|
|
1608
|
-
logger$1.debug("search by metadata: {search}", { search });
|
|
1609
|
-
predicate = (el) => !!el.getMetadata(search);
|
|
1610
|
-
} else if (search.startsWith("#")) {
|
|
1611
|
-
search = search.slice(1);
|
|
1612
|
-
logger$1.debug("search by tag: {search}", { search });
|
|
1613
|
-
predicate = (el) => el.tags.some((tag) => tag.toLowerCase().includes(search));
|
|
1614
|
-
} else {
|
|
1615
|
-
logger$1.debug("search by id/title: {search}", { search });
|
|
1616
|
-
predicate = (el) => el.id.toLowerCase().includes(search) || el.title.toLowerCase().includes(search);
|
|
1617
|
-
}
|
|
1618
|
-
for (const project of projects) try {
|
|
1619
|
-
const model = await languageServices.computedModel(project.id);
|
|
1620
|
-
for (const el of ifilter(model.elements(), (e) => !e.imported && predicate(e))) found.push({
|
|
1621
|
-
type: "element",
|
|
1622
|
-
project: project.id,
|
|
1623
|
-
id: el.id,
|
|
1624
|
-
name: el.name,
|
|
1625
|
-
kind: el.kind,
|
|
1626
|
-
title: el.title,
|
|
1627
|
-
technology: el.technology,
|
|
1628
|
-
shape: el.shape,
|
|
1629
|
-
tags: [...el.tags],
|
|
1630
|
-
metadata: el.getMetadata(),
|
|
1631
|
-
includedInViews: includedInViews(el.views())
|
|
1632
|
-
});
|
|
1633
|
-
for (const el of ifilter(model.deployment.nodes(), predicate)) found.push({
|
|
1634
|
-
type: "deployment-node",
|
|
1635
|
-
project: project.id,
|
|
1636
|
-
id: el.id,
|
|
1637
|
-
name: el.name,
|
|
1638
|
-
kind: el.kind,
|
|
1639
|
-
title: el.title,
|
|
1640
|
-
technology: el.technology,
|
|
1641
|
-
shape: el.shape,
|
|
1642
|
-
tags: [...el.tags],
|
|
1643
|
-
metadata: el.getMetadata(),
|
|
1644
|
-
includedInViews: includedInViews(el.views())
|
|
1645
|
-
});
|
|
1646
|
-
} catch (error) {
|
|
1647
|
-
logger$1.error(`Error searching in project ${project.id}:`, { error });
|
|
1648
|
-
}
|
|
1649
|
-
return {
|
|
1650
|
-
total: found.length,
|
|
1651
|
-
found: found.slice(0, 20)
|
|
1652
|
-
};
|
|
1653
|
-
});
|
|
1654
|
-
|
|
1655
|
-
//#endregion
|
|
1656
|
-
//#region src/mcp/server/MCPServerFactory.ts
|
|
1657
|
-
var MCPServerFactory = class {
|
|
1658
|
-
constructor(services) {
|
|
1659
|
-
this.services = services;
|
|
1660
|
-
}
|
|
1661
|
-
create(options) {
|
|
1662
|
-
const isInEditor = this.services.shared.lsp.Connection !== void 0;
|
|
1663
|
-
const mcp = new McpServer({
|
|
1664
|
-
name: "LikeC4",
|
|
1665
|
-
version
|
|
1666
|
-
}, {
|
|
1667
|
-
instructions: `LikeC4 MCP – query and navigate LikeC4 models.
|
|
1668
|
-
|
|
1669
|
-
Conventions:
|
|
1670
|
-
- All tools are read-only and idempotent.
|
|
1671
|
-
- "project" is optional and defaults to "default".
|
|
1672
|
-
|
|
1673
|
-
Available tools:
|
|
1674
|
-
- list-projects — List all LikeC4 projects in the workspace.
|
|
1675
|
-
- read-project-summary — Project specification (element kinds, deployment node kinds, tags, metadata keys), all elements, deployment nodes and views. Input: { project? }.
|
|
1676
|
-
- search-element — Search elements and deployment nodes across all projects by id/title/kind/shape/tags/metadata. Input: { search }.
|
|
1677
|
-
- read-element — Full element details including relationships, includedInViews, deployedInstances, metadata and sourceLocation. Input: { id, project? }.
|
|
1678
|
-
- read-deployment — Details of a deployment node or deployed instance. Input: { id, project? }.
|
|
1679
|
-
- read-view — Full view details (nodes/edges) and sourceLocation. Input: { viewId, project? }.
|
|
1680
|
-
- find-relationships — Direct and indirect relationships between two elements in a project. Input: { element1, element2, project? }.
|
|
1681
|
-
${isInEditor ? "- open-view — Opens the LikeC4 view panel in the editor. Triggers UI; at most one preview panel at a time. Input: { viewId, project? }." : ""}
|
|
1682
|
-
|
|
1683
|
-
Instructions:
|
|
1684
|
-
- Identify the project first
|
|
1685
|
-
- Use "search-element" to find elements by id/title/kind/shape/tags/metadata and select the project
|
|
1686
|
-
- Use "read-project-summary" to find all elements and deployment nodes inside the project, what kinds, tags, metadata keys are available
|
|
1687
|
-
- Use "list-projects" to list all available projects
|
|
1688
|
-
- If response returns "sourceLocation", provide link to this location in the editor
|
|
1689
|
-
|
|
1690
|
-
Full documentation: https://likec4.dev/llms-full.txt
|
|
1691
|
-
`,
|
|
1692
|
-
enforceStrictCapabilities: true,
|
|
1693
|
-
...options,
|
|
1694
|
-
capabilities: {
|
|
1695
|
-
tools: {},
|
|
1696
|
-
...options?.capabilities
|
|
1697
|
-
}
|
|
1698
|
-
});
|
|
1699
|
-
mcp.registerTool(...listProjects(this.services.likec4.LanguageServices));
|
|
1700
|
-
mcp.registerTool(...readProjectSummary(this.services.likec4.LanguageServices));
|
|
1701
|
-
mcp.registerTool(...readElement(this.services.likec4.LanguageServices));
|
|
1702
|
-
mcp.registerTool(...readDeployment(this.services.likec4.LanguageServices));
|
|
1703
|
-
mcp.registerTool(...readView(this.services.likec4.LanguageServices));
|
|
1704
|
-
mcp.registerTool(...searchElement(this.services.likec4.LanguageServices));
|
|
1705
|
-
mcp.registerTool(...findRelationships(this.services.likec4.LanguageServices));
|
|
1706
|
-
if (isInEditor) mcp.registerTool(...openView(this.services.likec4.LanguageServices));
|
|
1707
|
-
mcp.server.onerror = (err) => {
|
|
1708
|
-
logger$4.error(loggable(err));
|
|
1709
|
-
};
|
|
1710
|
-
return mcp;
|
|
1711
|
-
}
|
|
1712
|
-
};
|
|
1713
|
-
|
|
1714
|
-
//#endregion
|
|
1715
|
-
//#region src/mcp/server/StdioLikeC4MCPServer.ts
|
|
1716
|
-
var StdioLikeC4MCPServer = class {
|
|
1717
|
-
transport = void 0;
|
|
1718
|
-
_mcp = void 0;
|
|
1719
|
-
constructor(services) {
|
|
1720
|
-
this.services = services;
|
|
1721
|
-
}
|
|
1722
|
-
get mcp() {
|
|
1723
|
-
if (!this._mcp) throw new Error("MCP server is not started");
|
|
1724
|
-
return this._mcp;
|
|
1725
|
-
}
|
|
1726
|
-
get isStarted() {
|
|
1727
|
-
return this.transport !== void 0;
|
|
1728
|
-
}
|
|
1729
|
-
get port() {
|
|
1730
|
-
return NaN;
|
|
1731
|
-
}
|
|
1732
|
-
async dispose() {
|
|
1733
|
-
await this.stop();
|
|
1734
|
-
}
|
|
1735
|
-
async start() {
|
|
1736
|
-
if (this.transport) return;
|
|
1737
|
-
logger$1.info("Starting MCP stdio server");
|
|
1738
|
-
this._mcp = this.services.mcp.ServerFactory.create();
|
|
1739
|
-
this.transport = new StdioServerTransport();
|
|
1740
|
-
await this._mcp.connect(this.transport);
|
|
1741
|
-
logger$1.info("LikeC4 MCP Server running on stdio");
|
|
1742
|
-
}
|
|
1743
|
-
async stop() {
|
|
1744
|
-
if (!this.transport) return;
|
|
1745
|
-
try {
|
|
1746
|
-
logger$1.info("Stopping MCP stdio server");
|
|
1747
|
-
await this.transport.close();
|
|
1748
|
-
if (this._mcp) await this._mcp.close();
|
|
1749
|
-
} finally {
|
|
1750
|
-
this._mcp = void 0;
|
|
1751
|
-
this.transport = void 0;
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
};
|
|
1755
|
-
|
|
1756
|
-
//#endregion
|
|
1757
|
-
//#region src/mcp/server/StreamableLikeC4MCPServer.ts
|
|
1758
|
-
async function createHonoApp(factory) {
|
|
1759
|
-
const mcp = factory.create();
|
|
1760
|
-
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: () => nanoid() });
|
|
1761
|
-
const app = new Hono();
|
|
1762
|
-
app.use("*", cors({
|
|
1763
|
-
origin: "*",
|
|
1764
|
-
allowHeaders: [
|
|
1765
|
-
"Content-Type",
|
|
1766
|
-
"mcp-session-id",
|
|
1767
|
-
"Last-Event-ID",
|
|
1768
|
-
"mcp-protocol-version"
|
|
1769
|
-
],
|
|
1770
|
-
exposeHeaders: ["mcp-session-id", "mcp-protocol-version"]
|
|
1771
|
-
}));
|
|
1772
|
-
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
1773
|
-
app.all("/mcp", (c) => transport.handleRequest(c.req.raw));
|
|
1774
|
-
app.notFound((c) => {
|
|
1775
|
-
logger$1.debug(`${c.req.method} ${c.req.url} not found`);
|
|
1776
|
-
return c.json({
|
|
1777
|
-
jsonrpc: "2.0",
|
|
1778
|
-
error: {
|
|
1779
|
-
code: -32e3,
|
|
1780
|
-
message: "Method not found."
|
|
1781
|
-
},
|
|
1782
|
-
id: null
|
|
1783
|
-
}, { status: 404 });
|
|
1784
|
-
});
|
|
1785
|
-
app.onError((e, c) => {
|
|
1786
|
-
logger$1.error(loggable(e));
|
|
1787
|
-
return c.json({
|
|
1788
|
-
jsonrpc: "2.0",
|
|
1789
|
-
error: {
|
|
1790
|
-
code: -32603,
|
|
1791
|
-
message: "Internal server error"
|
|
1792
|
-
},
|
|
1793
|
-
id: null
|
|
1794
|
-
}, { status: 500 });
|
|
1795
|
-
});
|
|
1796
|
-
await mcp.connect(transport);
|
|
1797
|
-
return app;
|
|
1798
|
-
}
|
|
1799
|
-
async function startServer(params) {
|
|
1800
|
-
const { factory, port } = params;
|
|
1801
|
-
const app = await createHonoApp(factory);
|
|
1802
|
-
return new Promise((resolve, reject) => {
|
|
1803
|
-
const server = serve({
|
|
1804
|
-
fetch: app.fetch,
|
|
1805
|
-
hostname: "0.0.0.0",
|
|
1806
|
-
port
|
|
1807
|
-
}).prependOnceListener("error", reject).prependOnceListener("listening", () => {
|
|
1808
|
-
server.removeListener("error", reject);
|
|
1809
|
-
resolve(server.unref());
|
|
1810
|
-
});
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
var StreamableLikeC4MCPServer = class {
|
|
1814
|
-
server = void 0;
|
|
1815
|
-
constructor(services, _port = 33335) {
|
|
1816
|
-
this.services = services;
|
|
1817
|
-
this._port = _port;
|
|
1818
|
-
}
|
|
1819
|
-
get mcp() {
|
|
1820
|
-
throw new Error("StreamableLikeC4MCPServer has access to McpServer only during the request");
|
|
1821
|
-
}
|
|
1822
|
-
get isStarted() {
|
|
1823
|
-
return this.server?.listening === true;
|
|
1824
|
-
}
|
|
1825
|
-
get port() {
|
|
1826
|
-
return this._port;
|
|
1827
|
-
}
|
|
1828
|
-
async dispose() {
|
|
1829
|
-
await this.stop();
|
|
1830
|
-
}
|
|
1831
|
-
async start(port = this._port) {
|
|
1832
|
-
if (this.server) {
|
|
1833
|
-
if (this.port === port) return;
|
|
1834
|
-
await this.stop();
|
|
1835
|
-
}
|
|
1836
|
-
logger$1.info("Starting MCP server on port {port}", { port });
|
|
1837
|
-
this._port = port;
|
|
1838
|
-
this.server = await startServer({
|
|
1839
|
-
factory: this.services.mcp.ServerFactory,
|
|
1840
|
-
port
|
|
1841
|
-
});
|
|
1842
|
-
logger$1.info("MCP server ready at http://0.0.0.0:{port}/mcp", { port });
|
|
1843
|
-
}
|
|
1844
|
-
stop() {
|
|
1845
|
-
const server = this.server;
|
|
1846
|
-
if (!server) {
|
|
1847
|
-
logger$1.info("MCP server is not running, nothing to stop");
|
|
1848
|
-
return Promise.resolve();
|
|
1849
|
-
}
|
|
1850
|
-
logger$1.info("Stopping MCP server");
|
|
1851
|
-
this.server = void 0;
|
|
1852
|
-
return new Promise((resolve) => {
|
|
1853
|
-
server.close((err) => {
|
|
1854
|
-
if (err) logger$1.error("Failed to stop MCP server", { err });
|
|
1855
|
-
else logger$1.info("MCP server stopped");
|
|
1856
|
-
resolve();
|
|
1857
|
-
});
|
|
1858
|
-
});
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1861
|
-
|
|
1862
|
-
//#endregion
|
|
1863
|
-
//#region src/mcp/server/WithMCPServer.ts
|
|
1864
|
-
function streamableLikeC4MCPServer(services, defaultPort = 33335) {
|
|
1865
|
-
logger$1.debug("Creating StreamableLikeC4MCPServer");
|
|
1866
|
-
const server = new StreamableLikeC4MCPServer(services, defaultPort);
|
|
1867
|
-
const langId = services.LanguageMetaData.languageId;
|
|
1868
|
-
const connection = services.shared.lsp.Connection;
|
|
1869
|
-
services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
|
|
1870
|
-
if (update.section !== langId) {
|
|
1871
|
-
logger$1.warn("Unexpected configuration update: {update}", { update });
|
|
1872
|
-
return;
|
|
1873
|
-
}
|
|
1874
|
-
const { enabled = false, port = defaultPort } = update.configuration.mcp;
|
|
1875
|
-
if (!enabled) {
|
|
1876
|
-
server.stop();
|
|
1877
|
-
return;
|
|
1878
|
-
}
|
|
1879
|
-
Promise.resolve().then(() => server.start(port)).then(() => {
|
|
1880
|
-
connection?.telemetry?.logEvent({
|
|
1881
|
-
eventName: "mcp-server-started",
|
|
1882
|
-
mcpPort: port
|
|
1883
|
-
});
|
|
1884
|
-
}).catch((err) => {
|
|
1885
|
-
const message = loggable(err);
|
|
1886
|
-
connection?.telemetry?.logEvent({
|
|
1887
|
-
eventName: "mcp-server-start-failed",
|
|
1888
|
-
mcpPort: port,
|
|
1889
|
-
message
|
|
1890
|
-
});
|
|
1891
|
-
logger$1.warn(`Failed to start LikeC4 MCP Server: \n${message}`);
|
|
1892
|
-
if (connection) connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server\n\n${message}`);
|
|
1893
|
-
});
|
|
1894
|
-
});
|
|
1895
|
-
return server;
|
|
1896
|
-
}
|
|
1897
|
-
function stdioLikeC4MCPServer(services) {
|
|
1898
|
-
return new StdioLikeC4MCPServer(services);
|
|
1899
|
-
}
|
|
1900
|
-
function WithMCPServer(type = "sse") {
|
|
1901
|
-
return {
|
|
1902
|
-
mcpServer: (services) => {
|
|
1903
|
-
if (type === "stdio") return stdioLikeC4MCPServer(services);
|
|
1904
|
-
return streamableLikeC4MCPServer(services, typeof type === "object" ? type.port : 33335);
|
|
1905
|
-
},
|
|
1906
|
-
mcpServerFactory: (services) => {
|
|
1907
|
-
return new MCPServerFactory(services);
|
|
1908
|
-
}
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
//#endregion
|
|
1913
|
-
//#region src/views/ConfigurableLayouter.ts
|
|
1914
|
-
function graphvizBinPath() {
|
|
1915
|
-
try {
|
|
1916
|
-
return which.sync("dot");
|
|
1917
|
-
} catch (error) {
|
|
1918
|
-
logger$4.error("Error checking for native Graphviz:", { error });
|
|
1919
|
-
return null;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
const ConfigurableLayouter = { likec4: { Layouter(services) {
|
|
1923
|
-
logger$4.debug("Creating ConfigurableLayouter");
|
|
1924
|
-
const layouter = new QueueGraphvizLayoter();
|
|
1925
|
-
services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
|
|
1926
|
-
logger$4.debug("Configuration update: {update}", { update });
|
|
1927
|
-
if (update.section !== services.LanguageMetaData.languageId) {
|
|
1928
|
-
logger$4.debug(`Ignoring configuration update as it is not for ${services.LanguageMetaData.languageId}`);
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
try {
|
|
1932
|
-
const { mode, path } = update.configuration.graphviz ?? {
|
|
1933
|
-
mode: "wasm",
|
|
1934
|
-
path: ""
|
|
1935
|
-
};
|
|
1936
|
-
if (mode !== "wasm") {
|
|
1937
|
-
let binaryPath = e$1(path) ? graphvizBinPath() : path;
|
|
1938
|
-
if (!e$1(binaryPath)) {
|
|
1939
|
-
layouter.changePort(new GraphvizBinaryAdapter(binaryPath));
|
|
1940
|
-
logger$4.info`use graphviz binary: ${binaryPath}`;
|
|
1941
|
-
return;
|
|
1942
|
-
}
|
|
1943
|
-
logger$4.warn(`No Graphviz binaries found on PATH, use graphviz wasm`);
|
|
1944
|
-
services.shared.lsp.Connection?.window.showWarningMessage("No Graphviz binaries found on PATH, set path to binaries in settings.");
|
|
1945
|
-
}
|
|
1946
|
-
layouter.changePort(new GraphvizWasmAdapter());
|
|
1947
|
-
logger$4.info("use graphviz wasm");
|
|
1948
|
-
} catch (error) {
|
|
1949
|
-
logger$4.error("Failed to update configuration", { error });
|
|
1950
|
-
}
|
|
1951
|
-
});
|
|
1952
|
-
return layouter;
|
|
1953
|
-
} } };
|
|
1954
|
-
|
|
1955
|
-
//#endregion
|
|
1956
|
-
export { WithLikeC4ManualLayouts as i, WithMCPServer as n, WithFileSystem as r, ConfigurableLayouter as t };
|
|
1
|
+
import{i as logger}from"./logger.mjs";import{C as e}from"./libs/remeda.mjs";import{GraphvizWasmAdapter,QueueGraphvizLayoter}from"@likec4/layouts";import{GraphvizBinaryAdapter}from"@likec4/layouts/graphviz/binary";import which from"which";function graphvizBinPath(){try{return which.sync(`dot`)}catch(t){return logger.error(`Error checking for native Graphviz:`,{error:t}),null}}const ConfigurableLayouter={likec4:{Layouter(i){logger.debug(`Creating ConfigurableLayouter`);let a=new QueueGraphvizLayoter;return i.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate(r=>{if(logger.debug(`Configuration update: {update}`,{update:r}),r.section!==i.LanguageMetaData.languageId){logger.debug(`Ignoring configuration update as it is not for ${i.LanguageMetaData.languageId}`);return}try{let{mode:o,path:s}=r.configuration.graphviz??{mode:`wasm`,path:``};if(o!==`wasm`){let n=e(s)?graphvizBinPath():s;if(!e(n)){a.changePort(new GraphvizBinaryAdapter(n)),logger.info`use graphviz binary: ${n}`;return}logger.warn(`No Graphviz binaries found on PATH, use graphviz wasm`),i.shared.lsp.Connection?.window.showWarningMessage(`No Graphviz binaries found on PATH, set path to binaries in settings.`)}a.changePort(new GraphvizWasmAdapter),logger.info(`use graphviz wasm`)}catch(t){logger.error(`Failed to update configuration`,{error:t})}}),a}}};export{ConfigurableLayouter as t};
|