@likec4/language-server 1.37.0 → 1.38.1

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 (68) hide show
  1. package/dist/LikeC4LanguageServices.d.ts +8 -4
  2. package/dist/LikeC4LanguageServices.js +12 -2
  3. package/dist/Rpc.js +2 -2
  4. package/dist/browser.d.ts +1 -1
  5. package/dist/browser.js +1 -1
  6. package/dist/bundled.mjs +4259 -3106
  7. package/dist/empty.d.ts +2 -0
  8. package/dist/empty.js +1 -0
  9. package/dist/filesystem/ChokidarWatcher.d.ts +14 -0
  10. package/dist/filesystem/ChokidarWatcher.js +64 -0
  11. package/dist/filesystem/FileSystemWatcher.d.ts +19 -0
  12. package/dist/filesystem/FileSystemWatcher.js +11 -0
  13. package/dist/filesystem/LikeC4FileSystem.d.ts +5 -0
  14. package/dist/filesystem/LikeC4FileSystem.js +56 -0
  15. package/dist/filesystem/index.d.ts +20 -0
  16. package/dist/filesystem/index.js +16 -0
  17. package/dist/index.d.ts +18 -4
  18. package/dist/index.js +23 -10
  19. package/dist/mcp/{sseserver/MCPServerFactory.d.ts → MCPServerFactory.d.ts} +1 -1
  20. package/dist/mcp/MCPServerFactory.js +69 -0
  21. package/dist/mcp/NoopLikeC4MCPServer.d.ts +4 -10
  22. package/dist/mcp/NoopLikeC4MCPServer.js +5 -10
  23. package/dist/mcp/interfaces.d.ts +7 -5
  24. package/dist/mcp/interfaces.js +4 -0
  25. package/dist/mcp/server/StdioLikeC4MCPServer.d.ts +16 -0
  26. package/dist/mcp/server/StdioLikeC4MCPServer.js +43 -0
  27. package/dist/mcp/{sseserver/MCPServer.d.ts → server/StreamableLikeC4MCPServer.d.ts} +3 -2
  28. package/dist/mcp/server/StreamableLikeC4MCPServer.js +156 -0
  29. package/dist/mcp/server/WithMCPServer.d.ts +2 -0
  30. package/dist/mcp/server/WithMCPServer.js +57 -0
  31. package/dist/mcp/tools/_common.d.ts +24 -5
  32. package/dist/mcp/tools/_common.js +31 -3
  33. package/dist/mcp/tools/find-relationships.d.ts +13 -0
  34. package/dist/mcp/tools/find-relationships.js +151 -0
  35. package/dist/mcp/tools/list-projects.js +40 -12
  36. package/dist/mcp/tools/open-view.d.ts +4 -3
  37. package/dist/mcp/tools/open-view.js +37 -14
  38. package/dist/mcp/tools/{read-project-elements.d.ts → read-deployment.d.ts} +6 -3
  39. package/dist/mcp/tools/read-deployment.js +130 -0
  40. package/dist/mcp/tools/read-element.d.ts +4 -3
  41. package/dist/mcp/tools/read-element.js +114 -51
  42. package/dist/mcp/tools/read-project-summary.d.ts +3 -2
  43. package/dist/mcp/tools/read-project-summary.js +139 -32
  44. package/dist/mcp/tools/read-view.d.ts +4 -3
  45. package/dist/mcp/tools/read-view.js +146 -105
  46. package/dist/mcp/tools/search-element.js +81 -30
  47. package/dist/mcp/utils.js +7 -4
  48. package/dist/model/builder/MergedSpecification.d.ts +1 -1
  49. package/dist/model/builder/buildModel.d.ts +1 -1
  50. package/dist/module.d.ts +9 -9
  51. package/dist/module.js +24 -29
  52. package/dist/protocol.d.ts +1 -1
  53. package/dist/protocol.js +1 -1
  54. package/dist/test/testServices.js +3 -2
  55. package/dist/workspace/LangiumDocuments.d.ts +4 -1
  56. package/dist/workspace/LangiumDocuments.js +15 -0
  57. package/dist/workspace/ProjectsManager.d.ts +1 -1
  58. package/dist/workspace/ProjectsManager.js +18 -19
  59. package/dist/workspace/WorkspaceManager.d.ts +9 -2
  60. package/dist/workspace/WorkspaceManager.js +30 -39
  61. package/package.json +16 -12
  62. package/dist/LikeC4FileSystem.d.ts +0 -14
  63. package/dist/LikeC4FileSystem.js +0 -39
  64. package/dist/mcp/sseserver/MCPServer.js +0 -80
  65. package/dist/mcp/sseserver/MCPServerFactory.js +0 -50
  66. package/dist/mcp/sseserver/WithMCPServer.d.ts +0 -9
  67. package/dist/mcp/sseserver/WithMCPServer.js +0 -53
  68. package/dist/mcp/tools/read-project-elements.js +0 -93
