@likec4/language-server 1.28.1 → 1.29.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.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { getLspConnectionSink, logger as lspLogger } from './logger';
4
4
  export type { DocumentParser, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model';
5
5
  export type { LikeC4LanguageServices } from './LikeC4LanguageServices';
6
6
  export { isLikeC4Builtin } from './likec4lib';
7
+ export { LikeC4MCPTools } from './mcp/LikeC4MCPTools';
7
8
  export { createCustomLanguageServices, createLanguageServices, LikeC4Module } from './module';
8
9
  export type { LikeC4Services, LikeC4SharedServices } from './module';
9
10
  export type { LikeC4Views } from './views';
package/dist/index.js CHANGED
@@ -3,10 +3,12 @@ import { startLanguageServer as startLanguim } from "langium/lsp";
3
3
  import { createConnection, ProposedFeatures } from "vscode-languageserver/node";
4
4
  import { LikeC4FileSystem } from "./LikeC4FileSystem.js";
5
5
  import { getTelemetrySink, logger } from "./logger.js";
6
+ import { WithMCPServer } from "./mcp/sseserver/with-mcp-server.js";
6
7
  import { createCustomLanguageServices } from "./module.js";
7
8
  import { ConfigurableLayouter } from "./views/configurable-layouter.js";
8
9
  export { getLspConnectionSink, logger as lspLogger } from "./logger.js";
9
10
  export { isLikeC4Builtin } from "./likec4lib.js";
11
+ export { LikeC4MCPTools } from "./mcp/LikeC4MCPTools.js";
10
12
  export { createCustomLanguageServices, createLanguageServices, LikeC4Module } from "./module.js";
11
13
  export { LikeC4FileSystem };
12
14
  export function startLanguageServer() {
@@ -26,7 +28,11 @@ export function startLanguageServer() {
26
28
  ]
27
29
  });
28
30
  logger.info("Starting LikeC4 language server");
29
- const services = createCustomLanguageServices({ connection, ...LikeC4FileSystem }, ConfigurableLayouter);
31
+ const services = createCustomLanguageServices(
32
+ { connection, ...LikeC4FileSystem },
33
+ ConfigurableLayouter,
34
+ WithMCPServer
35
+ );
30
36
  startLanguim(services.shared);
31
37
  return services;
32
38
  }
@@ -27,7 +27,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
27
27
  });
28
28
  return "prune";
29
29
  }
