@eclipse-glsp/server-mcp 2.7.0-next.10
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/LICENSE +642 -0
- package/README.md +57 -0
- package/lib/index.d.ts +66 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +82 -0
- package/lib/index.js.map +1 -0
- package/lib/modules/mcp-diagram-module.d.ts +123 -0
- package/lib/modules/mcp-diagram-module.d.ts.map +1 -0
- package/lib/modules/mcp-diagram-module.js +202 -0
- package/lib/modules/mcp-diagram-module.js.map +1 -0
- package/lib/modules/mcp-server-module.d.ts +143 -0
- package/lib/modules/mcp-server-module.d.ts.map +1 -0
- package/lib/modules/mcp-server-module.js +251 -0
- package/lib/modules/mcp-server-module.js.map +1 -0
- package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.d.ts +44 -0
- package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.d.ts.map +1 -0
- package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.js +102 -0
- package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.js.map +1 -0
- package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.d.ts +44 -0
- package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.d.ts.map +1 -0
- package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.js +99 -0
- package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.js.map +1 -0
- package/lib/resources/handlers/diagram-png-mcp-resource-handler.d.ts +84 -0
- package/lib/resources/handlers/diagram-png-mcp-resource-handler.d.ts.map +1 -0
- package/lib/resources/handlers/diagram-png-mcp-resource-handler.js +177 -0
- package/lib/resources/handlers/diagram-png-mcp-resource-handler.js.map +1 -0
- package/lib/resources/handlers/diagram-svg-mcp-resource-handler.d.ts +55 -0
- package/lib/resources/handlers/diagram-svg-mcp-resource-handler.d.ts.map +1 -0
- package/lib/resources/handlers/diagram-svg-mcp-resource-handler.js +99 -0
- package/lib/resources/handlers/diagram-svg-mcp-resource-handler.js.map +1 -0
- package/lib/resources/services/element-types-provider.d.ts +65 -0
- package/lib/resources/services/element-types-provider.d.ts.map +1 -0
- package/lib/resources/services/element-types-provider.js +81 -0
- package/lib/resources/services/element-types-provider.js.map +1 -0
- package/lib/resources/services/mcp-model-serializer.d.ts +78 -0
- package/lib/resources/services/mcp-model-serializer.d.ts.map +1 -0
- package/lib/resources/services/mcp-model-serializer.js +188 -0
- package/lib/resources/services/mcp-model-serializer.js.map +1 -0
- package/lib/server/glsp-mcp-server.d.ts +82 -0
- package/lib/server/glsp-mcp-server.d.ts.map +1 -0
- package/lib/server/glsp-mcp-server.js +140 -0
- package/lib/server/glsp-mcp-server.js.map +1 -0
- package/lib/server/lru-event-store.d.ts +53 -0
- package/lib/server/lru-event-store.d.ts.map +1 -0
- package/lib/server/lru-event-store.js +100 -0
- package/lib/server/lru-event-store.js.map +1 -0
- package/lib/server/mcp-diagram-handler-dispatcher.d.ts +144 -0
- package/lib/server/mcp-diagram-handler-dispatcher.d.ts.map +1 -0
- package/lib/server/mcp-diagram-handler-dispatcher.js +382 -0
- package/lib/server/mcp-diagram-handler-dispatcher.js.map +1 -0
- package/lib/server/mcp-diagram-prompt-handler-registry.d.ts +33 -0
- package/lib/server/mcp-diagram-prompt-handler-registry.d.ts.map +1 -0
- package/lib/server/mcp-diagram-prompt-handler-registry.js +76 -0
- package/lib/server/mcp-diagram-prompt-handler-registry.js.map +1 -0
- package/lib/server/mcp-diagram-resource-handler-registry.d.ts +35 -0
- package/lib/server/mcp-diagram-resource-handler-registry.d.ts.map +1 -0
- package/lib/server/mcp-diagram-resource-handler-registry.js +94 -0
- package/lib/server/mcp-diagram-resource-handler-registry.js.map +1 -0
- package/lib/server/mcp-diagram-tool-handler-registry.d.ts +57 -0
- package/lib/server/mcp-diagram-tool-handler-registry.d.ts.map +1 -0
- package/lib/server/mcp-diagram-tool-handler-registry.js +111 -0
- package/lib/server/mcp-diagram-tool-handler-registry.js.map +1 -0
- package/lib/server/mcp-handler-shared.d.ts +142 -0
- package/lib/server/mcp-handler-shared.d.ts.map +1 -0
- package/lib/server/mcp-handler-shared.js +199 -0
- package/lib/server/mcp-handler-shared.js.map +1 -0
- package/lib/server/mcp-http-transport.d.ts +93 -0
- package/lib/server/mcp-http-transport.d.ts.map +1 -0
- package/lib/server/mcp-http-transport.js +350 -0
- package/lib/server/mcp-http-transport.js.map +1 -0
- package/lib/server/mcp-id-alias-service.d.ts +70 -0
- package/lib/server/mcp-id-alias-service.d.ts.map +1 -0
- package/lib/server/mcp-id-alias-service.js +85 -0
- package/lib/server/mcp-id-alias-service.js.map +1 -0
- package/lib/server/mcp-input-schemas.d.ts +73 -0
- package/lib/server/mcp-input-schemas.d.ts.map +1 -0
- package/lib/server/mcp-input-schemas.js +67 -0
- package/lib/server/mcp-input-schemas.js.map +1 -0
- package/lib/server/mcp-label-provider.d.ts +45 -0
- package/lib/server/mcp-label-provider.d.ts.map +1 -0
- package/lib/server/mcp-label-provider.js +42 -0
- package/lib/server/mcp-label-provider.js.map +1 -0
- package/lib/server/mcp-log-level-registry.d.ts +54 -0
- package/lib/server/mcp-log-level-registry.d.ts.map +1 -0
- package/lib/server/mcp-log-level-registry.js +80 -0
- package/lib/server/mcp-log-level-registry.js.map +1 -0
- package/lib/server/mcp-logger.d.ts +59 -0
- package/lib/server/mcp-logger.d.ts.map +1 -0
- package/lib/server/mcp-logger.js +104 -0
- package/lib/server/mcp-logger.js.map +1 -0
- package/lib/server/mcp-mime-types.d.ts +28 -0
- package/lib/server/mcp-mime-types.d.ts.map +1 -0
- package/lib/server/mcp-mime-types.js +18 -0
- package/lib/server/mcp-mime-types.js.map +1 -0
- package/lib/server/mcp-options.d.ts +39 -0
- package/lib/server/mcp-options.d.ts.map +1 -0
- package/lib/server/mcp-options.js +53 -0
- package/lib/server/mcp-options.js.map +1 -0
- package/lib/server/mcp-progress-reporter.d.ts +48 -0
- package/lib/server/mcp-progress-reporter.d.ts.map +1 -0
- package/lib/server/mcp-progress-reporter.js +66 -0
- package/lib/server/mcp-progress-reporter.js.map +1 -0
- package/lib/server/mcp-prompt-handler.d.ts +120 -0
- package/lib/server/mcp-prompt-handler.d.ts.map +1 -0
- package/lib/server/mcp-prompt-handler.js +131 -0
- package/lib/server/mcp-prompt-handler.js.map +1 -0
- package/lib/server/mcp-request-context.d.ts +37 -0
- package/lib/server/mcp-request-context.d.ts.map +1 -0
- package/lib/server/mcp-request-context.js +37 -0
- package/lib/server/mcp-request-context.js.map +1 -0
- package/lib/server/mcp-resource-handler.d.ts +212 -0
- package/lib/server/mcp-resource-handler.d.ts.map +1 -0
- package/lib/server/mcp-resource-handler.js +298 -0
- package/lib/server/mcp-resource-handler.js.map +1 -0
- package/lib/server/mcp-server-launcher.d.ts +143 -0
- package/lib/server/mcp-server-launcher.d.ts.map +1 -0
- package/lib/server/mcp-server-launcher.js +355 -0
- package/lib/server/mcp-server-launcher.js.map +1 -0
- package/lib/server/mcp-session.d.ts +44 -0
- package/lib/server/mcp-session.d.ts.map +1 -0
- package/lib/server/mcp-session.js +18 -0
- package/lib/server/mcp-session.js.map +1 -0
- package/lib/server/mcp-tool-handler.d.ts +259 -0
- package/lib/server/mcp-tool-handler.d.ts.map +1 -0
- package/lib/server/mcp-tool-handler.js +355 -0
- package/lib/server/mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/count-elements-mcp-tool-handler.d.ts +47 -0
- package/lib/tools/handlers/count-elements-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/count-elements-mcp-tool-handler.js +77 -0
- package/lib/tools/handlers/count-elements-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/create-edges-mcp-tool-handler.d.ts +113 -0
- package/lib/tools/handlers/create-edges-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/create-edges-mcp-tool-handler.js +191 -0
- package/lib/tools/handlers/create-edges-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/create-nodes-mcp-tool-handler.d.ts +82 -0
- package/lib/tools/handlers/create-nodes-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/create-nodes-mcp-tool-handler.js +124 -0
- package/lib/tools/handlers/create-nodes-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/delete-elements-mcp-tool-handler.d.ts +53 -0
- package/lib/tools/handlers/delete-elements-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/delete-elements-mcp-tool-handler.js +74 -0
- package/lib/tools/handlers/delete-elements-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/diagram-model-mcp-tool-handler.d.ts +60 -0
- package/lib/tools/handlers/diagram-model-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/diagram-model-mcp-tool-handler.js +79 -0
- package/lib/tools/handlers/diagram-model-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/element-types-mcp-tool-handler.d.ts +98 -0
- package/lib/tools/handlers/element-types-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/element-types-mcp-tool-handler.js +156 -0
- package/lib/tools/handlers/element-types-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/get-selection-mcp-tool-handler.d.ts +44 -0
- package/lib/tools/handlers/get-selection-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/get-selection-mcp-tool-handler.js +70 -0
- package/lib/tools/handlers/get-selection-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.d.ts +44 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.js +72 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/modify-edges-mcp-tool-handler.d.ts +79 -0
- package/lib/tools/handlers/modify-edges-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/modify-edges-mcp-tool-handler.js +138 -0
- package/lib/tools/handlers/modify-edges-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/modify-nodes-mcp-tool-handler.d.ts +93 -0
- package/lib/tools/handlers/modify-nodes-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/modify-nodes-mcp-tool-handler.js +127 -0
- package/lib/tools/handlers/modify-nodes-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/query-elements-mcp-tool-handler.d.ts +103 -0
- package/lib/tools/handlers/query-elements-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/query-elements-mcp-tool-handler.js +160 -0
- package/lib/tools/handlers/query-elements-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.d.ts +46 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.js +75 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/save-model-mcp-tool-handler.d.ts +57 -0
- package/lib/tools/handlers/save-model-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/save-model-mcp-tool-handler.js +93 -0
- package/lib/tools/handlers/save-model-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/session-info-mcp-tool-handler.d.ts +66 -0
- package/lib/tools/handlers/session-info-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/session-info-mcp-tool-handler.js +109 -0
- package/lib/tools/handlers/session-info-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/set-selection-mcp-tool-handler.d.ts +61 -0
- package/lib/tools/handlers/set-selection-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/set-selection-mcp-tool-handler.js +104 -0
- package/lib/tools/handlers/set-selection-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/set-view-mcp-tool-handler.d.ts +111 -0
- package/lib/tools/handlers/set-view-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/set-view-mcp-tool-handler.js +144 -0
- package/lib/tools/handlers/set-view-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.d.ts +46 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.js +76 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/validate-diagram-mcp-tool-handler.d.ts +67 -0
- package/lib/tools/handlers/validate-diagram-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/validate-diagram-mcp-tool-handler.js +0 -0
- package/lib/tools/handlers/validate-diagram-mcp-tool-handler.js.map +1 -0
- package/lib/util/markdown-util.d.ts +20 -0
- package/lib/util/markdown-util.d.ts.map +1 -0
- package/lib/util/markdown-util.js +45 -0
- package/lib/util/markdown-util.js.map +1 -0
- package/lib/util/mcp-util.d.ts +22 -0
- package/lib/util/mcp-util.d.ts.map +1 -0
- package/lib/util/mcp-util.js +29 -0
- package/lib/util/mcp-util.js.map +1 -0
- package/package.json +63 -0
- package/src/index.ts +65 -0
- package/src/modules/mcp-diagram-module.ts +247 -0
- package/src/modules/mcp-server-module.ts +289 -0
- package/src/prompts/handlers/describe-diagram-mcp-prompt-handler.ts +88 -0
- package/src/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts +85 -0
- package/src/resources/handlers/diagram-png-mcp-resource-handler.ts +177 -0
- package/src/resources/handlers/diagram-svg-mcp-resource-handler.ts +85 -0
- package/src/resources/services/element-types-provider.ts +105 -0
- package/src/resources/services/mcp-model-serializer.ts +211 -0
- package/src/server/glsp-mcp-server.spec.ts +73 -0
- package/src/server/glsp-mcp-server.ts +196 -0
- package/src/server/lru-event-store.spec.ts +121 -0
- package/src/server/lru-event-store.ts +112 -0
- package/src/server/mcp-diagram-handler-dispatcher.spec.ts +231 -0
- package/src/server/mcp-diagram-handler-dispatcher.ts +459 -0
- package/src/server/mcp-diagram-prompt-handler-registry.ts +59 -0
- package/src/server/mcp-diagram-resource-handler-registry.ts +73 -0
- package/src/server/mcp-diagram-tool-handler-registry.ts +97 -0
- package/src/server/mcp-handler-shared.spec.ts +53 -0
- package/src/server/mcp-handler-shared.ts +247 -0
- package/src/server/mcp-http-transport-e2e.spec.ts +151 -0
- package/src/server/mcp-http-transport.spec.ts +385 -0
- package/src/server/mcp-http-transport.ts +368 -0
- package/src/server/mcp-id-alias-service.spec.ts +106 -0
- package/src/server/mcp-id-alias-service.ts +104 -0
- package/src/server/mcp-input-schemas.ts +82 -0
- package/src/server/mcp-label-provider.ts +52 -0
- package/src/server/mcp-log-level-registry.spec.ts +75 -0
- package/src/server/mcp-log-level-registry.ts +90 -0
- package/src/server/mcp-logger.spec.ts +227 -0
- package/src/server/mcp-logger.ts +91 -0
- package/src/server/mcp-mime-types.ts +31 -0
- package/src/server/mcp-options.ts +43 -0
- package/src/server/mcp-progress-reporter.spec.ts +93 -0
- package/src/server/mcp-progress-reporter.ts +67 -0
- package/src/server/mcp-prompt-handler.ts +157 -0
- package/src/server/mcp-request-context.ts +39 -0
- package/src/server/mcp-resource-handler.ts +389 -0
- package/src/server/mcp-server-launcher.spec.ts +173 -0
- package/src/server/mcp-server-launcher.ts +369 -0
- package/src/server/mcp-session.ts +45 -0
- package/src/server/mcp-tool-handler.spec.ts +182 -0
- package/src/server/mcp-tool-handler.ts +431 -0
- package/src/server/raw-http.spec.ts +59 -0
- package/src/tools/handlers/count-elements-mcp-tool-handler.spec.ts +101 -0
- package/src/tools/handlers/count-elements-mcp-tool-handler.ts +68 -0
- package/src/tools/handlers/create-edges-mcp-tool-handler.spec.ts +198 -0
- package/src/tools/handlers/create-edges-mcp-tool-handler.ts +200 -0
- package/src/tools/handlers/create-nodes-mcp-tool-handler.spec.ts +199 -0
- package/src/tools/handlers/create-nodes-mcp-tool-handler.ts +126 -0
- package/src/tools/handlers/delete-elements-mcp-tool-handler.ts +69 -0
- package/src/tools/handlers/diagram-model-mcp-tool-handler.ts +68 -0
- package/src/tools/handlers/element-types-mcp-tool-handler.ts +152 -0
- package/src/tools/handlers/get-selection-mcp-tool-handler.ts +56 -0
- package/src/tools/handlers/layout-mcp-tool-handler.ts +58 -0
- package/src/tools/handlers/modify-edges-mcp-tool-handler.ts +142 -0
- package/src/tools/handlers/modify-nodes-mcp-tool-handler.ts +134 -0
- package/src/tools/handlers/query-elements-mcp-tool-handler.spec.ts +212 -0
- package/src/tools/handlers/query-elements-mcp-tool-handler.ts +157 -0
- package/src/tools/handlers/redo-mcp-tool-handler.ts +64 -0
- package/src/tools/handlers/save-model-mcp-tool-handler.ts +74 -0
- package/src/tools/handlers/session-info-mcp-tool-handler.spec.ts +152 -0
- package/src/tools/handlers/session-info-mcp-tool-handler.ts +98 -0
- package/src/tools/handlers/set-selection-mcp-tool-handler.spec.ts +120 -0
- package/src/tools/handlers/set-selection-mcp-tool-handler.ts +92 -0
- package/src/tools/handlers/set-view-mcp-tool-handler.ts +156 -0
- package/src/tools/handlers/undo-mcp-tool-handler.ts +63 -0
- package/src/tools/handlers/validate-diagram-mcp-tool-handler.ts +0 -0
- package/src/tools/tool-annotations.spec.ts +141 -0
- package/src/util/markdown-util.ts +44 -0
- package/src/util/mcp-util.ts +25 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { GModelElement } from '@eclipse-glsp/graph';
|
|
18
|
+
import { Dimension, GModelSerializer, Logger, Point } from '@eclipse-glsp/server';
|
|
19
|
+
import { inject, injectable, postConstruct } from 'inversify';
|
|
20
|
+
import { McpStructuredContent } from '../../server/mcp-handler-shared';
|
|
21
|
+
import { McpIdAliasService } from '../../server/mcp-id-alias-service';
|
|
22
|
+
import { objectArrayToMarkdownTable } from '../../util/markdown-util';
|
|
23
|
+
|
|
24
|
+
export const McpModelSerializer = Symbol('McpModelSerializer');
|
|
25
|
+
|
|
26
|
+
/** Loose JSON-object shape mirroring `GModelSerializer.createSchema` output — open-keyed because
|
|
27
|
+
* GModel attributes vary per element type. Reads must narrow before use. */
|
|
28
|
+
export interface SerializedElement {
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Transforms a graphical model into a representation suitable for LLM consumption. Markdown and
|
|
34
|
+
* JSON are both reasonable formats; the default impl ({@link MarkdownMcpModelSerializer}) emits
|
|
35
|
+
* Markdown for {@link serialize}/{@link serializeArray} and an object payload for
|
|
36
|
+
* {@link serializeStructured} (the dual-emit `structuredContent` counterpart). Aliasing of element
|
|
37
|
+
* ids happens internally via the per-session {@link McpIdAliasService}.
|
|
38
|
+
*
|
|
39
|
+
* @experimental
|
|
40
|
+
*/
|
|
41
|
+
export interface McpModelSerializer {
|
|
42
|
+
/** Serializes a single element (and its descendants). */
|
|
43
|
+
serialize(element: GModelElement): string;
|
|
44
|
+
|
|
45
|
+
/** Serializes an array of elements; duplicates introduced by hierarchy are removed. */
|
|
46
|
+
serializeArray(elements: GModelElement[]): string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Structured-content counterpart of {@link serialize} for dual-emit
|
|
50
|
+
* (`CallToolResult.structuredContent`). The shape is intentionally open: `{ elements: [...] }`
|
|
51
|
+
* with each entry carrying `id` + `type` + adopter-specific attrs (passthrough).
|
|
52
|
+
*/
|
|
53
|
+
serializeStructured(element: GModelElement): McpStructuredContent;
|
|
54
|
+
|
|
55
|
+
/** Structured-content counterpart of {@link serializeArray}. */
|
|
56
|
+
serializeStructuredArray(elements: GModelElement[]): McpStructuredContent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default {@link McpModelSerializer} — emits Markdown with one H1 section per element type
|
|
61
|
+
* followed by a table of all elements of that type. Flattens the GModel tree, drops keys with
|
|
62
|
+
* no LLM value (`cssClasses`, `revision`, `layout`, etc., see {@link keysToRemove}), truncates
|
|
63
|
+
* position+size to integers and derives a `bounds` rectangle so the LLM doesn't redo arithmetic.
|
|
64
|
+
*
|
|
65
|
+
* Generic: no control over element order or per-type attribute order, since no specific GLSP
|
|
66
|
+
* adopter is known. Adopters override (see workflow's `WorkflowMcpModelSerializer`) when
|
|
67
|
+
* semantic ordering matters.
|
|
68
|
+
*/
|
|
69
|
+
@injectable()
|
|
70
|
+
export class MarkdownMcpModelSerializer implements McpModelSerializer {
|
|
71
|
+
@inject(GModelSerializer) protected gModelSerializer: GModelSerializer;
|
|
72
|
+
@inject(McpIdAliasService) protected aliasService: McpIdAliasService;
|
|
73
|
+
@inject(Logger) protected logger: Logger;
|
|
74
|
+
|
|
75
|
+
protected keysToRemove: string[] = [
|
|
76
|
+
'cssClasses',
|
|
77
|
+
'revision',
|
|
78
|
+
'layout',
|
|
79
|
+
'args',
|
|
80
|
+
'layoutOptions',
|
|
81
|
+
'alignment',
|
|
82
|
+
'children',
|
|
83
|
+
'routingPoints',
|
|
84
|
+
'resizeLocations',
|
|
85
|
+
'parent'
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
/** Warn once per binding when the bare default is used — subclasses suppress via the constructor check. */
|
|
89
|
+
@postConstruct()
|
|
90
|
+
protected warnIfGenericDefault(): void {
|
|
91
|
+
if (this.constructor === MarkdownMcpModelSerializer) {
|
|
92
|
+
this.logger.warn(
|
|
93
|
+
'Using generic MarkdownMcpModelSerializer; bind a diagram-specific subclass via ' +
|
|
94
|
+
'DefaultMcpDiagramModule.bindModelSerializer() for richer LLM output.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
serialize(element: GModelElement): string {
|
|
100
|
+
return this.serializeArray([element]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
serializeArray(elements: GModelElement[]): string {
|
|
104
|
+
return Object.entries(this.buildAliasedTypeBuckets(elements))
|
|
105
|
+
.filter(([, bucket]) => bucket.length > 0)
|
|
106
|
+
.flatMap(([type, bucket]) => [`# ${type}`, objectArrayToMarkdownTable(bucket)])
|
|
107
|
+
.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
serializeStructured(element: GModelElement): McpStructuredContent {
|
|
111
|
+
return this.serializeStructuredArray([element]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
serializeStructuredArray(elements: GModelElement[]): McpStructuredContent {
|
|
115
|
+
return {
|
|
116
|
+
elements: Object.values(this.buildAliasedTypeBuckets(elements))
|
|
117
|
+
.filter(bucket => bucket.length > 0)
|
|
118
|
+
.flat()
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Common pipeline: prepare, dedupe by id, group by type, alias ids — used by both renders. */
|
|
123
|
+
protected buildAliasedTypeBuckets(elements: GModelElement[]): Record<string, SerializedElement[]> {
|
|
124
|
+
const elementsByTypeArray = elements.map(element => this.prepareElement(element));
|
|
125
|
+
const result: Record<string, SerializedElement[]> = {};
|
|
126
|
+
const allKeys = new Set(elementsByTypeArray.flatMap(obj => Object.keys(obj)));
|
|
127
|
+
allKeys.forEach(key => {
|
|
128
|
+
const combined = elementsByTypeArray.flatMap(obj => obj[key] ?? []);
|
|
129
|
+
result[key] = Array.from(new Map(combined.map(item => [item.id, item])).values()).map(item => this.applyAlias(item));
|
|
130
|
+
});
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected applyAlias(element: SerializedElement): SerializedElement {
|
|
135
|
+
for (const field of ['id', 'sourceId', 'targetId', 'parentId'] as const) {
|
|
136
|
+
const value = element[field];
|
|
137
|
+
if (typeof value === 'string') {
|
|
138
|
+
element[field] = this.aliasService.alias(value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return element;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected prepareElement(element: GModelElement): Record<string, SerializedElement[]> {
|
|
145
|
+
const schema = this.gModelSerializer.createSchema(element) as unknown as SerializedElement;
|
|
146
|
+
const elements = this.flattenStructure(schema, element.parent?.id);
|
|
147
|
+
|
|
148
|
+
const result: Record<string, SerializedElement[]> = {};
|
|
149
|
+
elements.forEach(elem => {
|
|
150
|
+
this.removeKeys(elem);
|
|
151
|
+
this.combinePositionAndSize(elem);
|
|
152
|
+
const type = typeof elem.type === 'string' ? elem.type : 'unknown';
|
|
153
|
+
(result[type] ??= []).push(elem);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected flattenStructure(element: SerializedElement, parentId?: string): SerializedElement[] {
|
|
160
|
+
const newElement: SerializedElement = { ...element };
|
|
161
|
+
const result: SerializedElement[] = [newElement];
|
|
162
|
+
const children = newElement.children;
|
|
163
|
+
if (Array.isArray(children)) {
|
|
164
|
+
const ownId = typeof newElement.id === 'string' ? newElement.id : undefined;
|
|
165
|
+
children.forEach(child =>
|
|
166
|
+
this.flattenStructure(child as SerializedElement, ownId).forEach(descendant => result.push(descendant))
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
newElement.parentId = parentId;
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected removeKeys(element: SerializedElement): void {
|
|
174
|
+
for (const key of Object.keys(element)) {
|
|
175
|
+
if (this.keysToRemove.includes(key)) {
|
|
176
|
+
delete element[key];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected combinePositionAndSize(element: SerializedElement): void {
|
|
182
|
+
const position = element.position;
|
|
183
|
+
if (!Point.is(position)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const x = Math.trunc(position.x);
|
|
187
|
+
const y = Math.trunc(position.y);
|
|
188
|
+
element.position = { x, y };
|
|
189
|
+
|
|
190
|
+
// Omit `size` and `bounds` when an element has no explicit geometry yet (e.g., a freshly
|
|
191
|
+
// created node whose `.size()` has not been applied). Emitting `{width: 0, height: 0}`
|
|
192
|
+
// would mislead an LLM consumer into "fixing" placeholder bounds. Visual rendering on
|
|
193
|
+
// the GLSP/sprotty client is unaffected — those nodes are laid out via CSS regardless.
|
|
194
|
+
const size = element.size;
|
|
195
|
+
if (!Dimension.is(size) || (Math.trunc(size.width) === 0 && Math.trunc(size.height) === 0)) {
|
|
196
|
+
delete element.size;
|
|
197
|
+
delete element.bounds;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const width = Math.trunc(size.width);
|
|
202
|
+
const height = Math.trunc(size.height);
|
|
203
|
+
element.size = { width, height };
|
|
204
|
+
element.bounds = {
|
|
205
|
+
left: x,
|
|
206
|
+
right: x + width,
|
|
207
|
+
top: y,
|
|
208
|
+
bottom: y + height
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { NullLogger } from '@eclipse-glsp/server';
|
|
18
|
+
import { McpServer, RegisteredResource } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
19
|
+
import { expect } from 'chai';
|
|
20
|
+
import * as sinon from 'sinon';
|
|
21
|
+
import * as z from 'zod/v4';
|
|
22
|
+
import { DefaultGLSPMcpServer } from './glsp-mcp-server';
|
|
23
|
+
|
|
24
|
+
describe('DefaultGLSPMcpServer', () => {
|
|
25
|
+
function makeServer(): { wrapper: DefaultGLSPMcpServer; sdk: McpServer } {
|
|
26
|
+
const sdk = new McpServer({ name: 'test', version: '1.0.0' }, { capabilities: {} });
|
|
27
|
+
const wrapper = new DefaultGLSPMcpServer(sdk, { dataMode: 'tools' }, new NullLogger());
|
|
28
|
+
return { wrapper, sdk };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
it('tracks every registerTool call so listTools returns the registered set', () => {
|
|
32
|
+
const { wrapper } = makeServer();
|
|
33
|
+
wrapper.registerTool('alpha', { description: 'first', inputSchema: { x: z.string() } }, async () => ({ content: [] }));
|
|
34
|
+
wrapper.registerTool('beta', { description: 'second' }, async () => ({ content: [] }));
|
|
35
|
+
|
|
36
|
+
const names = wrapper.listTools().map(tool => tool.description);
|
|
37
|
+
expect(names).to.have.members(['first', 'second']);
|
|
38
|
+
expect(wrapper.listTools()).to.have.lengthOf(2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('tracks registerResource (static URI) and listResources reflects it by name', () => {
|
|
42
|
+
const { wrapper } = makeServer();
|
|
43
|
+
wrapper.registerResource('my-resource', 'glsp://test', { title: 'res', mimeType: 'text/plain' }, async () => ({
|
|
44
|
+
contents: [{ uri: 'glsp://test', text: 'ok' }]
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const resources = wrapper.listResources();
|
|
48
|
+
expect(resources).to.have.lengthOf(1);
|
|
49
|
+
expect((resources[0] as RegisteredResource).name).to.equal('my-resource');
|
|
50
|
+
expect((resources[0] as RegisteredResource).title).to.equal('res');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('tracks registerPrompt and listPrompts reflects it by description', () => {
|
|
54
|
+
const { wrapper } = makeServer();
|
|
55
|
+
wrapper.registerPrompt('describe', { description: 'desc' }, async () => ({ messages: [] }));
|
|
56
|
+
|
|
57
|
+
const prompts = wrapper.listPrompts();
|
|
58
|
+
expect(prompts).to.have.lengthOf(1);
|
|
59
|
+
expect(prompts[0].description).to.equal('desc');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('getRawServer() returns the exact SDK instance passed in (escape hatch identity)', () => {
|
|
63
|
+
const { wrapper, sdk } = makeServer();
|
|
64
|
+
expect(wrapper.getRawServer()).to.equal(sdk);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('dispose() invokes close() on the underlying SDK server', () => {
|
|
68
|
+
const { wrapper, sdk } = makeServer();
|
|
69
|
+
const closeSpy = sinon.spy(sdk, 'close');
|
|
70
|
+
wrapper.dispose();
|
|
71
|
+
expect(closeSpy.calledOnce).to.be.true;
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { Disposable, Logger, McpServerOptions } from '@eclipse-glsp/server';
|
|
18
|
+
import {
|
|
19
|
+
McpServer,
|
|
20
|
+
RegisteredPrompt,
|
|
21
|
+
RegisteredResource,
|
|
22
|
+
RegisteredResourceTemplate,
|
|
23
|
+
RegisteredTool
|
|
24
|
+
} from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
25
|
+
import { EmptyResultSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
26
|
+
import { injectable, unmanaged } from 'inversify';
|
|
27
|
+
|
|
28
|
+
export const GLSPMcpServer = Symbol('GLSPMcpServer');
|
|
29
|
+
|
|
30
|
+
/** Either a static resource or a templated resource registration. */
|
|
31
|
+
export type GLSPMcpResource = RegisteredResource | RegisteredResourceTemplate;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Cadence for server-initiated `ping` requests on the standalone SSE GET stream. Shorter than
|
|
35
|
+
* typical client-side SSE read timeouts (e.g. undici's 5-min `bodyTimeout`) so the stream stays
|
|
36
|
+
* alive across chat-idle periods.
|
|
37
|
+
*/
|
|
38
|
+
const KEEP_ALIVE_INTERVAL_MS = 30_000;
|
|
39
|
+
|
|
40
|
+
/** Per-ping timeout — short, since a ping with no SSE GET stream open will never resolve. */
|
|
41
|
+
const KEEP_ALIVE_PING_TIMEOUT_MS = 5_000;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Curated, per-session view onto the underlying MCP server. Handlers see this during
|
|
45
|
+
* registration; the launcher disposes it on session-close and on server shutdown.
|
|
46
|
+
*/
|
|
47
|
+
export interface GLSPMcpServer
|
|
48
|
+
extends Pick<McpServer, 'registerPrompt' | 'registerResource' | 'registerTool' | 'sendLoggingMessage' | 'isConnected' | 'connect'>,
|
|
49
|
+
Disposable {
|
|
50
|
+
readonly options: McpServerOptions;
|
|
51
|
+
|
|
52
|
+
/** Register a tool. Wraps the SDK's {@link McpServer.registerTool} and updates the local log. */
|
|
53
|
+
readonly registerTool: McpServer['registerTool'];
|
|
54
|
+
/** Register a resource. Wraps the SDK's {@link McpServer.registerResource} and updates the local log. */
|
|
55
|
+
readonly registerResource: McpServer['registerResource'];
|
|
56
|
+
/** Register a prompt. Wraps the SDK's {@link McpServer.registerPrompt} and updates the local log. */
|
|
57
|
+
readonly registerPrompt: McpServer['registerPrompt'];
|
|
58
|
+
/** Send an LLM-facing log message via the MCP `logging/message` notification. */
|
|
59
|
+
sendLoggingMessage: McpServer['sendLoggingMessage'];
|
|
60
|
+
/** True iff a transport is currently connected. */
|
|
61
|
+
isConnected: McpServer['isConnected'];
|
|
62
|
+
/** Attach the supplied transport. */
|
|
63
|
+
connect: McpServer['connect'];
|
|
64
|
+
|
|
65
|
+
listTools(): RegisteredTool[];
|
|
66
|
+
/** True iff a tool with this exact `name` has been registered on this server instance. */
|
|
67
|
+
hasTool(name: string): boolean;
|
|
68
|
+
listResources(): GLSPMcpResource[];
|
|
69
|
+
listPrompts(): RegisteredPrompt[];
|
|
70
|
+
/** Sends an MCP `ping` and resolves on empty result, rejects on transport timeout. */
|
|
71
|
+
ping(): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Escape hatch to the underlying SDK `McpServer` for advanced APIs not covered by the
|
|
74
|
+
* stable surface. The returned value is the SDK type, not a stable GLSP surface.
|
|
75
|
+
*/
|
|
76
|
+
getRawServer(): McpServer;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const GLSPMcpServerFactory = Symbol('GLSPMcpServerFactory');
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Factory that produces a {@link GLSPMcpServer} per MCP client session.
|
|
83
|
+
* Bound to {@link DefaultGLSPMcpServer} by default; rebind to swap the
|
|
84
|
+
* implementation across all sessions.
|
|
85
|
+
*/
|
|
86
|
+
export type GLSPMcpServerFactory = (mcpServer: McpServer, options: McpServerOptions) => GLSPMcpServer;
|
|
87
|
+
|
|
88
|
+
@injectable()
|
|
89
|
+
export class DefaultGLSPMcpServer implements GLSPMcpServer {
|
|
90
|
+
protected readonly tools = new Map<string, RegisteredTool>();
|
|
91
|
+
protected readonly resources = new Map<string, GLSPMcpResource>();
|
|
92
|
+
protected readonly prompts = new Map<string, RegisteredPrompt>();
|
|
93
|
+
protected keepAliveTimer?: NodeJS.Timeout;
|
|
94
|
+
|
|
95
|
+
readonly registerTool: McpServer['registerTool'];
|
|
96
|
+
readonly registerResource: McpServer['registerResource'];
|
|
97
|
+
readonly registerPrompt: McpServer['registerPrompt'];
|
|
98
|
+
|
|
99
|
+
constructor(
|
|
100
|
+
@unmanaged() protected readonly mcpServer: McpServer,
|
|
101
|
+
@unmanaged() readonly options: McpServerOptions,
|
|
102
|
+
@unmanaged() protected readonly logger: Logger
|
|
103
|
+
) {
|
|
104
|
+
// `register*` need an interception layer so the local registration log stays in
|
|
105
|
+
// sync; the Proxy preserves the SDK's generic signatures (which a wrapped method
|
|
106
|
+
// using `Parameters<>` would collapse, breaking adopter autocomplete on
|
|
107
|
+
// `inputSchema → handler arg shape`).
|
|
108
|
+
const { tools, resources, prompts } = this;
|
|
109
|
+
this.registerTool = new Proxy(mcpServer.registerTool.bind(mcpServer), {
|
|
110
|
+
apply(target, thisArg, args) {
|
|
111
|
+
const registered = Reflect.apply(target, thisArg, args);
|
|
112
|
+
if (typeof args[0] === 'string') {
|
|
113
|
+
tools.set(args[0], registered);
|
|
114
|
+
}
|
|
115
|
+
return registered;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
this.registerResource = new Proxy(mcpServer.registerResource.bind(mcpServer), {
|
|
119
|
+
apply(target, thisArg, args) {
|
|
120
|
+
const registered = Reflect.apply(target, thisArg, args);
|
|
121
|
+
if (typeof args[0] === 'string') {
|
|
122
|
+
resources.set(args[0], registered);
|
|
123
|
+
}
|
|
124
|
+
return registered;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
this.registerPrompt = new Proxy(mcpServer.registerPrompt.bind(mcpServer), {
|
|
128
|
+
apply(target, thisArg, args) {
|
|
129
|
+
const registered = Reflect.apply(target, thisArg, args);
|
|
130
|
+
if (typeof args[0] === 'string') {
|
|
131
|
+
prompts.set(args[0], registered);
|
|
132
|
+
}
|
|
133
|
+
return registered;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sendLoggingMessage(...args: Parameters<McpServer['sendLoggingMessage']>): ReturnType<McpServer['sendLoggingMessage']> {
|
|
139
|
+
return this.mcpServer.sendLoggingMessage(...args);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async connect(...args: Parameters<McpServer['connect']>): ReturnType<McpServer['connect']> {
|
|
143
|
+
await this.mcpServer.connect(...args);
|
|
144
|
+
// Suppress the expected `RequestTimeout` when no SSE GET stream is open; surface other
|
|
145
|
+
// failures via debug. `unref` so a forgotten timer can't pin the process.
|
|
146
|
+
this.keepAliveTimer = setInterval(
|
|
147
|
+
() =>
|
|
148
|
+
this.ping().catch(err => {
|
|
149
|
+
if (!(err instanceof McpError) || err.code !== ErrorCode.RequestTimeout) {
|
|
150
|
+
this.logger.debug('MCP keep-alive ping failed:', err);
|
|
151
|
+
}
|
|
152
|
+
}),
|
|
153
|
+
KEEP_ALIVE_INTERVAL_MS
|
|
154
|
+
);
|
|
155
|
+
this.keepAliveTimer.unref();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
isConnected(): boolean {
|
|
159
|
+
return this.mcpServer.isConnected();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async ping(): Promise<void> {
|
|
163
|
+
// Bypass `Server.ping()` to pass a per-request timeout; SDK's no-arg wrapper uses the
|
|
164
|
+
// 60s default, which leaves keep-alive pings dangling for a full minute when no SSE GET
|
|
165
|
+
// stream is open.
|
|
166
|
+
await this.mcpServer.server.request({ method: 'ping' }, EmptyResultSchema, { timeout: KEEP_ALIVE_PING_TIMEOUT_MS });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
listTools(): RegisteredTool[] {
|
|
170
|
+
return [...this.tools.values()];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
hasTool(name: string): boolean {
|
|
174
|
+
return this.tools.has(name);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
listResources(): GLSPMcpResource[] {
|
|
178
|
+
return [...this.resources.values()];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
listPrompts(): RegisteredPrompt[] {
|
|
182
|
+
return [...this.prompts.values()];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getRawServer(): McpServer {
|
|
186
|
+
return this.mcpServer;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
dispose(): void {
|
|
190
|
+
if (this.keepAliveTimer) {
|
|
191
|
+
clearInterval(this.keepAliveTimer);
|
|
192
|
+
this.keepAliveTimer = undefined;
|
|
193
|
+
}
|
|
194
|
+
this.mcpServer.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { Logger } from '@eclipse-glsp/server';
|
|
18
|
+
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
19
|
+
import { expect } from 'chai';
|
|
20
|
+
import { LruEventStore } from './lru-event-store';
|
|
21
|
+
|
|
22
|
+
function jsonRpc(id: number): JSONRPCMessage {
|
|
23
|
+
return { jsonrpc: '2.0', id, result: { value: id } };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('LruEventStore', () => {
|
|
27
|
+
it('rejects an invalid limit (< 1)', () => {
|
|
28
|
+
expect(() => new LruEventStore(0)).to.throw(/limit must be >= 1/);
|
|
29
|
+
expect(() => new LruEventStore(-5)).to.throw(/limit must be >= 1/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('storeEvent returns a streamId-prefixed event id', async () => {
|
|
33
|
+
const store = new LruEventStore();
|
|
34
|
+
const id = await store.storeEvent('stream-A', jsonRpc(1));
|
|
35
|
+
expect(id.split('_')[0]).to.equal('stream-A');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('evicts the oldest event once the cap is exceeded', async () => {
|
|
39
|
+
const store = new LruEventStore(3);
|
|
40
|
+
const id1 = await store.storeEvent('s', jsonRpc(1));
|
|
41
|
+
const id2 = await store.storeEvent('s', jsonRpc(2));
|
|
42
|
+
await store.storeEvent('s', jsonRpc(3));
|
|
43
|
+
|
|
44
|
+
expect(store.size).to.equal(3);
|
|
45
|
+
|
|
46
|
+
// Adding a 4th event evicts the oldest (id1).
|
|
47
|
+
const id4 = await store.storeEvent('s', jsonRpc(4));
|
|
48
|
+
expect(store.size).to.equal(3);
|
|
49
|
+
|
|
50
|
+
// Replay since id1 → can't find it (evicted) → returns ''.
|
|
51
|
+
const replayed: string[] = [];
|
|
52
|
+
const streamId = await store.replayEventsAfter(id1, {
|
|
53
|
+
send: async eventId => {
|
|
54
|
+
replayed.push(eventId);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
expect(streamId).to.equal(''); // id1 has been evicted
|
|
58
|
+
expect(replayed).to.have.lengthOf(0);
|
|
59
|
+
|
|
60
|
+
// Replay since id2 still works — id3 (still in store) and id4 are sent in order.
|
|
61
|
+
const replayedFromId2: string[] = [];
|
|
62
|
+
await store.replayEventsAfter(id2, {
|
|
63
|
+
send: async eventId => {
|
|
64
|
+
replayedFromId2.push(eventId);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
expect(replayedFromId2).to.have.lengthOf(2);
|
|
68
|
+
expect(replayedFromId2[1]).to.equal(id4);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('replays only events from the same stream after the lastEventId', async () => {
|
|
72
|
+
const store = new LruEventStore();
|
|
73
|
+
const a1 = await store.storeEvent('A', jsonRpc(1));
|
|
74
|
+
await store.storeEvent('B', jsonRpc(2)); // different stream — must be skipped
|
|
75
|
+
const a3 = await store.storeEvent('A', jsonRpc(3));
|
|
76
|
+
const a4 = await store.storeEvent('A', jsonRpc(4));
|
|
77
|
+
|
|
78
|
+
const replayed: Array<{ id: string; msg: JSONRPCMessage }> = [];
|
|
79
|
+
const streamId = await store.replayEventsAfter(a1, {
|
|
80
|
+
send: async (id, msg) => {
|
|
81
|
+
replayed.push({ id, msg });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(streamId).to.equal('A');
|
|
86
|
+
expect(replayed.map(e => e.id)).to.deep.equal([a3, a4]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns "" when lastEventId is unknown', async () => {
|
|
90
|
+
const store = new LruEventStore();
|
|
91
|
+
await store.storeEvent('A', jsonRpc(1));
|
|
92
|
+
|
|
93
|
+
const streamId = await store.replayEventsAfter('A_999_xxx', {
|
|
94
|
+
send: async () => {
|
|
95
|
+
throw new Error('should not be called');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(streamId).to.equal('');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('logs a cap-eviction warn when replaying an evicted last-event-id', async () => {
|
|
103
|
+
const warns: string[] = [];
|
|
104
|
+
const logger = { warn: (message: string) => warns.push(message) } as unknown as Logger;
|
|
105
|
+
const store = new LruEventStore(2, logger);
|
|
106
|
+
const evictedId = await store.storeEvent('s', jsonRpc(1));
|
|
107
|
+
await store.storeEvent('s', jsonRpc(2));
|
|
108
|
+
await store.storeEvent('s', jsonRpc(3));
|
|
109
|
+
|
|
110
|
+
const streamId = await store.replayEventsAfter(evictedId, {
|
|
111
|
+
send: async () => {
|
|
112
|
+
throw new Error('should not be called');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(streamId).to.equal('');
|
|
117
|
+
expect(warns).to.have.lengthOf(1);
|
|
118
|
+
expect(warns[0]).to.match(/Replay miss/);
|
|
119
|
+
expect(warns[0]).to.include('Cap is 2');
|
|
120
|
+
});
|
|
121
|
+
});
|