package/dist/module.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
2
2
  import {
3
3
  DocumentState,
4
- EmptyFileSystem,
5
4
  inject,
6
5
  WorkspaceCache
7
6
  } from "langium";
@@ -10,6 +9,9 @@ import {
10
9
  createDefaultSharedModule
11
10
  } from "langium/lsp";
12
11
  import { LikeC4DocumentationProvider } from "./documentation/index.js";
12
+ import {
13
+ NoopFileSystem
14
+ } from "./filesystem/index.js";
13
15
  import { LikeC4Formatter } from "./formatting/LikeC4Formatter.js";
14
16
  import {
15
17
  LikeC4GeneratedModule,
@@ -25,7 +27,10 @@ import {
25
27
  LikeC4HoverProvider,
26
28
  LikeC4SemanticTokenProvider
27
29
  } from "./lsp/index.js";
28
- import { NoopLikeC4MCPServer, NoopLikeC4MCPServerFactory } from "./mcp/NoopLikeC4MCPServer.js";
30
+ import {
31
+ NoMCPServer
32
+ } from "./mcp/interfaces.js";
33
+ import { LikeC4MCPServerFactory } from "./mcp/MCPServerFactory.js";
29
34
  import {
30
35
  DefaultLikeC4ModelBuilder,
31
36
  DeploymentsIndex,
@@ -54,7 +59,7 @@ import {
54
59
  LikeC4WorkspaceManager,
55
60
  ProjectsManager
56
61
  } from "./workspace/index.js";
57
- const LikeC4SharedModule = {
62
+ const createLikeC4SharedModule = (context) => ({
58
63
  lsp: {
59
64
  NodeKindProvider: (services) => new NodeKindProvider(services),
60
65
  WorkspaceSymbolProvider: (services) => new WorkspaceSymbolProvider(services)
@@ -63,13 +68,15 @@ const LikeC4SharedModule = {
63
68
  IndexManager: (services) => new IndexManager(services),
64
69
  LangiumDocuments: (services) => new LangiumDocuments(services),
65
70
  ProjectsManager: (services) => new ProjectsManager(services),
66
- WorkspaceManager: (services) => new LikeC4WorkspaceManager(services)
71
+ WorkspaceManager: (services) => new LikeC4WorkspaceManager(services),
72
+ FileSystemProvider: (services) => context.fileSystemProvider(services),
73
+ FileSystemWatcher: (services) => context.fileSystemWatcher(services)
67
74
  }
68
- };
75
+ });
69
76
  function bind(Type) {
70
77
  return (services) => new Type(services);
71
78
  }
72
- export const LikeC4Module = {
79
+ export const createLikeC4Module = (context) => ({
73
80
  documentation: {
74
81
  DocumentationProvider: bind(LikeC4DocumentationProvider)
75
82
  },
@@ -79,8 +86,8 @@ export const LikeC4Module = {
79
86
  ValidatedWorkspaceCache: (services) => new WorkspaceCache(services.shared, DocumentState.Validated),
80
87
  Rpc: bind(Rpc),
81
88
  mcp: {
82
- Server: bind(NoopLikeC4MCPServer),
83
- ServerFactory: bind(NoopLikeC4MCPServerFactory)
89
+ Server: (services) => context.mcpServer(services),
90
+ ServerFactory: bind(LikeC4MCPServerFactory)
84
91
  },
85
92
  likec4: {
86
93
  LanguageServices: bind(DefaultLikeC4LanguageServices),
@@ -119,13 +126,16 @@ export const LikeC4Module = {
119
126
  parser: {
120
127
  ValueConverter: bind(LikeC4ValueConverter)
121
128
  }
122
- };
123
- export function createCustomLanguageServices(context, module, module2, module3) {
129
+ });
130
+ export function createLanguageServices(context, module, module2, module3) {
124
131
  const shared = createSharedServices(context);
125
132
  const modules = [
126
133
  createDefaultModule({ shared }),
127
134
  LikeC4GeneratedModule,
128
- LikeC4Module,
135
+ createLikeC4Module({
136
+ ...NoMCPServer,
137
+ ...context
138
+ }),
129
139
  module,
130
140
  module2,
131
141
  module3
@@ -142,30 +152,15 @@ export function createCustomLanguageServices(context, module, module2, module3)
142
152
  }
143
153
  export function createSharedServices(context = {}) {
144
154
  const moduleContext = {
145
- ...EmptyFileSystem,
155
+ ...NoMCPServer,
156
+ ...NoopFileSystem,
146
157
  ...context
147
158
  };
148
159
  return inject(
149
160
  createDefaultSharedModule(moduleContext),
150
161
  LikeC4GeneratedSharedModule,
151
- LikeC4SharedModule
152
- );
153
- }
154
- export function createLanguageServices(context = {}) {
155
- const shared = createSharedServices(context);
156
- const likec4 = inject(
157
- createDefaultModule({ shared }),
158
- LikeC4GeneratedModule,
159
- LikeC4Module
162
+ createLikeC4SharedModule(moduleContext)
160
163
  );
161
- shared.ServiceRegistry.register(likec4);
162
- registerValidationChecks(likec4);
163
- if (!context.connection) {
164
- shared.workspace.ConfigurationProvider.initialized({});
165
- } else {
166
- likec4.Rpc.init();
167
- }
168
- return { shared, likec4 };
169
164
  }
170
165
  function _merge(target, source) {
171
166
  if (source) {
@@ -1,5 +1,5 @@
1
1
  import type { ComputedLikeC4ModelData, ComputedView, DeploymentFqn, DiagramView, Fqn, LayoutedLikeC4ModelData, NonEmptyArray, ProjectId, RelationId, ViewChange, ViewId } from '@likec4/core';
2
- import { NotificationType, RequestType, RequestType0 } from 'vscode-languageserver-protocol';
2
+ import { NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc';
3
3
  import type { DiagnosticSeverity, DocumentUri, Location, Position, Range, URI } from 'vscode-languageserver-types';
4
4
  import type { ProjectConfig } from './config';
5
5
  export declare namespace DidChangeModelNotification {
package/dist/protocol.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NotificationType, RequestType, RequestType0 } from "vscode-languageserver-protocol";
1
+ import { NotificationType, RequestType, RequestType0 } from "vscode-jsonrpc";
2
2
  export var DidChangeModelNotification;
3
3
  ((DidChangeModelNotification2) => {
4
4
  DidChangeModelNotification2.type = new NotificationType("likec4/onDidChangeModel");
@@ -1,12 +1,13 @@
1
- import { DocumentState, EmptyFileSystem, TextDocument, UriUtils } from "langium";
1
+ import { DocumentState, TextDocument, UriUtils } from "langium";
2
2
  import * as assert from "node:assert";
3
3
  import { entries } from "remeda";
4
4
  import stripIndent from "strip-indent";
5
5
  import { DiagnosticSeverity } from "vscode-languageserver-types";
6
6
  import { URI, Utils } from "vscode-uri";
7
+ import { NoopFileSystem } from "../filesystem/index.js";
7
8
  import { createLanguageServices } from "../module.js";
8
9
  export function createTestServices(workspace = "file:///test/workspace") {
9
- const services = createLanguageServices(EmptyFileSystem).likec4;
10
+ const services = createLanguageServices(NoopFileSystem).likec4;
10
11
  const metaData = services.LanguageMetaData;
11
12
  const langiumDocuments = services.shared.workspace.LangiumDocuments;
12
13
  const documentBuilder = services.shared.workspace.DocumentBuilder;
@@ -1,10 +1,13 @@
1
1
  import type { NonEmptyArray, ProjectId } from '@likec4/core';
2
- import { type Stream, DefaultLangiumDocuments } from 'langium';
2
+ import type { LangiumDocument, Stream } from 'langium';
3
+ import { DefaultLangiumDocuments } from 'langium';
3
4
  import { type LikeC4LangiumDocument } from '../ast';
4
5
  import type { LikeC4SharedServices } from '../module';
5
6
  export declare class LangiumDocuments extends DefaultLangiumDocuments {
6
7
  protected services: LikeC4SharedServices;
8
+ protected compare: any;
7
9
  constructor(services: LikeC4SharedServices);
10
+ addDocument(document: LangiumDocument): void;
8
11
  /**
9
12
  * Returns all user documents, excluding built-in documents.
10
13
  */
@@ -1,12 +1,27 @@
1
+ import { compareNaturalHierarchically } from "@likec4/core/utils";
1
2
  import { DefaultLangiumDocuments } from "langium";
2
3
  import { groupBy, prop } from "remeda";
3
4
  import { isLikeC4LangiumDocument } from "../ast.js";
4
5
  import { isLikeC4Builtin } from "../likec4lib.js";
6
+ const compare = compareNaturalHierarchically("/", true);
7
+ const ensureOrder = (a, b) => compare(a.uri.path, b.uri.path);
5
8
  export class LangiumDocuments extends DefaultLangiumDocuments {
6
9
  constructor(services) {
7
10
  super(services);
8
11
  this.services = services;
9
12
  }
13
+ compare = compareNaturalHierarchically("/", true);
14
+ addDocument(document) {
15
+ const uriString = document.uri.toString();
16
+ if (this.documentMap.has(uriString)) {
17
+ throw new Error(`A document with the URI '${uriString}' is already present.`);
18
+ }
19
+ const docs = [...this.documentMap.values(), document].sort(ensureOrder);
20
+ this.documentMap.clear();
21
+ for (const doc of docs) {
22
+ this.documentMap.set(doc.uri.toString(), doc);
23
+ }
24
+ }
10
25
  /**
11
26
  * Returns all user documents, excluding built-in documents.
12
27
  */
@@ -5,7 +5,7 @@ import type { Tagged } from 'type-fest';
5
5
  import { ProjectConfig } from '../config';
6
6
  import type { LikeC4SharedServices } from '../module';
7
7
  /**
8
- * A tagged string that represents a project folder.
8
+ * A tagged string that represents a project folder URI
9
9
  * Always has trailing slash.
10
10
  */
11
11
  export type ProjectFolder = Tagged<string, 'ProjectFolder'>;
@@ -10,23 +10,26 @@ import PQueue from "p-queue";
10
10
  import picomatch from "picomatch";
11
11
  import { hasAtLeast, isNullish, isTruthy, map, pickBy, pipe, prop, sortBy } from "remeda";
12
12
  import {
13
- hasProtocol,
14
13
  joinRelativeURL,
15
- normalizeURL,
16
14
  parseFilename,
17
15
  withoutProtocol,
18
- withProtocol,
19
16
  withTrailingSlash
20
17
  } from "ufo";
21
18
  import { parseConfigJson, validateConfig } from "../config/index.js";
22
19
  import { logger as mainLogger } from "../logger.js";
23
20
  const logger = mainLogger.getChild("ProjectsManager");
24
- export function ProjectFolder(folder) {
25
- if (URI.isUri(folder)) {
26
- folder = folder.toString();
21
+ function normalizeUri(uri) {
22
+ if (URI.isUri(uri)) {
23
+ return uri.toString();
24
+ } else if (typeof uri === "string") {
25
+ return uri.startsWith("file://") ? uri : URI.file(uri).toString();
26
+ } else {
27
+ return uri.uri.toString();
27
28
  }
28
- folder = hasProtocol(folder) ? folder : withProtocol(folder, "file://");
29
- return withTrailingSlash(normalizeURL(folder));
29
+ }
30
+ export function ProjectFolder(folder) {
31
+ folder = normalizeUri(folder);
32
+ return withTrailingSlash(folder);
30
33
  }
31
34
  export class ProjectsManager {
32
35
  constructor(services) {
@@ -115,7 +118,7 @@ export class ProjectsManager {
115
118
  }
116
119
  ensureProjectId(projectId) {
117
120
  if (projectId === ProjectsManager.DefaultProjectId) {
118
- return projectId;
121
+ return this.defaultProjectId ?? ProjectsManager.DefaultProjectId;
119
122
  }
120
123
  if (projectId) {
121
124
  invariant(this.projectIdToFolder.has(projectId), `Project ID ${projectId} is not registered`);
@@ -131,7 +134,7 @@ export class ProjectsManager {
131
134
  }
132
135
  checkIfExcluded(document) {
133
136
  if (typeof document === "string" || URI.isUri(document)) {
134
- let docUriAsString = typeof document === "string" ? document : document.toString();
137
+ let docUriAsString = normalizeUri(document);
135
138
  const project = this.findProjectForDocument(docUriAsString);
136
139
  return project.exclude ? project.exclude(withoutProtocol(docUriAsString)) : false;
137
140
  }
@@ -201,6 +204,9 @@ ${loggable(error)}`
201
204
  let mustReset = !!project && !deepEqual(project.config, config);
202
205
  let id;
203
206
  if (!project) {
207
+ if (this.projectIdToFolder.has(config.name)) {
208
+ logger.warn`Project "${config.name}" already exists, generating unique ID`;
209
+ }
204
210
  id = this.uniqueProjectId(config.name);
205
211
  project = {
206
212
  id,
@@ -241,14 +247,7 @@ ${loggable(error)}`
241
247
  return project;
242
248
  }
243
249
  belongsTo(document) {
244
- let documentUri;
245
- if (typeof document === "string") {
246
- documentUri = hasProtocol(document) ? document : withProtocol(document, "file://");
247
- } else if (URI.isUri(document)) {
248
- documentUri = document.toString();
249
- } else {
250
- documentUri = document.uri.toString();
251
- }
250
+ const documentUri = normalizeUri(document);
252
251
  return this.findProjectForDocument(documentUri).id;
253
252
  }
254
253
  async reloadProjects(token) {
@@ -272,7 +271,7 @@ ${loggable(error)}`
272
271
  const configFiles = [];
273
272
  for (const folder of folders) {
274
273
  try {
275
- const files = await this.services.workspace.FileSystemProvider.readDirectory(URI.parse(folder.uri));
274
+ const files = await this.services.workspace.FileSystemProvider.scanProjectFiles(URI.parse(folder.uri));
276
275
  for (const file of files) {
277
276
  if (file.isFile && this.isConfigFile(file.uri)) {
278
277
  configFiles.push(file);
@@ -1,12 +1,19 @@
1
- import type { FileSelector, FileSystemNode, LangiumDocument } from 'langium';
1
+ import type { BuildOptions, FileSelector, FileSystemNode, LangiumDocument, LangiumDocumentFactory } from 'langium';
2
2
  import { DefaultWorkspaceManager } from 'langium';
3
3
  import type { WorkspaceFolder } from 'vscode-languageserver';
4
4
  import { URI } from 'vscode-uri';
5
+ import type { FileSystemProvider } from '../filesystem';
5
6
  import type { LikeC4SharedServices } from '../module';
6
7
  export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
7
8
  private services;
8
- private documentFactory;
9
+ protected readonly documentFactory: LangiumDocumentFactory;
10
+ protected readonly fileSystemProvider: FileSystemProvider;
11
+ initialBuildOptions: BuildOptions;
9
12
  constructor(services: LikeC4SharedServices);
13
+ /**
14
+ * First load all project config files, then load all documents in the workspace.
15
+ */
16
+ protected performStartup(folders: WorkspaceFolder[]): Promise<LangiumDocument[]>;
10
17
  /**
11
18
  * Load all additional documents that shall be visible in the context of the given workspace
12
19
  * folders and add them to the collector. This can be used to include built-in libraries of
@@ -8,58 +8,49 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
8
8
  super(services);
9
9
  this.services = services;
10
10
  this.documentFactory = services.workspace.LangiumDocumentFactory;
11
+ this.fileSystemProvider = services.workspace.FileSystemProvider;
11
12
  }
12
13
  documentFactory;
14
+ fileSystemProvider;
15
+ initialBuildOptions = {
16
+ eagerLinking: true,
17
+ validation: true
18
+ };
13
19
  /**
14
- * Load all additional documents that shall be visible in the context of the given workspace
15
- * folders and add them to the collector. This can be used to include built-in libraries of
16
- * your language, which can be either loaded from provided files or constructed in memory.
20
+ * First load all project config files, then load all documents in the workspace.
17
21
  */
18
- async loadAdditionalDocuments(folders, collector) {
19
- const projects = this.services.workspace.ProjectsManager;
22
+ async performStartup(folders) {
23
+ this.folders ??= folders;
24
+ const configFiles = [];
20
25
  for (const folder of folders) {
21
26
  try {
22
- const content = await this.fileSystemProvider.readDirectory(URI.parse(folder.uri));
23
- for (const entry of content) {
24
- try {
25
- await projects.loadConfigFile(entry);
26
- } catch (error) {
27
- logError(error);
28
- }
29
- }
27
+ const uri = URI.parse(folder.uri);
28
+ const found = await this.fileSystemProvider.scanProjectFiles(uri);
29
+ configFiles.push(...found);
30
+ this.services.workspace.FileSystemWatcher.watch(uri.fsPath);
31
+ } catch (error) {
32
+ logError(error);
33
+ }
34
+ }
35
+ const projects = this.services.workspace.ProjectsManager;
36
+ for (const entry of configFiles) {
37
+ try {
38
+ await projects.loadConfigFile(entry);
30
39
  } catch (error) {
31
40
  logError(error);
32
41
  }
33
42
  }
43
+ return await super.performStartup(folders);
44
+ }
45
+ /**
46
+ * Load all additional documents that shall be visible in the context of the given workspace
47
+ * folders and add them to the collector. This can be used to include built-in libraries of
48
+ * your language, which can be either loaded from provided files or constructed in memory.
49
+ */
50
+ async loadAdditionalDocuments(folders, collector) {
34
51
  collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)));
35
52
  await super.loadAdditionalDocuments(folders, collector);
36
53
  }
37
- // /**
38
- // * We override the default implementation to process project config files during the traversal.
39
- // * This is necessary to ensure that the project config files are loaded and processed correctly.
40
- // */
41
- // protected override async traverseFolder(
42
- // workspaceFolder: WorkspaceFolder,
43
- // folderPath: URI,
44
- // fileExtensions: string[],
45
- // collector: (document: LangiumDocument) => void,
46
- // ): Promise<void> {
47
- // // Then load other files
48
- // for (const entry of nonConfigFiles) {
49
- // try {
50
- // if (this.includeEntry(workspaceFolder, entry, fileExtensions)) {
51
- // if (entry.isDirectory) {
52
- // await this.traverseFolder(workspaceFolder, entry.uri, fileExtensions, collector)
53
- // } else if (entry.isFile) {
54
- // const document = await this.langiumDocuments.getOrCreateDocument(entry.uri)
55
- // collector(document)
56
- // }
57
- // }
58
- // } catch (error) {
59
- // logError(error)
60
- // }
61
- // }
62
- // }
63
54
  /**
64
55
  * Determine whether the given folder entry shall be included while indexing the workspace.
65
56
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "1.37.0",
4
+ "version": "1.38.1",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -89,21 +89,24 @@
89
89
  "access": "public"
90
90
  },
91
91
  "dependencies": {
92
- "@hpcc-js/wasm-graphviz": "1.10.0"
92
+ "@hpcc-js/wasm-graphviz": "1.11.0"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/chroma-js": "^3.1.1",
96
96
  "@types/natural-compare-lite": "^1.4.2",
97
- "@modelcontextprotocol/sdk": "^1.13.2",
97
+ "@types/vscode": "^1.84.0",
98
+ "@modelcontextprotocol/sdk": "^1.17.2",
98
99
  "@msgpack/msgpack": "^3.1.2",
99
100
  "@smithy/util-base64": "^4.0.0",
100
- "@types/express": "^5.0.3",
101
+ "@hono/node-server": "^1.14.4",
101
102
  "@types/node": "~20.19.2",
102
103
  "@types/picomatch": "^4.0.2",
103
104
  "@types/which": "^3.0.4",
105
+ "chokidar": "^4.0.3",
104
106
  "chroma-js": "^3.1.2",
107
+ "defu": "^6.1.4",
105
108
  "esm-env": "^1.2.2",
106
- "express": "^5.1.0",
109
+ "hono": "^4.9.0",
107
110
  "fast-equals": "^5.2.2",
108
111
  "fdir": "6.4.6",
109
112
  "indent-string": "^5.0.0",
@@ -116,10 +119,11 @@
116
119
  "p-timeout": "6.1.4",
117
120
  "picomatch": "^4.0.3",
118
121
  "pretty-ms": "^9.2.0",
122
+ "fetch-to-node": "^2.1.0",
119
123
  "remeda": "^2.23.1",
120
124
  "strip-indent": "^4.0.0",
121
125
  "tsx": "4.20.3",
122
- "turbo": "2.5.5",
126
+ "turbo": "2.5.6",
123
127
  "type-fest": "^4.41.0",
124
128
  "typescript": "5.9.2",
125
129
  "ufo": "1.6.1",
@@ -133,17 +137,17 @@
133
137
  "vscode-uri": "3.1.0",
134
138
  "which": "^5.0.0",
135
139
  "zod": "3.25.67",
136
- "@likec4/core": "1.37.0",
137
- "@likec4/icons": "1.37.0",
138
- "@likec4/layouts": "1.37.0",
139
- "@likec4/log": "1.37.0",
140
- "@likec4/tsconfig": "1.37.0"
140
+ "@likec4/core": "1.38.1",
141
+ "@likec4/layouts": "1.38.1",
142
+ "@likec4/tsconfig": "1.38.1",
143
+ "@likec4/icons": "1.38.1",
144
+ "@likec4/log": "1.38.1"
141
145
  },
142
146
  "scripts": {
143
147
  "typecheck": "tsc -b --verbose",
144
148
  "build": "unbuild",
145
149
  "pack": "pnpm pack",
146
- "pregenerate": "rm -f src/generated/*",
150
+ "pregenerate": "rm -f src/generated/* || true",
147
151
  "watch:langium": "langium generate --watch",
148
152
  "watch:ts": "tsc --watch",
149
153
  "generate": "langium generate && tsx scripts/generate-icons.ts",
@@ -1,14 +0,0 @@
1
- import { type FileSystemNode, URI } from 'langium';
2
- import { NodeFileSystemProvider } from 'langium/node';
3
- export declare const LikeC4FileSystem: {
4
- fileSystemProvider: () => SymLinkTraversingFileSystemProvider;
5
- };
6
- /**
7
- * A file system provider that follows symbolic links.
8
- * @see https://github.com/likec4/likec4/pull/1213
9
- */
10
- declare class SymLinkTraversingFileSystemProvider extends NodeFileSystemProvider {
11
- readFile(uri: URI): Promise<string>;
12
- readDirectory(folderPath: URI): Promise<FileSystemNode[]>;
13
- }
14
- export {};
@@ -1,39 +0,0 @@
1
- import { fdir } from "fdir";
2
- import { URI } from "langium";
3
- import { NodeFileSystemProvider } from "langium/node";
4
- import { LikeC4LanguageMetaData } from "./generated/module.js";
5
- import { Content, isLikeC4Builtin } from "./likec4lib.js";
6
- import { logError } from "./logger.js";
7
- import { ProjectsManager } from "./workspace/ProjectsManager.js";
8
- export const LikeC4FileSystem = {
9
- fileSystemProvider: () => new SymLinkTraversingFileSystemProvider()
10
- };
11
- const SearchExtension = [
12
- ...LikeC4LanguageMetaData.fileExtensions,
13
- ...ProjectsManager.ConfigFileNames
14
- ];
15
- const hasExtension = (path) => SearchExtension.some((ext) => path.endsWith(ext));
16
- class SymLinkTraversingFileSystemProvider extends NodeFileSystemProvider {
17
- async readFile(uri) {
18
- if (isLikeC4Builtin(uri)) {
19
- return Promise.resolve(Content);
20
- }
21
- return await super.readFile(uri);
22
- }
23
- async readDirectory(folderPath) {
24
- const entries = [];
25
- try {
26
- const crawled = await new fdir().withSymlinks({ resolvePaths: false }).withFullPaths().filter(hasExtension).crawl(folderPath.fsPath).withPromise();
27
- for (const path of crawled) {
28
- entries.push({
29
- isFile: true,
30
- isDirectory: false,
31
- uri: URI.file(path)
32
- });
33
- }
34
- } catch (error) {
35
- logError(error);
36
- }
37
- return entries;
38
- }
39
- }
@@ -1,80 +0,0 @@
1
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
2
- import express from "express";
3
- import { logger } from "../utils.js";
4
- export class SSELikeC4MCPServer {
5
- constructor(services) {
6
- this.services = services;
7
- }
8
- // Store transports by session ID to send notifications
9
- transports = {};
10
- server = void 0;
11
- _port = 33335;
12
- get isStarted() {
13
- return this.server?.listening === true;
14
- }
15
- get port() {
16
- return this._port;
17
- }
18
- async dispose() {
19
- await this.stop();
20
- }
21
- async start(port = 33335) {
22
- if (this.server) {
23
- if (this.port === port) {
24
- return;
25
- }
26
- await this.stop();
27
- }
28
- logger.info("Starting MCP server on port {port}", { port });
29
- this._port = port;
30
- const mcp = this.services.mcp.ServerFactory.create();
31
- const app = express();
32
- app.get("/sse", async (_, res) => {
33
- const transport = new SSEServerTransport("/messages", res);
34
- this.transports[transport.sessionId] = transport;
35
- logger.debug`SSE connection established, sessionId: ${transport.sessionId}`;
36
- res.on("close", () => {
37
- delete this.transports[transport.sessionId];
38
- });
39
- await mcp.connect(transport);
40
- });
41
- app.post("/messages", async (req, res) => {
42
- const sessionId = req.query["sessionId"];
43
- const transport = this.transports[sessionId];
44
- if (transport) {
45
- logger.debug`SSE message received, sessionId: ${sessionId}`;
46
- await transport.handlePostMessage(req, res);
47
- } else {
48
- res.status(400).send("No transport found for sessionId");
49
- }
50
- });
51
- return new Promise((resolve, reject) => {
52
- this.server = app.listen(this._port, (err) => {
53
- if (err) {
54
- reject(err);
55
- return;
56
- }
57
- logger.info("MCP server listening on port {port}", { port: this._port });
58
- resolve();
59
- });
60
- });
61
- }
62
- async stop() {
63
- this.transports = {};
64
- const server = this.server;
65
- if (!server) {
66
- return;
67
- }
68
- logger.info("Stopping MCP server");
69
- this.server = void 0;
70
- return new Promise((resolve) => {
71
- server.close((err) => {
72
- if (err) {
73
- logger.error("Failed to stop MCP server", { err });
74
- }
75
- logger.info("MCP server stopped");
76
- resolve();
77
- });
78
- });
79
- }
80
- }
@@ -1,50 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import packageJson from "../../../package.json" with { type: "json" };
3
- import { listProjects } from "../tools/list-projects.js";
4
- import { openView } from "../tools/open-view.js";
5
- import { readElement } from "../tools/read-element.js";
6
- import { readProjectElements } from "../tools/read-project-elements.js";
7
- import { readProjectSummary } from "../tools/read-project-summary.js";
8
- import { readView } from "../tools/read-view.js";
9
- import { searchElement } from "../tools/search-element.js";
10
- export class LikeC4MCPServerFactory {
11
- constructor(services) {
12
- this.services = services;
13
- }
14
- create(options) {
15
- const isInEditor = this.services.shared.lsp.Connection !== void 0;
16
- const mcp = new McpServer({
17
- name: "LikeC4",
18
- version: packageJson.version
19
- }, {
20
- instructions: `Provides access to LikeC4 model.
21
- Available tools:
22
- - list-projects: List all available LikeC4 projects in the workspace
23
- - read-project-summary: to understand project specifications (what element kinds, tags, metadata keys are available) and available project views
24
- - read-project-elements: list all elements in the project
25
- - search-element: Search for LikeC4 element by partial match of id, title, kind, shape or tags
26
- - read-element: all information about the element (includes source location)
27
- - read-view: all information about the view (includes source location)
28
- ${isInEditor ? "- open-view: opens the panel in the editor with the LikeC4 view" : ""}
29
-
30
- Documentation for LikeC4 is available at https://likec4.dev/llms-full.txt
31
- `,
32
- ...options,
33
- capabilities: {
34
- tools: {},
35
- resources: {},
36
- ...options?.capabilities
37
- }
38
- });
39
- mcp.registerTool(...listProjects(this.services.likec4.LanguageServices));
40
- mcp.registerTool(...readProjectSummary(this.services.likec4.LanguageServices));
41
- mcp.registerTool(...readProjectElements(this.services.likec4.LanguageServices));
42
- mcp.registerTool(...readElement(this.services.likec4.LanguageServices));
43
- mcp.registerTool(...readView(this.services.likec4.LanguageServices));
44
- mcp.registerTool(...searchElement(this.services.likec4.LanguageServices));
45
- if (isInEditor) {
46
- mcp.registerTool(...openView(this.services.likec4.LanguageServices));
47
- }
48
- return mcp;
49
- }
50
- }