30
- if (ast.isRelation(node) || ast.isOutgoingRelationExpr(node) && "kind" in node) {
30
+ if ((ast.isRelation(node) || ast.isOutgoingRelationExpr(node) || ast.isDeploymentRelation(node)) && "kind" in node) {
31
31
  acceptor({
32
32
  node,
33
33
  property: "kind",
@@ -43,7 +43,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
43
43
  acceptor({
44
44
  node,
45
45
  property: "value",
46
- type: SemanticTokenTypes.variable,
46
+ type: SemanticTokenTypes.interface,
47
47
  modifier: [
48
48
  SemanticTokenModifiers.definition,
49
49
  SemanticTokenModifiers.readonly
@@ -60,17 +60,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
60
60
  SemanticTokenModifiers.readonly
61
61
  ]
62
62
  });
63
- if (ast.isFqnRefExpr(node) && node.selector) {
64
- acceptor({
65
- node,
66
- property: "selector",
67
- type: SemanticTokenTypes.variable,
68
- modifier: [
69
- SemanticTokenModifiers.definition,
70
- SemanticTokenModifiers.readonly
71
- ]
72
- });
73
- }
74
63
  return "prune";
75
64
  }
76
65
  if (ast.isWhereRelationKind(node) && isTruthy(node.value)) {
@@ -107,7 +96,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
107
96
  type: SemanticTokenTypes.type,
108
97
  modifier: [SemanticTokenModifiers.definition]
109
98
  });
110
- return "prune";
99
+ return;
111
100
  }
112
101
  if (ast.isGlobalStyleGroup(node) || ast.isGlobalStyle(node)) {
113
102
  acceptor({
@@ -164,7 +153,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
164
153
  type: SemanticTokenTypes.type,
165
154
  modifier: [SemanticTokenModifiers.definition]
166
155
  });
167
- return "prune";
156
+ return;
168
157
  }
169
158
  if (ast.isFqnRef(node) || ast.isStrictFqnRef(node)) {
170
159
  acceptor({
@@ -190,18 +179,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
190
179
  });
191
180
  return !node.parent ? "prune" : void 0;
192
181
  }
193
- if (ast.isImported(node)) {
194
- acceptor({
195
- node,
196
- property: "imported",
197
- type: SemanticTokenTypes.variable,
198
- modifier: [
199
- SemanticTokenModifiers.definition,
200
- SemanticTokenModifiers.readonly
201
- ]
202
- });
203
- return "prune";
204
- }
205
182
  if (ast.isSpecificationColor(node)) {
206
183
  acceptor({
207
184
  node,
@@ -310,7 +287,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
310
287
  acceptor({
311
288
  node,
312
289
  property: "name",
313
- type: SemanticTokenTypes.function,
290
+ type: SemanticTokenTypes.variable,
314
291
  modifier: [
315
292
  SemanticTokenModifiers.declaration,
316
293
  SemanticTokenModifiers.readonly
@@ -0,0 +1,16 @@
1
+ import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { LikeC4Services } from '../module';
4
+ export interface LikeC4MCPServer {
5
+ start(port: number): Promise<void>;
6
+ stop(): Promise<void>;
7
+ }
8
+ export declare class NoopLikeC4MCPServer implements LikeC4MCPServer {
9
+ start(port: number): Promise<never>;
10
+ stop(): Promise<never>;
11
+ }
12
+ export declare class LikeC4MCPServerFactory {
13
+ private services;
14
+ constructor(services: LikeC4Services);
15
+ create(options?: ServerOptions): McpServer;
16
+ }
@@ -0,0 +1,86 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import packageJson from "../../package.json" with { type: "json" };
3
+ import { LikeC4MCPTools } from "./LikeC4MCPTools.js";
4
+ export class NoopLikeC4MCPServer {
5
+ start(port) {
6
+ return Promise.reject(new Error("Not implemented"));
7
+ }
8
+ stop() {
9
+ return Promise.reject(new Error("Not implemented"));
10
+ }
11
+ }
12
+ function toolResponse(text) {
13
+ return {
14
+ content: [{
15
+ type: "text",
16
+ text
17
+ }]
18
+ };
19
+ }
20
+ export class LikeC4MCPServerFactory {
21
+ constructor(services) {
22
+ this.services = services;
23
+ }
24
+ create(options) {
25
+ const {
26
+ instructions,
27
+ listProjects,
28
+ readProjectSummary,
29
+ searchElement,
30
+ readElement,
31
+ readView
32
+ } = LikeC4MCPTools;
33
+ const mcp = new McpServer({
34
+ name: "LikeC4",
35
+ version: packageJson.version
36
+ }, {
37
+ instructions,
38
+ ...options,
39
+ capabilities: {
40
+ tools: {},
41
+ ...options?.capabilities
42
+ }
43
+ });
44
+ const tools = this.services.mcp.Tools;
45
+ mcp.tool(
46
+ listProjects.name,
47
+ listProjects.description,
48
+ async () => {
49
+ return toolResponse(await tools.listProjects());
50
+ }
51
+ );
52
+ mcp.tool(
53
+ readProjectSummary.name,
54
+ readProjectSummary.description,
55
+ readProjectSummary.paramsSchema,
56
+ async (params) => {
57
+ return toolResponse(await tools.readProjectSummary(params.project));
58
+ }
59
+ );
60
+ mcp.tool(
61
+ searchElement.name,
62
+ searchElement.description,
63
+ searchElement.paramsSchema,
64
+ async (params) => {
65
+ return toolResponse(await tools.searchElement(params));
66
+ }
67
+ );
68
+ mcp.tool(
69
+ readElement.name,
70
+ readElement.description,
71
+ readElement.paramsSchema,
72
+ async (params) => {
73
+ return toolResponse(await tools.readElement(params));
74
+ }
75
+ );
76
+ mcp.tool(
77
+ readView.name,
78
+ readView.description,
79
+ readView.paramsSchema,
80
+ async (params) => {
81
+ return toolResponse(await tools.readView(params));
82
+ }
83
+ );
84
+ return mcp;
85
+ }
86
+ }
@@ -0,0 +1,96 @@
1
+ import { type ProjectId } from '@likec4/core';
2
+ import { z } from 'zod';
3
+ import type { LikeC4Services } from '../module';
4
+ export declare namespace LikeC4MCPTools {
5
+ const instructions = "This server provides access to LikeC4 model.\n\nKey capabilities:\n- List all available LikeC4 projects in the workspace\n- Search for LikeC4 project and return its summary, that includes specifications, all elements and views\n- Search for LikeC4 element by title\n- Read details about LikeC4 element by id\n- Read details about LikeC4 view by id\n\n";
6
+ const listProjects: {
7
+ name: string;
8
+ description: string;
9
+ };
10
+ const readProjectSummary: {
11
+ name: string;
12
+ description: string;
13
+ paramsSchema: {
14
+ project: z.ZodOptional<z.ZodString>;
15
+ };
16
+ };
17
+ const searchElement: {
18
+ name: string;
19
+ description: string;
20
+ paramsSchema: {
21
+ search: z.ZodString;
22
+ };
23
+ };
24
+ const readElement: {
25
+ name: string;
26
+ description: string;
27
+ paramsSchema: {
28
+ id: z.ZodString;
29
+ project: z.ZodOptional<z.ZodString>;
30
+ };
31
+ };
32
+ const readView: {
33
+ name: string;
34
+ description: string;
35
+ paramsSchema: {
36
+ id: z.ZodString;
37
+ project: z.ZodOptional<z.ZodString>;
38
+ };
39
+ };
40
+ }
41
+ export interface LikeC4MCPTools {
42
+ listProjects(): Promise<string>;
43
+ /**
44
+ * Searches for LikeC4 project and returns its summary, specifications, elements and views
45
+ *
46
+ * @param project Project name (optional, will use default project if not specified)
47
+ */
48
+ readProjectSummary(project?: string): Promise<string>;
49
+ /**
50
+ * Searches for LikeC4 elements that have the search string in their names
51
+ * Can be used to resolve projects for further requests (like read-element or read-project-summary)
52
+ *
53
+ * @param params.search non-empty string
54
+ */
55
+ searchElement(params: {
56
+ search: string;
57
+ }): Promise<string>;
58
+ /**
59
+ * Read details about LikeC4 element.
60
+ *
61
+ * @param params.id Element id (FQN)
62
+ * @param params.project Project name (optional, will use default project if not specified)
63
+ */
64
+ readElement(params: {
65
+ id: string;
66
+ project?: string | undefined;
67
+ }): Promise<string>;
68
+ /**
69
+ * Read details about LikeC4 view.
70
+ *
71
+ * @param params.id View id (FQN)
72
+ * @param params.project Project name (optional, will use default project if not specified)
73
+ */
74
+ readView(params: {
75
+ id: string;
76
+ project?: string | undefined;
77
+ }): Promise<string>;
78
+ }
79
+ export declare class DefaultLikeC4MCPTools implements LikeC4MCPTools {
80
+ private services;
81
+ private readonly languageServices;
82
+ constructor(services: LikeC4Services);
83
+ listProjects(): Promise<string>;
84
+ readProjectSummary(_project?: ProjectId): Promise<string>;
85
+ searchElement(params: {
86
+ search: string;
87
+ }): Promise<string>;
88
+ readElement(params: {
89
+ id: string;
90
+ project?: string;
91
+ }): Promise<string>;
92
+ readView(params: {
93
+ id: string;
94
+ project?: string;
95
+ }): Promise<string>;
96
+ }
@@ -0,0 +1,288 @@
1
+ import { loggable } from "@likec4/log";
2
+ import { flatMap } from "remeda";
3
+ import stripIndent from "strip-indent";
4
+ import { z } from "zod";
5
+ import { logger as mainLogger } from "../logger.js";
6
+ import { toSingleLine } from "../model/parser/Base.js";
7
+ import { ProjectsManager } from "../workspace/ProjectsManager.js";
8
+ import { elementResource, modelViewResource } from "./utils.js";
9
+ const logger = mainLogger.getChild("LikeC4MCPServices");
10
+ function singleLine(str) {
11
+ const res = toSingleLine(str)?.replaceAll('"', "'");
12
+ return res ? `"${res}"` : "null";
13
+ }
14
+ function outputEach(iterator, ifEmpty, output) {
15
+ const items = [...iterator];
16
+ if (items.length === 0) {
17
+ return [ifEmpty];
18
+ }
19
+ return flatMap(items, output);
20
+ }
21
+ export var LikeC4MCPTools;
22
+ ((LikeC4MCPTools2) => {
23
+ LikeC4MCPTools2.instructions = `This server provides access to LikeC4 model.
24
+
25
+ Key capabilities:
26
+ - List all available LikeC4 projects in the workspace
27
+ - Search for LikeC4 project and return its summary, that includes specifications, all elements and views
28
+ - Search for LikeC4 element by title
29
+ - Read details about LikeC4 element by id
30
+ - Read details about LikeC4 view by id
31
+
32
+ `;
33
+ LikeC4MCPTools2.listProjects = {
34
+ name: "list-projects",
35
+ description: "Lists all available LikeC4 projects in the workspace"
36
+ };
37
+ LikeC4MCPTools2.readProjectSummary = {
38
+ name: "read-project-summary",
39
+ description: stripIndent(`
40
+ Searches for LikeC4 project and returns its summary, specifications, elements and views
41
+
42
+ Args:
43
+ project: Project name
44
+ `),
45
+ paramsSchema: {
46
+ project: z.string().optional()
47
+ }
48
+ };
49
+ LikeC4MCPTools2.searchElement = {
50
+ name: "search-element",
51
+ description: stripIndent(`
52
+ Search for LikeC4 elements that have the search string in their names
53
+ Can be used to resolve projects for further requests (like read-element or read-project-summary)
54
+
55
+ Args:
56
+ search: non-empty string
57
+ `),
58
+ paramsSchema: {
59
+ search: z.string()
60
+ }
61
+ };
62
+ LikeC4MCPTools2.readElement = {
63
+ name: "read-element",
64
+ description: stripIndent(`
65
+ Read details about a LikeC4 element
66
+
67
+ Args:
68
+ id: Element id (FQN)
69
+ project: Project name (optional, will use default project if not specified)
70
+ `),
71
+ paramsSchema: {
72
+ id: z.string().min(1),
73
+ project: z.string().optional()
74
+ }
75
+ };
76
+ LikeC4MCPTools2.readView = {
77
+ name: "read-view",
78
+ description: stripIndent(`
79
+ Read details about a LikeC4 view
80
+
81
+ Args:
82
+ id: View id
83
+ project: Project name (optional, will use default project if not specified)
84
+ `),
85
+ paramsSchema: {
86
+ id: z.string().min(1),
87
+ project: z.string().optional()
88
+ }
89
+ };
90
+ })(LikeC4MCPTools || (LikeC4MCPTools = {}));
91
+ export class DefaultLikeC4MCPTools {
92
+ constructor(services) {
93
+ this.services = services;
94
+ this.languageServices = services.likec4.LanguageServices;
95
+ }
96
+ languageServices;
97
+ async listProjects() {
98
+ const projects = await this.languageServices.projects();
99
+ const response = [];
100
+ for (const project of projects) {
101
+ if (!project.documents) {
102
+ continue;
103
+ }
104
+ response.push(
105
+ `<likec4project>`,
106
+ `id: "${project.id}"`,
107
+ `folder: ${project.folder.toString()}`,
108
+ "sources:",
109
+ ...project.documents.map((d) => `- ${d.toString()}`),
110
+ ""
111
+ );
112
+ try {
113
+ const model = await this.languageServices.computedModel(project.id);
114
+ const elements = [...model.elements()];
115
+ if (elements.length > 0) {
116
+ response.push(
117
+ "elements:",
118
+ ...elements.flatMap((el) => [
119
+ `- id: ${el.id}`,
120
+ ` kind: ${el.kind}`,
121
+ ` title: ${singleLine(el.title)}`,
122
+ ""
123
+ ]),
124
+ ""
125
+ );
126
+ }
127
+ const views = [...model.views()];
128
+ if (views.length > 0) {
129
+ response.push(
130
+ "views:",
131
+ ...views.flatMap((v) => [
132
+ `- id: ${v.id}`,
133
+ ` title: ${singleLine(v.title)}`,
134
+ ""
135
+ ]),
136
+ ""
137
+ );
138
+ }
139
+ } catch (error) {
140
+ logger.error(loggable(error));
141
+ }
142
+ response.push(`</likec4project>`);
143
+ }
144
+ if (response.length === 0) {
145
+ response.push(
146
+ `<likec4project>`,
147
+ `id: "default"`,
148
+ `folder: ${this.languageServices.workspaceUri.toString()}`,
149
+ `</likec4project>`
150
+ );
151
+ }
152
+ return response.join("\n");
153
+ }
154
+ async readProjectSummary(_project) {
155
+ const projectId = _project ?? ProjectsManager.DefaultProjectId;
156
+ const project = this.languageServices.projects().find((p) => p.id === projectId);
157
+ if (!project) {
158
+ return "Project not found";
159
+ }
160
+ const model = await this.languageServices.computedModel(project.id);
161
+ const response = [
162
+ `project: "${project.id}"`,
163
+ `folder: ${project.folder.toString()}`
164
+ ];
165
+ if (project.documents) {
166
+ response.push(
167
+ "sources:",
168
+ ...project.documents.map((d) => `- ${d.toString()}`),
169
+ ""
170
+ );
171
+ }
172
+ response.push("<specifications>");
173
+ const elementKinds = Object.keys(model.$model.specification.elements);
174
+ if (elementKinds.length > 0) {
175
+ response.push(
176
+ "element kinds:",
177
+ ...elementKinds.map((kind) => `- ${kind}`),
178
+ ""
179
+ );
180
+ }
181
+ const relationshipKinds = Object.keys(model.$model.specification.relationships);
182
+ if (relationshipKinds.length > 0) {
183
+ response.push(
184
+ "relationship kinds:",
185
+ ...relationshipKinds.map((kind) => `- ${kind}`),
186
+ ""
187
+ );
188
+ }
189
+ const deploymentKinds = Object.keys(model.$model.specification.deployments);
190
+ if (deploymentKinds.length > 0) {
191
+ response.push(
192
+ "deployment node kinds:",
193
+ ...deploymentKinds.map((kind) => `- ${kind}`),
194
+ ""
195
+ );
196
+ }
197
+ if (model.allTags().length > 0) {
198
+ response.push(
199
+ "tags:",
200
+ ...model.allTags().map((t) => `- ${t}`),
201
+ ""
202
+ );
203
+ }
204
+ response.push(
205
+ "</specifications>",
206
+ ""
207
+ );
208
+ response.push(
209
+ "<elements>",
210
+ ...outputEach(model.elements(), "No elements", (el) => [
211
+ `- id: ${el.id}`,
212
+ ...el.parent ? [
213
+ ` parentId: ${el.parent.id}`
214
+ ] : [],
215
+ ` kind: ${el.kind}`,
216
+ ` shape: ${el.shape}`,
217
+ ` title: ${singleLine(el.title)}`,
218
+ ` description: ${singleLine(el.description)}`,
219
+ ` technology: ${singleLine(el.technology)}`,
220
+ ` tags: ${JSON.stringify(el.tags)}`,
221
+ ""
222
+ ]),
223
+ "</elements>",
224
+ "<views>",
225
+ ...outputEach(model.views(), "No views", (v) => [
226
+ `- id: ${v.id}`,
227
+ ` viewType: ${v.__}`,
228
+ ` title: ${singleLine(v.title)}`,
229
+ ""
230
+ ]),
231
+ "</views>"
232
+ );
233
+ return response.join("\n");
234
+ }
235
+ async searchElement(params) {
236
+ const search = params.search.toLowerCase();
237
+ const found = [];
238
+ for (const project of this.languageServices.projects()) {
239
+ try {
240
+ const model = await this.languageServices.computedModel(project.id);
241
+ const elements = [...model.elements()].filter((el) => el.title.toLowerCase().includes(search));
242
+ if (elements.length > 0) {
243
+ found.push(
244
+ "<project>",
245
+ `project: "${project.id}"`,
246
+ "found:",
247
+ ...elements.flatMap((el) => [
248
+ `- id: ${el.id}`,
249
+ ` kind: ${el.kind}`,
250
+ ` title: ${singleLine(el.title)}`,
251
+ ""
252
+ ]),
253
+ "</project>"
254
+ );
255
+ }
256
+ } catch (error) {
257
+ logger.error(loggable(error));
258
+ }
259
+ }
260
+ return found.length > 0 ? found.join("\n") : "No elements with this name found";
261
+ }
262
+ async readElement(params) {
263
+ const projectId = params.project ?? ProjectsManager.DefaultProjectId;
264
+ const project = this.languageServices.projects().find((p) => p.id === projectId);
265
+ if (!project) {
266
+ return `Project "${projectId}" not found`;
267
+ }
268
+ const model = await this.languageServices.computedModel(project.id);
269
+ const element = model.findElement(params.id);
270
+ if (!element) {
271
+ return `Element "${params.id}" not found in project "${projectId}"`;
272
+ }
273
+ return JSON.stringify(elementResource(this.languageServices, element, project.id));
274
+ }
275
+ async readView(params) {
276
+ const projectId = params.project ?? ProjectsManager.DefaultProjectId;
277
+ const project = this.languageServices.projects().find((p) => p.id === projectId);
278
+ if (!project) {
279
+ return `Project "${projectId}" not found`;
280
+ }
281
+ const model = await this.languageServices.computedModel(project.id);
282
+ const view = model.findView(params.id);
283
+ if (!view) {
284
+ return `View "${params.id}" not found in project "${projectId}"`;
285
+ }
286
+ return JSON.stringify(modelViewResource(this.languageServices, view, project.id));
287
+ }
288
+ }
@@ -0,0 +1,13 @@
1
+ import type { AsyncDisposable } from 'langium';
2
+ import type { LikeC4Services } from '../../module';
3
+ import type { LikeC4MCPServer } from '../LikeC4MCPServerFactory';
4
+ export declare class SSELikeC4MCPServer implements LikeC4MCPServer, AsyncDisposable {
5
+ private services;
6
+ private readonly transports;
7
+ private server;
8
+ private port;
9
+ constructor(services: LikeC4Services);
10
+ dispose(): Promise<void>;
11
+ start(port?: number): Promise<void>;
12
+ stop(): Promise<void>;
13
+ }
@@ -0,0 +1,74 @@
1
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
2
+ import express from "express";
3
+ import { logger as mainLogger } from "../../logger.js";
4
+ const logger = mainLogger.getChild("LikeC4MCPServer");
5
+ export class SSELikeC4MCPServer {
6
+ constructor(services) {
7
+ this.services = services;
8
+ }
9
+ // Store transports by session ID to send notifications
10
+ transports = {};
11
+ server = void 0;
12
+ port = 33335;
13
+ async dispose() {
14
+ await this.stop();
15
+ }
16
+ async start(port = 33335) {
17
+ if (this.server) {
18
+ if (this.port === port) {
19
+ return;
20
+ }
21
+ await this.stop();
22
+ }
23
+ logger.info("Starting server on port {port}", { port });
24
+ this.port = port;
25
+ const mcp = this.services.mcp.ServerFactory.create();
26
+ const app = express();
27
+ app.get("/sse", async (_, res) => {
28
+ const transport = new SSEServerTransport("/messages", res);
29
+ this.transports[transport.sessionId] = transport;
30
+ logger.debug`SSE connection established, sessionId: ${transport.sessionId}`;
31
+ res.on("close", () => {
32
+ delete this.transports[transport.sessionId];
33
+ });
34
+ await mcp.connect(transport);
35
+ });
36
+ app.post("/messages", async (req, res) => {
37
+ const sessionId = req.query["sessionId"];
38
+ const transport = this.transports[sessionId];
39
+ if (transport) {
40
+ logger.debug`SSE message received, sessionId: ${sessionId}`;
41
+ await transport.handlePostMessage(req, res);
42
+ } else {
43
+ res.status(400).send("No transport found for sessionId");
44
+ }
45
+ });
46
+ return new Promise((resolve, reject) => {
47
+ this.server = app.listen(port, (err) => {
48
+ if (err) {
49
+ logger.error("Failed to start server", { err });
50
+ reject(err);
51
+ return;
52
+ }
53
+ logger.info("server listening on port {port}", { port });
54
+ resolve();
55
+ });
56
+ });
57
+ }
58
+ async stop() {
59
+ const server = this.server;
60
+ if (!server) {
61
+ return;
62
+ }
63
+ logger.info("Stopping server");
64
+ this.server = void 0;
65
+ return new Promise((resolve) => {
66
+ server.close((err) => {
67
+ if (err) {
68
+ logger.error("Failed to stop SSE server", { err });
69
+ }
70
+ resolve();
71
+ });
72
+ });
73
+ }
74
+ }
@@ -0,0 +1,7 @@
1
+ import type { LikeC4Services } from '../../module';
2
+ import type { LikeC4MCPServer } from '../LikeC4MCPServerFactory';
3
+ export declare const WithMCPServer: {
4
+ mcp: {
5
+ Server(services: LikeC4Services): LikeC4MCPServer;
6
+ };
7
+ };