@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.
Files changed (78) hide show
  1. package/browser/package.json +2 -2
  2. package/browser-worker/package.json +2 -2
  3. package/dist/THIRD-PARTY-LICENSES.md +178 -0
  4. package/dist/_chunks/ConfigurableLayouter.mjs +1 -1956
  5. package/dist/_chunks/LikeC4FileSystem.mjs +3 -0
  6. package/dist/_chunks/LikeC4Views.mjs +34 -0
  7. package/dist/_chunks/ProjectsManager.mjs +1 -0
  8. package/dist/_chunks/WithMCPServer.mjs +481 -0
  9. package/dist/_chunks/icons.mjs +2 -5211
  10. package/dist/_chunks/{LikeC4LanguageServices.d.mts → index.d.mts} +1836 -707
  11. package/dist/_chunks/libs/@msgpack/msgpack.mjs +1 -805
  12. package/dist/_chunks/libs/eventemitter3.mjs +1 -243
  13. package/dist/_chunks/libs/fast-equals.mjs +1 -446
  14. package/dist/_chunks/libs/p-queue.mjs +1 -449
  15. package/dist/_chunks/libs/parse-ms.mjs +1 -36
  16. package/dist/_chunks/libs/picomatch.mjs +1 -1673
  17. package/dist/_chunks/libs/pretty-ms.mjs +1 -80
  18. package/dist/_chunks/libs/remeda.mjs +1 -482
  19. package/dist/_chunks/libs/strip-indent.mjs +1 -15
  20. package/dist/_chunks/libs/ufo.mjs +1 -166
  21. package/dist/_chunks/logger.mjs +1 -0
  22. package/dist/_chunks/rolldown-runtime.mjs +1 -42
  23. package/dist/_chunks/utils.mjs +1 -0
  24. package/dist/browser/index.d.mts +10 -0
  25. package/dist/browser/index.mjs +1 -0
  26. package/dist/browser/worker.mjs +1 -0
  27. package/dist/bundled.d.mts +2 -3
  28. package/dist/bundled.mjs +1 -51
  29. package/dist/filesystem/index.d.mts +2 -4
  30. package/dist/filesystem/index.mjs +1 -3
  31. package/dist/index.d.mts +38 -3
  32. package/dist/index.mjs +1 -48
  33. package/dist/likec4lib.d.mts +10 -3
  34. package/dist/likec4lib.mjs +1 -4
  35. package/dist/mcp/index.d.mts +2 -4
  36. package/dist/mcp/index.mjs +1 -3
  37. package/dist/module.d.mts +126 -4
  38. package/dist/module.mjs +1 -3
  39. package/dist/protocol.d.mts +314 -1
  40. package/dist/protocol.mjs +1 -3
  41. package/filesystem/package.json +4 -0
  42. package/mcp/package.json +4 -0
  43. package/module/package.json +4 -0
  44. package/package.json +79 -56
  45. package/LICENSE +0 -21
  46. package/dist/LikeC4LanguageServices.d.mts +0 -4
  47. package/dist/LikeC4LanguageServices.mjs +0 -3
  48. package/dist/_chunks/LikeC4LanguageServices.mjs +0 -725
  49. package/dist/_chunks/ast.d.mts +0 -1444
  50. package/dist/_chunks/ast.mjs +0 -2375
  51. package/dist/_chunks/ast2.mjs +0 -176
  52. package/dist/_chunks/common-exports.mjs +0 -0
  53. package/dist/_chunks/filesystem.mjs +0 -58
  54. package/dist/_chunks/grammar.mjs +0 -8
  55. package/dist/_chunks/libs/@hono/node-server.mjs +0 -436
  56. package/dist/_chunks/libs/hono.mjs +0 -1829
  57. package/dist/_chunks/likec4lib.mjs +0 -9
  58. package/dist/_chunks/mcp.mjs +0 -33
  59. package/dist/_chunks/module.mjs +0 -28
  60. package/dist/_chunks/module2.mjs +0 -6576
  61. package/dist/_chunks/protocol.d.mts +0 -311
  62. package/dist/_chunks/protocol.mjs +0 -78
  63. package/dist/ast.d.mts +0 -4
  64. package/dist/ast.mjs +0 -4
  65. package/dist/browser-worker.mjs +0 -6
  66. package/dist/browser.d.mts +0 -11
  67. package/dist/browser.mjs +0 -27
  68. package/dist/common-exports.d.mts +0 -4
  69. package/dist/common-exports.mjs +0 -5
  70. package/dist/generated/ast.d.mts +0 -2
  71. package/dist/generated/ast.mjs +0 -3
  72. package/dist/generated/grammar.d.mts +0 -6
  73. package/dist/generated/grammar.mjs +0 -3
  74. package/dist/generated/module.d.mts +0 -14
  75. package/dist/generated/module.mjs +0 -3
  76. package/dist/generated-lib/icons.d.mts +0 -4
  77. package/dist/generated-lib/icons.mjs +0 -3
  78. /package/dist/{browser-worker.d.mts → browser/worker.d.mts} +0 -0
@@ -1,1956 +1 @@
1
- import { t as LibIcons } from "./icons.mjs";
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};