@likec4/language-server 1.44.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LikeC4LanguageServices.d.ts +1 -15
- package/dist/LikeC4LanguageServices.js +2 -32
- package/dist/Rpc.js +32 -20
- package/dist/ast.js +6 -2
- package/dist/browser.js +2 -2
- package/dist/bundled.js +2 -0
- package/dist/bundled.mjs +3184 -3162
- package/dist/filesystem/ChokidarWatcher.d.ts +2 -0
- package/dist/filesystem/ChokidarWatcher.js +27 -16
- package/dist/filesystem/LikeC4FileSystem.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -3
- package/dist/mcp/server/StdioLikeC4MCPServer.js +10 -6
- package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -97
- package/dist/mcp/server/WithMCPServer.js +5 -5
- package/dist/mcp/tools/search-element.js +5 -5
- package/dist/mcp/utils.js +1 -1
- package/dist/model/deployments-index.js +2 -2
- package/dist/model/fqn-index.d.ts +1 -2
- package/dist/model/fqn-index.js +13 -16
- package/dist/model/model-builder.js +0 -2
- package/dist/model/model-parser.js +34 -27
- package/dist/model/parser/SpecificationParser.js +4 -0
- package/dist/model/parser/ViewsParser.js +3 -1
- package/dist/model-change/ModelChanges.d.ts +2 -2
- package/dist/model-change/ModelChanges.js +36 -9
- package/dist/protocol.d.ts +33 -10
- package/dist/protocol.js +13 -4
- package/dist/view-utils/manual-layout.js +2 -4
- package/dist/views/LikeC4ManualLayouts.d.ts +16 -2
- package/dist/views/LikeC4ManualLayouts.js +99 -22
- package/dist/views/LikeC4Views.d.ts +26 -5
- package/dist/views/LikeC4Views.js +49 -33
- package/dist/workspace/AstNodeDescriptionProvider.js +6 -3
- package/dist/workspace/IndexManager.js +1 -1
- package/dist/workspace/LangiumDocuments.d.ts +3 -2
- package/dist/workspace/LangiumDocuments.js +29 -15
- package/dist/workspace/ProjectsManager.d.ts +19 -15
- package/dist/workspace/ProjectsManager.js +137 -41
- package/dist/workspace/WorkspaceManager.js +5 -0
- package/package.json +16 -15
|
@@ -7,8 +7,10 @@ export declare const chokidarFileSystemWatcher: FileSystemWatcherModuleContext;
|
|
|
7
7
|
export declare class ChokidarFileSystemWatcher implements FileSystemWatcher {
|
|
8
8
|
protected services: LikeC4SharedServices;
|
|
9
9
|
private watcher?;
|
|
10
|
+
private queue;
|
|
10
11
|
constructor(services: LikeC4SharedServices);
|
|
11
12
|
watch(folder: string): void;
|
|
12
13
|
dispose(): Promise<void>;
|
|
13
14
|
private createWatcher;
|
|
15
|
+
private enqueueFileOp;
|
|
14
16
|
}
|
|
@@ -2,6 +2,7 @@ import { isLikeC4Config } from '@likec4/config/node';
|
|
|
2
2
|
import { loggable } from '@likec4/log';
|
|
3
3
|
import chokidar from 'chokidar';
|
|
4
4
|
import { URI } from 'langium';
|
|
5
|
+
import PQueue from 'p-queue';
|
|
5
6
|
import { logger as mainLogger } from '../logger';
|
|
6
7
|
import { isManualLayoutFile } from '../views/LikeC4ManualLayouts';
|
|
7
8
|
import { isLikeC4File } from './LikeC4FileSystem';
|
|
@@ -16,6 +17,7 @@ const isAnyLikeC4File = (path) => isLikeC4File(path) || isLikeC4Config(path) ||
|
|
|
16
17
|
export class ChokidarFileSystemWatcher {
|
|
17
18
|
services;
|
|
18
19
|
watcher;
|
|
20
|
+
queue = new PQueue({ concurrency: 1, timeout: 5000 });
|
|
19
21
|
constructor(services) {
|
|
20
22
|
this.services = services;
|
|
21
23
|
}
|
|
@@ -30,8 +32,9 @@ export class ChokidarFileSystemWatcher {
|
|
|
30
32
|
}
|
|
31
33
|
async dispose() {
|
|
32
34
|
if (this.watcher) {
|
|
33
|
-
|
|
35
|
+
const watcher = this.watcher;
|
|
34
36
|
this.watcher = undefined;
|
|
37
|
+
await watcher.close();
|
|
35
38
|
}
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
@@ -41,10 +44,11 @@ export class ChokidarFileSystemWatcher {
|
|
|
41
44
|
path => path.includes('node_modules') || path.includes('.git'),
|
|
42
45
|
(path, stats) => (!!stats && stats.isFile() && !isAnyLikeC4File(path)),
|
|
43
46
|
],
|
|
47
|
+
followSymlinks: true,
|
|
44
48
|
ignoreInitial: true,
|
|
45
49
|
});
|
|
46
|
-
const onAddOrChange =
|
|
47
|
-
|
|
50
|
+
const onAddOrChange = (path) => {
|
|
51
|
+
this.enqueueFileOp('addOrChange: ' + path, async () => {
|
|
48
52
|
if (isLikeC4Config(path)) {
|
|
49
53
|
logger.debug `project file changed: ${path}`;
|
|
50
54
|
await this.services.workspace.ProjectsManager.reloadProjects();
|
|
@@ -61,16 +65,14 @@ export class ChokidarFileSystemWatcher {
|
|
|
61
65
|
else {
|
|
62
66
|
logger.warn `Unknown file change: ${path}`;
|
|
63
67
|
}
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
logger.error(loggable(error));
|
|
67
|
-
}
|
|
68
|
+
});
|
|
68
69
|
};
|
|
69
|
-
const onRemove =
|
|
70
|
-
|
|
70
|
+
const onRemove = (path) => {
|
|
71
|
+
this.enqueueFileOp('remove: ' + path, async () => {
|
|
72
|
+
const pm = this.services.workspace.ProjectsManager;
|
|
71
73
|
if (isLikeC4Config(path)) {
|
|
72
74
|
logger.debug `project file removed: ${path}`;
|
|
73
|
-
await
|
|
75
|
+
await pm.reloadProjects();
|
|
74
76
|
}
|
|
75
77
|
else if (isLikeC4File(path)) {
|
|
76
78
|
logger.debug `file removed: ${path}`;
|
|
@@ -78,20 +80,29 @@ export class ChokidarFileSystemWatcher {
|
|
|
78
80
|
}
|
|
79
81
|
else if (isManualLayoutFile(path)) {
|
|
80
82
|
logger.debug `manual layout file removed: ${path}`;
|
|
81
|
-
|
|
82
|
-
await
|
|
83
|
+
const project = pm.belongsTo(path);
|
|
84
|
+
await pm.rebuidProject(project);
|
|
83
85
|
}
|
|
84
86
|
else {
|
|
85
87
|
logger.warn `Unknown file removal: ${path}`;
|
|
86
88
|
}
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
logger.error(loggable(error));
|
|
90
|
-
}
|
|
89
|
+
});
|
|
91
90
|
};
|
|
92
91
|
watcher.on('add', onAddOrChange)
|
|
93
92
|
.on('change', onAddOrChange)
|
|
94
93
|
.on('unlink', onRemove);
|
|
95
94
|
return watcher;
|
|
96
95
|
}
|
|
96
|
+
enqueueFileOp(fileop, fn) {
|
|
97
|
+
this.queue.add(async () => {
|
|
98
|
+
try {
|
|
99
|
+
await fn();
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
logger.warn(`Failed on ${fileop}`, { error });
|
|
103
|
+
}
|
|
104
|
+
}).catch(error => {
|
|
105
|
+
logger.error(loggable(error));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
97
108
|
}
|
|
@@ -4,7 +4,7 @@ import { URI } from 'langium';
|
|
|
4
4
|
import { NodeFileSystemProvider } from 'langium/node';
|
|
5
5
|
import { mkdirSync } from 'node:fs';
|
|
6
6
|
import { stat, unlink, writeFile } from 'node:fs/promises';
|
|
7
|
-
import { dirname } from 'node:path';
|
|
7
|
+
import { basename, dirname } from 'node:path';
|
|
8
8
|
import { LikeC4LanguageMetaData } from '../generated/module';
|
|
9
9
|
import { Content, isLikeC4Builtin } from '../likec4lib';
|
|
10
10
|
import { logger as rootLogger } from '../logger';
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,9 @@ type StartLanguageServerOptions = {
|
|
|
23
23
|
* Whether to enable the MCP server.
|
|
24
24
|
* @default 'sse'
|
|
25
25
|
*/
|
|
26
|
-
enableMCP?: false | '
|
|
26
|
+
enableMCP?: false | 'stdio' | 'sse' | {
|
|
27
|
+
port: number;
|
|
28
|
+
};
|
|
27
29
|
/**
|
|
28
30
|
* Whether to enable manual layouts, stored in json5 files.
|
|
29
31
|
* @default true
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { configureLogger, getConsoleSink, getTextFormatter } from '@likec4/log';
|
|
1
|
+
import { configureLogger, getConsoleSink, getConsoleStderrSink, getTextFormatter } from '@likec4/log';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
|
+
import { DEV } from 'esm-env';
|
|
3
4
|
import { startLanguageServer as startLanguim } from 'langium/lsp';
|
|
4
5
|
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
|
|
5
6
|
import { NoopFileSystem } from './filesystem';
|
|
@@ -23,7 +24,7 @@ export function startLanguageServer(options) {
|
|
|
23
24
|
const connection = createConnection(ProposedFeatures.all);
|
|
24
25
|
configureLogger({
|
|
25
26
|
sinks: {
|
|
26
|
-
console: getConsoleSink({
|
|
27
|
+
console: opts.enableMCP === 'stdio' ? getConsoleStderrSink() : getConsoleSink({
|
|
27
28
|
formatter: getTextFormatter(),
|
|
28
29
|
}),
|
|
29
30
|
telemetry: getTelemetrySink(connection),
|
|
@@ -32,6 +33,7 @@ export function startLanguageServer(options) {
|
|
|
32
33
|
{
|
|
33
34
|
category: ['likec4'],
|
|
34
35
|
sinks: ['console', 'telemetry'],
|
|
36
|
+
lowestLevel: DEV ? 'trace' : 'debug',
|
|
35
37
|
},
|
|
36
38
|
],
|
|
37
39
|
});
|
|
@@ -40,7 +42,7 @@ export function startLanguageServer(options) {
|
|
|
40
42
|
const services = createLanguageServices({
|
|
41
43
|
connection,
|
|
42
44
|
...LikeC4FileSystem(opts.enableWatcher),
|
|
43
|
-
|
|
45
|
+
...!!opts.enableMCP && WithMCPServer(opts.enableMCP),
|
|
44
46
|
...opts.enableManualLayouts && WithLikeC4ManualLayouts,
|
|
45
47
|
}, {
|
|
46
48
|
likec4: {
|
|
@@ -36,12 +36,16 @@ export class StdioLikeC4MCPServer {
|
|
|
36
36
|
if (!this.transport) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
try {
|
|
40
|
+
logger.info('Stopping MCP stdio server');
|
|
41
|
+
await this.transport.close();
|
|
42
|
+
if (this._mcp) {
|
|
43
|
+
await this._mcp.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
this._mcp = undefined;
|
|
48
|
+
this.transport = undefined;
|
|
43
49
|
}
|
|
44
|
-
this._mcp = undefined;
|
|
45
|
-
this.transport = undefined;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -1,10 +1,91 @@
|
|
|
1
1
|
import { serve } from '@hono/node-server';
|
|
2
|
-
import { promiseNextTick } from '@likec4/core/utils';
|
|
3
2
|
import { loggable } from '@likec4/log';
|
|
4
3
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
4
|
import { toFetchResponse, toReqRes } from 'fetch-to-node';
|
|
6
5
|
import { Hono } from 'hono';
|
|
7
6
|
import { logger } from '../utils';
|
|
7
|
+
function createHonoApp(factory) {
|
|
8
|
+
return new Hono()
|
|
9
|
+
.post('/mcp', async (c) => {
|
|
10
|
+
const { req, res } = toReqRes(c.req.raw);
|
|
11
|
+
const mcp = factory.create();
|
|
12
|
+
try {
|
|
13
|
+
const transport = new StreamableHTTPServerTransport({
|
|
14
|
+
sessionIdGenerator: undefined,
|
|
15
|
+
});
|
|
16
|
+
// Added for extra debuggability
|
|
17
|
+
transport.onerror = (err) => {
|
|
18
|
+
logger.error(loggable(err));
|
|
19
|
+
};
|
|
20
|
+
await mcp.connect(transport);
|
|
21
|
+
await transport.handleRequest(req, res, await c.req.json());
|
|
22
|
+
res.on('close', async () => {
|
|
23
|
+
await transport.close();
|
|
24
|
+
await mcp.close();
|
|
25
|
+
});
|
|
26
|
+
return toFetchResponse(res);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
logger.error(loggable(e));
|
|
30
|
+
return c.json({
|
|
31
|
+
jsonrpc: '2.0',
|
|
32
|
+
error: {
|
|
33
|
+
code: -32603,
|
|
34
|
+
message: 'Internal server error',
|
|
35
|
+
},
|
|
36
|
+
id: null,
|
|
37
|
+
}, { status: 500 });
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
.all('/mcp', async (c) => {
|
|
41
|
+
return c.json({
|
|
42
|
+
jsonrpc: '2.0',
|
|
43
|
+
error: {
|
|
44
|
+
code: -32000,
|
|
45
|
+
message: 'Method not allowed.',
|
|
46
|
+
},
|
|
47
|
+
id: null,
|
|
48
|
+
}, { status: 405 });
|
|
49
|
+
})
|
|
50
|
+
.notFound((c) => {
|
|
51
|
+
logger.debug(`${c.req.method} ${c.req.url} not found`);
|
|
52
|
+
return c.json({
|
|
53
|
+
jsonrpc: '2.0',
|
|
54
|
+
error: {
|
|
55
|
+
code: -32000,
|
|
56
|
+
message: 'Method not found.',
|
|
57
|
+
},
|
|
58
|
+
id: null,
|
|
59
|
+
}, { status: 404 });
|
|
60
|
+
})
|
|
61
|
+
.onError((e, c) => {
|
|
62
|
+
logger.error(loggable(e));
|
|
63
|
+
return c.json({
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
error: {
|
|
66
|
+
code: -32603,
|
|
67
|
+
message: 'Internal server error',
|
|
68
|
+
},
|
|
69
|
+
id: null,
|
|
70
|
+
}, { status: 500 });
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function startServer(params) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const { factory, port } = params;
|
|
76
|
+
const app = createHonoApp(factory);
|
|
77
|
+
const server = serve({
|
|
78
|
+
fetch: app.fetch,
|
|
79
|
+
hostname: '0.0.0.0',
|
|
80
|
+
port,
|
|
81
|
+
})
|
|
82
|
+
.prependOnceListener('error', reject)
|
|
83
|
+
.prependOnceListener('listening', () => {
|
|
84
|
+
server.removeListener('error', reject);
|
|
85
|
+
resolve(server.unref());
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
8
89
|
export class StreamableLikeC4MCPServer {
|
|
9
90
|
services;
|
|
10
91
|
_port;
|
|
@@ -27,107 +108,24 @@ export class StreamableLikeC4MCPServer {
|
|
|
27
108
|
await this.stop();
|
|
28
109
|
}
|
|
29
110
|
async start(port = this._port) {
|
|
30
|
-
|
|
31
|
-
if (this.
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
await this.stop();
|
|
111
|
+
if (this.server) {
|
|
112
|
+
if (this.port === port) {
|
|
113
|
+
return;
|
|
36
114
|
}
|
|
37
|
-
|
|
38
|
-
this._port = port;
|
|
39
|
-
await promiseNextTick();
|
|
40
|
-
const app = new Hono()
|
|
41
|
-
.post('/mcp', async (c) => {
|
|
42
|
-
const { req, res } = toReqRes(c.req.raw);
|
|
43
|
-
const server = this.services.mcp.ServerFactory.create();
|
|
44
|
-
try {
|
|
45
|
-
const transport = new StreamableHTTPServerTransport({
|
|
46
|
-
sessionIdGenerator: undefined,
|
|
47
|
-
});
|
|
48
|
-
// Added for extra debuggability
|
|
49
|
-
transport.onerror = (err) => {
|
|
50
|
-
logger.error(loggable(err));
|
|
51
|
-
};
|
|
52
|
-
await server.connect(transport);
|
|
53
|
-
await transport.handleRequest(req, res, await c.req.json());
|
|
54
|
-
res.on('close', async () => {
|
|
55
|
-
logger.debug('Request closed');
|
|
56
|
-
await transport.close();
|
|
57
|
-
await server.close();
|
|
58
|
-
});
|
|
59
|
-
return toFetchResponse(res);
|
|
60
|
-
}
|
|
61
|
-
catch (e) {
|
|
62
|
-
logger.error(loggable(e));
|
|
63
|
-
return c.json({
|
|
64
|
-
jsonrpc: '2.0',
|
|
65
|
-
error: {
|
|
66
|
-
code: -32603,
|
|
67
|
-
message: 'Internal server error',
|
|
68
|
-
},
|
|
69
|
-
id: null,
|
|
70
|
-
}, { status: 500 });
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
.get('/mcp', async (c) => {
|
|
74
|
-
logger.debug('Received GET MCP request');
|
|
75
|
-
return c.json({
|
|
76
|
-
jsonrpc: '2.0',
|
|
77
|
-
error: {
|
|
78
|
-
code: -32000,
|
|
79
|
-
message: 'Method not allowed.',
|
|
80
|
-
},
|
|
81
|
-
id: null,
|
|
82
|
-
}, { status: 405 });
|
|
83
|
-
})
|
|
84
|
-
.delete('/mcp', async (c) => {
|
|
85
|
-
logger.debug('Received DELETE MCP request');
|
|
86
|
-
return c.json({
|
|
87
|
-
jsonrpc: '2.0',
|
|
88
|
-
error: {
|
|
89
|
-
code: -32000,
|
|
90
|
-
message: 'Method not allowed.',
|
|
91
|
-
},
|
|
92
|
-
id: null,
|
|
93
|
-
}, { status: 405 });
|
|
94
|
-
})
|
|
95
|
-
.notFound((c) => {
|
|
96
|
-
logger.debug(`${c.req.method} ${c.req.url} not found`);
|
|
97
|
-
return c.json({
|
|
98
|
-
jsonrpc: '2.0',
|
|
99
|
-
error: {
|
|
100
|
-
code: -32000,
|
|
101
|
-
message: 'Method not found.',
|
|
102
|
-
},
|
|
103
|
-
id: null,
|
|
104
|
-
}, { status: 404 });
|
|
105
|
-
})
|
|
106
|
-
.onError((e, c) => {
|
|
107
|
-
logger.error(loggable(e));
|
|
108
|
-
return c.json({
|
|
109
|
-
jsonrpc: '2.0',
|
|
110
|
-
error: {
|
|
111
|
-
code: -32603,
|
|
112
|
-
message: 'Internal server error',
|
|
113
|
-
},
|
|
114
|
-
id: null,
|
|
115
|
-
}, { status: 500 });
|
|
116
|
-
});
|
|
117
|
-
this.server = serve({
|
|
118
|
-
fetch: app.fetch,
|
|
119
|
-
hostname: '0.0.0.0',
|
|
120
|
-
port: this._port,
|
|
121
|
-
}, (info) => logger.info('MCP server ready at http://0.0.0.0:{port}/mcp', { port: info.port }));
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
logger.warn('Failed to start MCP server', { err });
|
|
125
|
-
return Promise.reject(err);
|
|
115
|
+
await this.stop();
|
|
126
116
|
}
|
|
117
|
+
logger.info('Starting MCP server on port {port}', { port });
|
|
118
|
+
this._port = port;
|
|
119
|
+
this.server = await startServer({
|
|
120
|
+
factory: this.services.mcp.ServerFactory,
|
|
121
|
+
port,
|
|
122
|
+
});
|
|
123
|
+
logger.info('MCP server ready at http://0.0.0.0:{port}/mcp', { port });
|
|
127
124
|
}
|
|
128
125
|
stop() {
|
|
129
126
|
const server = this.server;
|
|
130
127
|
if (!server) {
|
|
128
|
+
logger.info('MCP server is not running, nothing to stop');
|
|
131
129
|
return Promise.resolve();
|
|
132
130
|
}
|
|
133
131
|
logger.info('Stopping MCP server');
|
|
@@ -137,7 +135,9 @@ export class StreamableLikeC4MCPServer {
|
|
|
137
135
|
if (err) {
|
|
138
136
|
logger.error('Failed to stop MCP server', { err });
|
|
139
137
|
}
|
|
140
|
-
|
|
138
|
+
else {
|
|
139
|
+
logger.info('MCP server stopped');
|
|
140
|
+
}
|
|
141
141
|
resolve();
|
|
142
142
|
});
|
|
143
143
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loggable } from '@likec4/log';
|
|
2
|
+
import { error } from 'console';
|
|
2
3
|
import { isError } from 'remeda';
|
|
3
4
|
import { logger } from '../utils';
|
|
4
5
|
import { StdioLikeC4MCPServer } from './StdioLikeC4MCPServer';
|
|
@@ -13,7 +14,6 @@ const streamableLikeC4MCPServer = (services, defaultPort = 33335) => {
|
|
|
13
14
|
logger.warn('Unexpected configuration update: {update}', { update });
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
|
-
logger.debug('Configuration update: {update}', { update });
|
|
17
17
|
const { enabled = false, port = defaultPort, } = update.configuration.mcp;
|
|
18
18
|
if (!enabled) {
|
|
19
19
|
void server.stop();
|
|
@@ -28,15 +28,15 @@ const streamableLikeC4MCPServer = (services, defaultPort = 33335) => {
|
|
|
28
28
|
});
|
|
29
29
|
})
|
|
30
30
|
.catch(err => {
|
|
31
|
-
const message =
|
|
31
|
+
const message = loggable(err);
|
|
32
32
|
connection?.telemetry?.logEvent({
|
|
33
33
|
eventName: 'mcp-server-start-failed',
|
|
34
34
|
mcpPort: port,
|
|
35
|
-
|
|
35
|
+
message,
|
|
36
36
|
});
|
|
37
|
-
logger.
|
|
37
|
+
logger.warn(`Failed to start LikeC4 MCP Server: \n${message}`);
|
|
38
38
|
if (connection) {
|
|
39
|
-
connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server\n\n${
|
|
39
|
+
connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server\n\n${message}`);
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
});
|
|
@@ -41,11 +41,11 @@ export const searchElement = likec4Tool({
|
|
|
41
41
|
Search LikeC4 elements and deployment nodes across all projects.
|
|
42
42
|
|
|
43
43
|
Query syntax (case-insensitive):
|
|
44
|
-
- kind:<value
|
|
45
|
-
- shape:<value
|
|
46
|
-
- meta:<key
|
|
47
|
-
- #<value
|
|
48
|
-
-
|
|
44
|
+
- kind:<value> filters by kind
|
|
45
|
+
- shape:<value> filters by shape
|
|
46
|
+
- meta:<key> filters by having metadata with the given key
|
|
47
|
+
- #<value> matches assigned tags
|
|
48
|
+
- <value> matches id (FQN) or title
|
|
49
49
|
|
|
50
50
|
Request:
|
|
51
51
|
- search: string — at least 2 characters
|
package/dist/mcp/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ export class DeploymentsIndex extends FqnIndex {
|
|
|
9
9
|
services;
|
|
10
10
|
Names;
|
|
11
11
|
constructor(services) {
|
|
12
|
-
super(services
|
|
12
|
+
super(services);
|
|
13
13
|
this.services = services;
|
|
14
14
|
this.Names = services.references.NameProvider;
|
|
15
15
|
}
|
|
@@ -18,7 +18,7 @@ export class DeploymentsIndex extends FqnIndex {
|
|
|
18
18
|
if (rootNodes.length === 0) {
|
|
19
19
|
return DocumentFqnIndex.EMPTY;
|
|
20
20
|
}
|
|
21
|
-
const projectId =
|
|
21
|
+
const projectId = this.projects.belongsTo(document);
|
|
22
22
|
const root = new Array();
|
|
23
23
|
const children = new MultiMap();
|
|
24
24
|
const descendants = new MultiMap();
|
|
@@ -7,12 +7,11 @@ import { ADisposable } from '../utils';
|
|
|
7
7
|
import { type LangiumDocuments, ProjectsManager } from '../workspace';
|
|
8
8
|
export declare class FqnIndex<AstNd = ast.Element> extends ADisposable {
|
|
9
9
|
protected services: LikeC4Services;
|
|
10
|
-
private cachePrefix;
|
|
11
10
|
protected projects: ProjectsManager;
|
|
12
11
|
protected langiumDocuments: LangiumDocuments;
|
|
13
12
|
protected documentCache: DefaultWeakMap<LikeC4LangiumDocument, DocumentFqnIndex>;
|
|
14
13
|
protected workspaceCache: WorkspaceCache<string, AstNodeDescriptionWithFqn[]>;
|
|
15
|
-
constructor(services: LikeC4Services
|
|
14
|
+
constructor(services: LikeC4Services);
|
|
16
15
|
private documents;
|
|
17
16
|
get(document: LikeC4LangiumDocument): DocumentFqnIndex;
|
|
18
17
|
resolve(reference: ast.Referenceable): Fqn;
|
package/dist/model/fqn-index.js
CHANGED
|
@@ -10,22 +10,20 @@ import { readStrictFqn } from '../utils/elementRef';
|
|
|
10
10
|
import { ProjectsManager } from '../workspace';
|
|
11
11
|
export class FqnIndex extends ADisposable {
|
|
12
12
|
services;
|
|
13
|
-
cachePrefix;
|
|
14
13
|
projects;
|
|
15
14
|
langiumDocuments;
|
|
16
15
|
documentCache;
|
|
17
16
|
workspaceCache;
|
|
18
|
-
constructor(services
|
|
17
|
+
constructor(services) {
|
|
19
18
|
super();
|
|
20
19
|
this.services = services;
|
|
21
|
-
this.cachePrefix = cachePrefix;
|
|
22
20
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
23
21
|
this.projects = services.shared.workspace.ProjectsManager;
|
|
24
22
|
this.documentCache = new DefaultWeakMap(doc => this.createDocumentIndex(doc));
|
|
25
23
|
this.workspaceCache = new WorkspaceCache(services.shared, DocumentState.IndexedContent);
|
|
26
24
|
this.onDispose(services.shared.workspace.DocumentBuilder.onDocumentPhase(DocumentState.IndexedContent, (doc) => {
|
|
27
25
|
if (isLikeC4LangiumDocument(doc)) {
|
|
28
|
-
this.documentCache.
|
|
26
|
+
this.documentCache.delete(doc);
|
|
29
27
|
}
|
|
30
28
|
}));
|
|
31
29
|
}
|
|
@@ -57,23 +55,23 @@ export class FqnIndex extends ADisposable {
|
|
|
57
55
|
// Document index is not yet created
|
|
58
56
|
const doc = AstUtils.getDocument(el);
|
|
59
57
|
invariant(isLikeC4LangiumDocument(doc));
|
|
60
|
-
|
|
61
|
-
// This will create the document index
|
|
58
|
+
// Ensure the document is indexed
|
|
62
59
|
this.get(doc);
|
|
60
|
+
// This will create the document index
|
|
63
61
|
return nonNullable(ElementOps.readId(el), 'Element fqn must be set, invalid state');
|
|
64
62
|
}
|
|
65
63
|
byFqn(projectId, fqn) {
|
|
66
|
-
return stream(this.workspaceCache.get(`${
|
|
64
|
+
return stream(this.workspaceCache.get(`${projectId}:fqn:${fqn}`, () => {
|
|
67
65
|
return this
|
|
68
66
|
.documents(projectId)
|
|
69
|
-
.toArray()
|
|
70
67
|
.flatMap(doc => {
|
|
71
68
|
return this.get(doc).byFqn(fqn);
|
|
72
|
-
})
|
|
69
|
+
})
|
|
70
|
+
.toArray();
|
|
73
71
|
}));
|
|
74
72
|
}
|
|
75
73
|
rootElements(projectId) {
|
|
76
|
-
return stream(this.workspaceCache.get(`${
|
|
74
|
+
return stream(this.workspaceCache.get(`${projectId}:rootElements`, () => {
|
|
77
75
|
const allchildren = this.documents(projectId)
|
|
78
76
|
.reduce((map, doc) => {
|
|
79
77
|
this.get(doc).rootElements().forEach(desc => {
|
|
@@ -85,7 +83,7 @@ export class FqnIndex extends ADisposable {
|
|
|
85
83
|
}));
|
|
86
84
|
}
|
|
87
85
|
directChildrenOf(projectId, parent) {
|
|
88
|
-
return stream(this.workspaceCache.get(`${
|
|
86
|
+
return stream(this.workspaceCache.get(`${projectId}:directChildrenOf:${parent}`, () => {
|
|
89
87
|
const allchildren = this.documents(projectId)
|
|
90
88
|
.reduce((map, doc) => {
|
|
91
89
|
this.get(doc).children(parent).forEach(desc => {
|
|
@@ -100,7 +98,7 @@ export class FqnIndex extends ADisposable {
|
|
|
100
98
|
* Returns descedant elements with unique names in the scope
|
|
101
99
|
*/
|
|
102
100
|
uniqueDescedants(projectId, parent) {
|
|
103
|
-
return stream(this.workspaceCache.get(`${
|
|
101
|
+
return stream(this.workspaceCache.get(`${projectId}:uniqueDescedants:${parent}`, () => {
|
|
104
102
|
const { children, descendants } = this.documents(projectId)
|
|
105
103
|
.reduce((map, doc) => {
|
|
106
104
|
const docIndex = this.get(doc);
|
|
@@ -129,18 +127,17 @@ export class FqnIndex extends ADisposable {
|
|
|
129
127
|
if (rootElements.length === 0) {
|
|
130
128
|
return DocumentFqnIndex.EMPTY;
|
|
131
129
|
}
|
|
132
|
-
const projectId = document.likec4ProjectId
|
|
130
|
+
const projectId = document.likec4ProjectId ?? this.projects.belongsTo(document);
|
|
133
131
|
const root = new Array();
|
|
134
132
|
const children = new MultiMap();
|
|
135
133
|
const descendants = new MultiMap();
|
|
136
134
|
const byfqn = new MultiMap();
|
|
137
135
|
const Descriptions = this.services.workspace.AstNodeDescriptionProvider;
|
|
138
136
|
const createAndSaveDescription = (node, name, fqn) => {
|
|
139
|
-
const desc = {
|
|
140
|
-
...Descriptions.createDescription(node, name, document),
|
|
137
|
+
const desc = Object.assign(Descriptions.createDescription(node, name, document), {
|
|
141
138
|
id: fqn,
|
|
142
139
|
likec4ProjectId: projectId,
|
|
143
|
-
};
|
|
140
|
+
});
|
|
144
141
|
ElementOps.writeId(node, fqn);
|
|
145
142
|
byfqn.set(fqn, desc);
|
|
146
143
|
return desc;
|
|
@@ -132,7 +132,6 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
132
132
|
unsafeSyncComputeModel(projectId, manualLayouts) {
|
|
133
133
|
const logger = builderLogger.getChild(projectId);
|
|
134
134
|
const cache = this.cache;
|
|
135
|
-
const viewsCache = this.cache;
|
|
136
135
|
const hasManualLayouts = !!manualLayouts && !isEmpty(manualLayouts);
|
|
137
136
|
const key = computedModelCacheKey(projectId) + (hasManualLayouts ? '+manualLayouts' : '');
|
|
138
137
|
if (cache.has(key)) {
|
|
@@ -163,7 +162,6 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
163
162
|
const previous = this.previousViews[key];
|
|
164
163
|
const view = previous && eq(v, previous) ? previous : v;
|
|
165
164
|
this.previousViews[key] = view;
|
|
166
|
-
viewsCache.set(key, view);
|
|
167
165
|
return [v.id, view];
|
|
168
166
|
});
|
|
169
167
|
const data = {
|