@eclipse-glsp/server-mcp 2.7.0-next.9
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 +23 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.d.ts +43 -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 +96 -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 +43 -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 +95 -0
- package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.js.map +1 -0
- package/lib/prompts/index.d.ts +18 -0
- package/lib/prompts/index.d.ts.map +1 -0
- package/lib/prompts/index.js +34 -0
- package/lib/prompts/index.js.map +1 -0
- package/lib/resources/handlers/diagram-png-mcp-resource-handler.d.ts +81 -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 +174 -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 +52 -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 +96 -0
- package/lib/resources/handlers/diagram-svg-mcp-resource-handler.js.map +1 -0
- package/lib/resources/index.d.ts +20 -0
- package/lib/resources/index.d.ts.map +1 -0
- package/lib/resources/index.js +36 -0
- package/lib/resources/index.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/index.d.ts +37 -0
- package/lib/server/index.d.ts.map +1 -0
- package/lib/server/index.js +57 -0
- package/lib/server/index.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-module.d.ts +123 -0
- package/lib/server/mcp-diagram-module.d.ts.map +1 -0
- package/lib/server/mcp-diagram-module.js +186 -0
- package/lib/server/mcp-diagram-module.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-server-module.d.ts +143 -0
- package/lib/server/mcp-server-module.d.ts.map +1 -0
- package/lib/server/mcp-server-module.js +249 -0
- package/lib/server/mcp-server-module.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 +46 -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 +76 -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 +112 -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 +190 -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 +81 -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 +123 -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 +52 -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 +73 -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 +59 -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 +78 -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 +97 -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 +155 -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 +43 -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 +68 -0
- package/lib/tools/handlers/get-selection-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.d.ts +43 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.js +71 -0
- package/lib/tools/handlers/layout-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/modify-edges-mcp-tool-handler.d.ts +78 -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 +136 -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 +92 -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 +125 -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 +102 -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 +158 -0
- package/lib/tools/handlers/query-elements-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.d.ts +45 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.js +73 -0
- package/lib/tools/handlers/redo-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/save-model-mcp-tool-handler.d.ts +55 -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 +91 -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 +65 -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 +108 -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 +60 -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 +103 -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 +110 -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 +142 -0
- package/lib/tools/handlers/set-view-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.d.ts +45 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.d.ts.map +1 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.js +74 -0
- package/lib/tools/handlers/undo-mcp-tool-handler.js.map +1 -0
- package/lib/tools/handlers/validate-diagram-mcp-tool-handler.d.ts +66 -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/tools/index.d.ts +34 -0
- package/lib/tools/index.d.ts.map +1 -0
- package/lib/tools/index.js +50 -0
- package/lib/tools/index.js.map +1 -0
- package/lib/util/index.d.ts +18 -0
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/index.js +34 -0
- package/lib/util/index.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 +24 -0
- package/src/prompts/handlers/describe-diagram-mcp-prompt-handler.ts +89 -0
- package/src/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts +86 -0
- package/src/prompts/index.ts +18 -0
- package/src/resources/handlers/diagram-png-mcp-resource-handler.ts +181 -0
- package/src/resources/handlers/diagram-svg-mcp-resource-handler.ts +89 -0
- package/src/resources/index.ts +20 -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/index.ts +42 -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-module.ts +248 -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-server-module.ts +287 -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 +99 -0
- package/src/tools/handlers/count-elements-mcp-tool-handler.ts +66 -0
- package/src/tools/handlers/create-edges-mcp-tool-handler.spec.ts +196 -0
- package/src/tools/handlers/create-edges-mcp-tool-handler.ts +205 -0
- package/src/tools/handlers/create-nodes-mcp-tool-handler.spec.ts +197 -0
- package/src/tools/handlers/create-nodes-mcp-tool-handler.ts +131 -0
- package/src/tools/handlers/delete-elements-mcp-tool-handler.ts +73 -0
- package/src/tools/handlers/diagram-model-mcp-tool-handler.ts +66 -0
- package/src/tools/handlers/element-types-mcp-tool-handler.ts +151 -0
- package/src/tools/handlers/get-selection-mcp-tool-handler.ts +54 -0
- package/src/tools/handlers/layout-mcp-tool-handler.ts +56 -0
- package/src/tools/handlers/modify-edges-mcp-tool-handler.ts +148 -0
- package/src/tools/handlers/modify-nodes-mcp-tool-handler.ts +140 -0
- package/src/tools/handlers/query-elements-mcp-tool-handler.spec.ts +210 -0
- package/src/tools/handlers/query-elements-mcp-tool-handler.ts +161 -0
- package/src/tools/handlers/redo-mcp-tool-handler.ts +62 -0
- package/src/tools/handlers/save-model-mcp-tool-handler.ts +71 -0
- package/src/tools/handlers/session-info-mcp-tool-handler.spec.ts +152 -0
- package/src/tools/handlers/session-info-mcp-tool-handler.ts +97 -0
- package/src/tools/handlers/set-selection-mcp-tool-handler.spec.ts +118 -0
- package/src/tools/handlers/set-selection-mcp-tool-handler.ts +90 -0
- package/src/tools/handlers/set-view-mcp-tool-handler.ts +162 -0
- package/src/tools/handlers/undo-mcp-tool-handler.ts +61 -0
- package/src/tools/handlers/validate-diagram-mcp-tool-handler.ts +0 -0
- package/src/tools/index.ts +34 -0
- package/src/tools/tool-annotations.spec.ts +141 -0
- package/src/util/index.ts +18 -0
- package/src/util/markdown-util.ts +44 -0
- package/src/util/mcp-util.ts +25 -0
|
@@ -0,0 +1,197 @@
|
|
|
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 {
|
|
18
|
+
Action,
|
|
19
|
+
ActionDispatcher,
|
|
20
|
+
ApplyLabelEditOperation,
|
|
21
|
+
ClientId,
|
|
22
|
+
CreateNodeOperation,
|
|
23
|
+
GLabel,
|
|
24
|
+
GModelElement,
|
|
25
|
+
Logger,
|
|
26
|
+
ModelState,
|
|
27
|
+
NullLogger
|
|
28
|
+
} from '@eclipse-glsp/server';
|
|
29
|
+
import { expect } from 'chai';
|
|
30
|
+
import { Container, ContainerModule } from 'inversify';
|
|
31
|
+
import { McpIdAliasService, McpLabelProvider, McpToolResult } from '../../server';
|
|
32
|
+
import { CreateNodesInput, CreateNodesMcpToolHandler } from './create-nodes-mcp-tool-handler';
|
|
33
|
+
|
|
34
|
+
interface CreatedElementsStructured {
|
|
35
|
+
createdNodes: { id: string; elementTypeId: string; label?: string }[];
|
|
36
|
+
errors: string[];
|
|
37
|
+
warnings: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeElement(id: string, type: string, children: GModelElement[] = []): GModelElement {
|
|
41
|
+
return { id, type, children } as unknown as GModelElement;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Mutable model that drops in new elements as CreateNodeOperations are dispatched. */
|
|
45
|
+
class FakeModelState {
|
|
46
|
+
public elements = new Map<string, GModelElement>();
|
|
47
|
+
constructor(initial: GModelElement[]) {
|
|
48
|
+
initial.forEach(el => this.elements.set(el.id, el));
|
|
49
|
+
}
|
|
50
|
+
addElement(el: GModelElement): void {
|
|
51
|
+
this.elements.set(el.id, el);
|
|
52
|
+
}
|
|
53
|
+
get index(): ModelState['index'] {
|
|
54
|
+
return {
|
|
55
|
+
allIds: () => [...this.elements.keys()],
|
|
56
|
+
get: (id: string) => this.elements.get(id),
|
|
57
|
+
find: (id: string) => this.elements.get(id)
|
|
58
|
+
} as unknown as ModelState['index'];
|
|
59
|
+
}
|
|
60
|
+
get isReadonly(): boolean {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Dispatcher that materializes a new element per `CreateNodeOperation` and records the action. */
|
|
66
|
+
class CreatingDispatcher {
|
|
67
|
+
public dispatched: Action[] = [];
|
|
68
|
+
private idCounter = 0;
|
|
69
|
+
constructor(
|
|
70
|
+
private readonly model: FakeModelState,
|
|
71
|
+
private readonly labeled: Set<string>
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
async dispatch(action: Action): Promise<void> {
|
|
75
|
+
this.dispatched.push(action);
|
|
76
|
+
if (CreateNodeOperation.is(action)) {
|
|
77
|
+
const id = `${action.elementTypeId}#${++this.idCounter}`;
|
|
78
|
+
const children: GModelElement[] = this.labeled.has(action.elementTypeId)
|
|
79
|
+
? [Object.assign(new GLabel(), { id: `${id}_label`, type: 'label', children: [] })]
|
|
80
|
+
: [];
|
|
81
|
+
this.model.addElement(makeElement(id, action.elementTypeId, children));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface BuildArgs {
|
|
87
|
+
/** Element types whose newly created instance includes a child GLabel. */
|
|
88
|
+
labeled?: string[];
|
|
89
|
+
/** Element types for which CreateNodeOperation should produce no new element (silent failure simulation). */
|
|
90
|
+
failed?: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildHandler({ labeled = [], failed = [] }: BuildArgs = {}): {
|
|
94
|
+
handler: CreateNodesMcpToolHandler;
|
|
95
|
+
dispatcher: CreatingDispatcher;
|
|
96
|
+
} {
|
|
97
|
+
const model = new FakeModelState([]);
|
|
98
|
+
const failedSet = new Set(failed);
|
|
99
|
+
const labeledSet = new Set(labeled);
|
|
100
|
+
const dispatcher = new (class extends CreatingDispatcher {
|
|
101
|
+
override async dispatch(action: Action): Promise<void> {
|
|
102
|
+
if (CreateNodeOperation.is(action) && failedSet.has(action.elementTypeId)) {
|
|
103
|
+
this.dispatched.push(action);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
return super.dispatch(action);
|
|
107
|
+
}
|
|
108
|
+
})(model, labeledSet);
|
|
109
|
+
|
|
110
|
+
const container = new Container();
|
|
111
|
+
container.load(
|
|
112
|
+
new ContainerModule(bind => {
|
|
113
|
+
bind(Logger).toConstantValue(new NullLogger());
|
|
114
|
+
bind(ClientId).toConstantValue('test-session');
|
|
115
|
+
bind(ModelState).toConstantValue(model as unknown as ModelState);
|
|
116
|
+
bind(McpIdAliasService).toConstantValue({
|
|
117
|
+
lookup: (id: string) => id,
|
|
118
|
+
alias: (id: string) => id
|
|
119
|
+
} as McpIdAliasService);
|
|
120
|
+
bind(ActionDispatcher).toConstantValue(dispatcher as unknown as ActionDispatcher);
|
|
121
|
+
bind(McpLabelProvider).toConstantValue({
|
|
122
|
+
getLabel: (element: GModelElement) => element.children.find(c => c instanceof GLabel) as GLabel | undefined
|
|
123
|
+
} as McpLabelProvider);
|
|
124
|
+
bind(CreateNodesMcpToolHandler).toSelf();
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
return { handler: container.get(CreateNodesMcpToolHandler), dispatcher };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function callCreateResult(handler: CreateNodesMcpToolHandler, params: CreateNodesInput): Promise<McpToolResult> {
|
|
131
|
+
return (handler as unknown as { createResult: (p: CreateNodesInput) => Promise<McpToolResult> }).createResult(params);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe('CreateNodesMcpToolHandler', () => {
|
|
135
|
+
it('dispatches one CreateNodeOperation per node and reports the new element ids', async () => {
|
|
136
|
+
const { handler, dispatcher } = buildHandler();
|
|
137
|
+
|
|
138
|
+
const result = await callCreateResult(handler, {
|
|
139
|
+
sessionId: 's',
|
|
140
|
+
nodes: [
|
|
141
|
+
{ elementTypeId: 'task:manual', position: { x: 0, y: 0 } },
|
|
142
|
+
{ elementTypeId: 'task:manual', position: { x: 100, y: 0 } }
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const structured = result.structuredContent as unknown as CreatedElementsStructured;
|
|
147
|
+
expect(structured.createdNodes).to.have.lengthOf(2);
|
|
148
|
+
expect(structured.createdNodes[0].id).to.equal('task:manual#1');
|
|
149
|
+
expect(structured.createdNodes[1].id).to.equal('task:manual#2');
|
|
150
|
+
expect(structured.errors).to.deep.equal([]);
|
|
151
|
+
expect(structured.warnings).to.deep.equal([]);
|
|
152
|
+
expect(dispatcher.dispatched.filter(CreateNodeOperation.is)).to.have.lengthOf(2);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('dispatches ApplyLabelEditOperation when `text` is supplied and the new element has a label', async () => {
|
|
156
|
+
const { handler, dispatcher } = buildHandler({ labeled: ['task:manual'] });
|
|
157
|
+
|
|
158
|
+
await callCreateResult(handler, {
|
|
159
|
+
sessionId: 's',
|
|
160
|
+
nodes: [{ elementTypeId: 'task:manual', position: { x: 0, y: 0 }, text: 'Hello' }]
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const edits = dispatcher.dispatched.filter(ApplyLabelEditOperation.is);
|
|
164
|
+
expect(edits).to.have.lengthOf(1);
|
|
165
|
+
expect(edits[0].text).to.equal('Hello');
|
|
166
|
+
expect(edits[0].labelId).to.equal('task:manual#1_label');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('emits a warning (no ApplyLabelEditOperation) when `text` is supplied but the element type has no label', async () => {
|
|
170
|
+
const { handler, dispatcher } = buildHandler({ labeled: [] });
|
|
171
|
+
|
|
172
|
+
const result = await callCreateResult(handler, {
|
|
173
|
+
sessionId: 's',
|
|
174
|
+
nodes: [{ elementTypeId: 'decision', position: { x: 0, y: 0 }, text: 'Ignored' }]
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const structured = result.structuredContent as unknown as CreatedElementsStructured;
|
|
178
|
+
expect(structured.warnings).to.have.lengthOf(1);
|
|
179
|
+
expect(structured.warnings[0]).to.match(/no editable label/i);
|
|
180
|
+
expect(dispatcher.dispatched.filter(ApplyLabelEditOperation.is)).to.have.lengthOf(0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('records an error and skips label-edit when the CreateNodeOperation produces no new element', async () => {
|
|
184
|
+
const { handler, dispatcher } = buildHandler({ failed: ['ghost'] });
|
|
185
|
+
|
|
186
|
+
const result = await callCreateResult(handler, {
|
|
187
|
+
sessionId: 's',
|
|
188
|
+
nodes: [{ elementTypeId: 'ghost', position: { x: 0, y: 0 }, text: 'Hello' }]
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const structured = result.structuredContent as unknown as CreatedElementsStructured;
|
|
192
|
+
expect(structured.createdNodes).to.deep.equal([]);
|
|
193
|
+
expect(structured.errors).to.have.lengthOf(1);
|
|
194
|
+
expect(structured.errors[0]).to.include('ghost');
|
|
195
|
+
expect(dispatcher.dispatched.filter(ApplyLabelEditOperation.is)).to.have.lengthOf(0);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
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 { ApplyLabelEditOperation, CreateNodeOperation } from '@eclipse-glsp/server';
|
|
18
|
+
import { injectable } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import {
|
|
21
|
+
ElementIdentity,
|
|
22
|
+
ElementIdentitySchema,
|
|
23
|
+
McpDiagramScopedInputSchema,
|
|
24
|
+
McpToolResult,
|
|
25
|
+
OperationMcpDiagramToolHandler,
|
|
26
|
+
position
|
|
27
|
+
} from '../../server';
|
|
28
|
+
import { formatNoticeList } from '../../util';
|
|
29
|
+
|
|
30
|
+
/** Single node-creation entry. Strict so an LLM-typoed field surfaces as a validation error instead of being silently dropped. */
|
|
31
|
+
export const CreateNodeSpecSchema = z.strictObject({
|
|
32
|
+
elementTypeId: z
|
|
33
|
+
.string()
|
|
34
|
+
.describe('Element type ID (e.g., `task:manual`, `task:automated`). Use the `element-types` tool to discover valid IDs.'),
|
|
35
|
+
position: position.describe('Position where the node should be created (absolute diagram coordinates)'),
|
|
36
|
+
text: z.string().optional().describe('Label text to use in case the given element type allows for labels.'),
|
|
37
|
+
containerId: z.string().optional().describe('ID of the container element. If not provided, node is added to the root.'),
|
|
38
|
+
// `args` stays open (`record(...)`) — adopter-specific extension surface for per-element-type creation hints.
|
|
39
|
+
args: z.record(z.string(), z.any()).optional().describe('Additional type-specific arguments for node creation (varies by element type)')
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const CreateNodesInputSchema = McpDiagramScopedInputSchema.extend({
|
|
43
|
+
nodes: z.array(CreateNodeSpecSchema).min(1).describe('Array of nodes to create. Must include at least one node.')
|
|
44
|
+
});
|
|
45
|
+
export type CreateNodesInput = z.infer<typeof CreateNodesInputSchema>;
|
|
46
|
+
|
|
47
|
+
export const CreateNodesOutputSchema = z.object({
|
|
48
|
+
createdNodes: z
|
|
49
|
+
.array(ElementIdentitySchema)
|
|
50
|
+
.describe('Nodes successfully created, in input order. `label` is only present when the type actually accepted text.'),
|
|
51
|
+
errors: z.array(z.string()).describe('Per-input hard-failure messages — the creation could not complete.'),
|
|
52
|
+
warnings: z
|
|
53
|
+
.array(z.string())
|
|
54
|
+
.describe(
|
|
55
|
+
'Soft notices for inputs that succeeded with caveats (e.g. `text` supplied for a type whose elements have no editable label).'
|
|
56
|
+
)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
@injectable()
|
|
60
|
+
export class CreateNodesMcpToolHandler extends OperationMcpDiagramToolHandler<CreateNodesInput> {
|
|
61
|
+
static readonly NAME = 'create-nodes';
|
|
62
|
+
readonly name = CreateNodesMcpToolHandler.NAME;
|
|
63
|
+
override readonly title = 'Create Diagram Nodes';
|
|
64
|
+
readonly description =
|
|
65
|
+
'Create one or multiple new nodes in the diagram at the specified positions. ' +
|
|
66
|
+
'When creating new nodes absolutely consider the visual alignment with existing nodes — call ' +
|
|
67
|
+
'`query-elements` (or `count-elements` for a quick overview) first to avoid overlap. ' +
|
|
68
|
+
'Each node descriptor needs an `elementTypeId` (from `element-types`) and a `position`; ' +
|
|
69
|
+
'`text`, `containerId`, and per-type `args` are optional. ' +
|
|
70
|
+
'This operation modifies the diagram state and requires user approval.';
|
|
71
|
+
readonly inputSchema = CreateNodesInputSchema;
|
|
72
|
+
override readonly outputSchema = CreateNodesOutputSchema;
|
|
73
|
+
|
|
74
|
+
protected async createResult({ nodes }: CreateNodesInput): Promise<McpToolResult> {
|
|
75
|
+
let beforeIds = this.modelState.index.allIds();
|
|
76
|
+
|
|
77
|
+
const errors: string[] = [];
|
|
78
|
+
const warnings: string[] = [];
|
|
79
|
+
const createdNodes: ElementIdentity[] = [];
|
|
80
|
+
let dispatchedOperations = 0;
|
|
81
|
+
// Sequential — each iteration must isolate its own creation in the post-dispatch diff.
|
|
82
|
+
for (const node of nodes) {
|
|
83
|
+
const { elementTypeId, position, text, args } = node;
|
|
84
|
+
const containerId = node.containerId ? this.aliasService.lookup(node.containerId) : undefined;
|
|
85
|
+
|
|
86
|
+
// Surface as `position` (matches element properties) rather than core's `location` for AI-facing API consistency.
|
|
87
|
+
const operation = CreateNodeOperation.create(elementTypeId, { location: position, containerId, args });
|
|
88
|
+
await this.actionDispatcher.dispatch(operation);
|
|
89
|
+
dispatchedOperations++;
|
|
90
|
+
|
|
91
|
+
const afterIds = this.modelState.index.allIds();
|
|
92
|
+
const newIds = afterIds.filter(id => !beforeIds.includes(id));
|
|
93
|
+
const newElements = newIds.map(id => this.modelState.index.find(id)).filter(element => element?.type === elementTypeId);
|
|
94
|
+
const newElement = newElements[0];
|
|
95
|
+
if (newElements.length > 1) {
|
|
96
|
+
this.logger.warn('More than 1 new element created');
|
|
97
|
+
}
|
|
98
|
+
beforeIds = afterIds;
|
|
99
|
+
|
|
100
|
+
// Operations don't surface failure directly — infer from absence of a new id of the requested type.
|
|
101
|
+
if (!newElement) {
|
|
102
|
+
errors.push(`Node creation likely failed because no new element ID was found for input: ${JSON.stringify(node)}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (text) {
|
|
107
|
+
const labelId = this.labelProvider.getLabel(newElement)?.id;
|
|
108
|
+
if (labelId) {
|
|
109
|
+
await this.actionDispatcher.dispatch(ApplyLabelEditOperation.create({ labelId, text }));
|
|
110
|
+
dispatchedOperations++;
|
|
111
|
+
} else {
|
|
112
|
+
warnings.push(`Ignored \`text\` for '${elementTypeId}' — this element type has no editable label.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
createdNodes.push(this.describeResolvedElement(newElement));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const successListStr = createdNodes
|
|
120
|
+
.map(({ id, elementTypeId, label }) => `- ${label ? `'${label}' ` : ''}${elementTypeId} (#${id})`)
|
|
121
|
+
.join('\n');
|
|
122
|
+
// Per-input errors / warnings are surfaced in `errors` / `warnings`; the call itself still
|
|
123
|
+
// succeeds — rolling back partial creates would require operation-level transactions.
|
|
124
|
+
return this.success(
|
|
125
|
+
`Successfully created ${createdNodes.length} node(s) (in ${dispatchedOperations} commands):\n${successListStr}` +
|
|
126
|
+
formatNoticeList('errors', errors) +
|
|
127
|
+
formatNoticeList('warnings', warnings),
|
|
128
|
+
{ createdNodes, errors, warnings }
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -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 { DeleteElementOperation } from '@eclipse-glsp/server';
|
|
18
|
+
import { injectable } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import {
|
|
21
|
+
ElementIdentitySchema,
|
|
22
|
+
McpDiagramScopedInputSchema,
|
|
23
|
+
McpToolResult,
|
|
24
|
+
OperationMcpDiagramToolHandler,
|
|
25
|
+
elementIds
|
|
26
|
+
} from '../../server';
|
|
27
|
+
|
|
28
|
+
export const DeleteElementsInputSchema = McpDiagramScopedInputSchema.extend({ elementIds });
|
|
29
|
+
export type DeleteElementsInput = z.infer<typeof DeleteElementsInputSchema>;
|
|
30
|
+
|
|
31
|
+
export const DeleteElementsOutputSchema = z.object({
|
|
32
|
+
deletedElements: z
|
|
33
|
+
.array(ElementIdentitySchema)
|
|
34
|
+
.describe(
|
|
35
|
+
'Identity of each element the LLM requested for deletion (captured before dispatch). Dependents auto-deleted by GLSP are not enumerated here.'
|
|
36
|
+
),
|
|
37
|
+
deletedCount: z
|
|
38
|
+
.number()
|
|
39
|
+
.int()
|
|
40
|
+
.describe(
|
|
41
|
+
'Total number of elements removed from the model. Higher than `deletedElements.length` when dependents (e.g. edges) are auto-deleted.'
|
|
42
|
+
)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
@injectable()
|
|
46
|
+
export class DeleteElementsMcpToolHandler extends OperationMcpDiagramToolHandler<DeleteElementsInput> {
|
|
47
|
+
static readonly NAME = 'delete-elements';
|
|
48
|
+
readonly name = DeleteElementsMcpToolHandler.NAME;
|
|
49
|
+
override readonly title = 'Delete Diagram Elements';
|
|
50
|
+
readonly description =
|
|
51
|
+
'Delete one or more elements (nodes or edges) from the diagram. ' +
|
|
52
|
+
'This operation modifies the diagram state and requires user approval. ' +
|
|
53
|
+
'Automatically handles dependent elements (e.g., deleting a node also deletes connected edges).';
|
|
54
|
+
readonly inputSchema = DeleteElementsInputSchema;
|
|
55
|
+
override readonly outputSchema = DeleteElementsOutputSchema;
|
|
56
|
+
/** Deletion is the canonical destructive update — flip the operation-base default. */
|
|
57
|
+
override readonly destructiveHint = true;
|
|
58
|
+
|
|
59
|
+
protected async createResult({ elementIds }: DeleteElementsInput): Promise<McpToolResult> {
|
|
60
|
+
const realIds = this.resolveExistingIds(elementIds);
|
|
61
|
+
// Capture identities BEFORE dispatch — once deleted, `describeElement` returns undefined.
|
|
62
|
+
const deletedElements = realIds
|
|
63
|
+
.map(realId => this.describeElement(realId))
|
|
64
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== undefined);
|
|
65
|
+
const beforeCount = this.modelState.index.allIds().length;
|
|
66
|
+
await this.actionDispatcher.dispatch(DeleteElementOperation.create(realIds));
|
|
67
|
+
const deletedCount = beforeCount - this.modelState.index.allIds().length;
|
|
68
|
+
return this.success(`Successfully deleted ${deletedCount} element(s) (including dependents)`, {
|
|
69
|
+
deletedElements,
|
|
70
|
+
deletedCount
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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/server';
|
|
18
|
+
import { inject, injectable } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import { McpModelSerializer } from '../../resources/services/mcp-model-serializer';
|
|
21
|
+
import { McpDiagramScopedInputSchema, AbstractMcpDiagramToolHandler, McpToolResult } from '../../server';
|
|
22
|
+
|
|
23
|
+
export const DiagramModelInputSchema = McpDiagramScopedInputSchema;
|
|
24
|
+
export type DiagramModelInput = z.infer<typeof DiagramModelInputSchema>;
|
|
25
|
+
|
|
26
|
+
/** `.loose()` lets adopter element types attach extra fields (position, size, …) without widening the schema each time. */
|
|
27
|
+
export const DiagramModelElementSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
id: z.string(),
|
|
30
|
+
type: z.string(),
|
|
31
|
+
parentId: z.string().optional()
|
|
32
|
+
})
|
|
33
|
+
.loose();
|
|
34
|
+
|
|
35
|
+
export const DiagramModelOutputSchema = z.object({
|
|
36
|
+
sessionId: z.string(),
|
|
37
|
+
elements: z.array(DiagramModelElementSchema)
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
@injectable()
|
|
41
|
+
export class DiagramModelMcpToolHandler extends AbstractMcpDiagramToolHandler<DiagramModelInput> {
|
|
42
|
+
static readonly NAME = 'diagram-model';
|
|
43
|
+
readonly name = DiagramModelMcpToolHandler.NAME;
|
|
44
|
+
override readonly title = 'Diagram Model Structure';
|
|
45
|
+
readonly description =
|
|
46
|
+
'Get the complete GLSP model for a session as a markdown structure. ' +
|
|
47
|
+
'Includes all nodes, edges, and their relevant properties. ' +
|
|
48
|
+
'For large diagrams, prefer `query-elements` (filtered listing) or `count-elements` (size summary) ' +
|
|
49
|
+
'before falling back to this full dump.';
|
|
50
|
+
readonly inputSchema = DiagramModelInputSchema;
|
|
51
|
+
override readonly outputSchema = DiagramModelOutputSchema;
|
|
52
|
+
|
|
53
|
+
@inject(McpModelSerializer) protected serializer: McpModelSerializer;
|
|
54
|
+
|
|
55
|
+
protected createResult({ sessionId }: DiagramModelInput): McpToolResult {
|
|
56
|
+
const root = this.modelState.root;
|
|
57
|
+
const structured = this.serializer.serializeStructured(root);
|
|
58
|
+
const count = Array.isArray(structured.elements) ? structured.elements.length : 0;
|
|
59
|
+
return this.success(this.summarizeModel(root, count), { sessionId, ...structured });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Builds the LLM-facing summary line. Override to customize per-adopter wording. */
|
|
63
|
+
protected summarizeModel(root: GModelElement, elementCount: number): string {
|
|
64
|
+
return `Diagram '${this.aliasService.alias(root.id)}' (${root.type}): ${elementCount} element(s) total. Full structure in structuredContent.`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
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 {
|
|
18
|
+
ClientSessionInitializer,
|
|
19
|
+
ClientSessionManager,
|
|
20
|
+
DiagramModules,
|
|
21
|
+
InjectionContainer,
|
|
22
|
+
createClientSessionModule
|
|
23
|
+
} from '@eclipse-glsp/server';
|
|
24
|
+
import { Container, ContainerModule, inject, injectable } from 'inversify';
|
|
25
|
+
import * as z from 'zod/v4';
|
|
26
|
+
import { AbstractMcpToolHandler, McpToolError, McpToolResult } from '../../server';
|
|
27
|
+
import { ElementTypes, ElementTypesProvider } from '../../resources/services/element-types-provider';
|
|
28
|
+
|
|
29
|
+
export const ElementTypesInputSchema = z.object({
|
|
30
|
+
sessionId: z.string().optional().describe('Open GLSP session id; diagram type is derived from it. Provide this OR `diagramType`.'),
|
|
31
|
+
diagramType: z.string().optional().describe('Diagram type to query. Provide this OR `sessionId`; useful when no session is open yet.')
|
|
32
|
+
});
|
|
33
|
+
export type ElementTypesInput = z.infer<typeof ElementTypesInputSchema>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `id` + `label` are required (the default registry-scrape provides both); `description` and
|
|
37
|
+
* `acceptsText` are optional adopter-richer fields. `.loose()` lets adopter providers attach
|
|
38
|
+
* arbitrary extra fields without us having to widen the schema each time.
|
|
39
|
+
*/
|
|
40
|
+
export const ElementTypeEntrySchema = z
|
|
41
|
+
.object({
|
|
42
|
+
id: z.string().describe('Element type id used by `create-*` tools (e.g. `task:manual`).'),
|
|
43
|
+
label: z.string().describe('Human-readable display name for the element TYPE (e.g. `Manual Task`).'),
|
|
44
|
+
description: z.string().optional().describe('Adopter-supplied human-readable description.'),
|
|
45
|
+
acceptsText: z
|
|
46
|
+
.boolean()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Whether `create-*` / `modify-*` tools should pass a `text` arg for elements of this type. Absent ⇒ unknown.')
|
|
49
|
+
})
|
|
50
|
+
.loose();
|
|
51
|
+
|
|
52
|
+
export const ElementTypesOutputSchema = z.object({
|
|
53
|
+
diagramType: z.string(),
|
|
54
|
+
nodeTypes: z.array(ElementTypeEntrySchema),
|
|
55
|
+
edgeTypes: z.array(ElementTypeEntrySchema)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Server-scope so the LLM can discover types before opening a session. Lazy-harvests the
|
|
60
|
+
* per-diagram-type {@link ElementTypesProvider} via a temporary child container (same pattern
|
|
61
|
+
* as core's `DefaultGlobalActionProvider`) and caches the result for the server's lifetime —
|
|
62
|
+
* element-type sets are static at runtime, so we pay the harvest cost once per type.
|
|
63
|
+
*
|
|
64
|
+
* Adopters with statically-known type info bind their own {@link ElementTypesProvider} via
|
|
65
|
+
* `bindElementTypesProvider()`; the harvest picks it up without further overrides.
|
|
66
|
+
*/
|
|
67
|
+
@injectable()
|
|
68
|
+
export class ElementTypesMcpToolHandler extends AbstractMcpToolHandler<ElementTypesInput> {
|
|
69
|
+
@inject(DiagramModules) protected diagramModules: Map<string, ContainerModule[]>;
|
|
70
|
+
@inject(InjectionContainer) protected serverContainer: Container;
|
|
71
|
+
@inject(ClientSessionManager) protected clientSessionManager: ClientSessionManager;
|
|
72
|
+
|
|
73
|
+
static readonly NAME = 'element-types';
|
|
74
|
+
readonly name = ElementTypesMcpToolHandler.NAME;
|
|
75
|
+
override readonly title = 'Creatable Element Types';
|
|
76
|
+
readonly description =
|
|
77
|
+
'List all element types (nodes and edges) that can be created for a specific diagram type. ' +
|
|
78
|
+
'Use this to discover valid `elementTypeId` values for the create-nodes / create-edges tools. ' +
|
|
79
|
+
'Pass `sessionId` for the diagram you are working in (recommended), or `diagramType` directly ' +
|
|
80
|
+
'when no session of that type is open yet (e.g. when creating a brand-new diagram).';
|
|
81
|
+
readonly inputSchema = ElementTypesInputSchema;
|
|
82
|
+
override readonly outputSchema = ElementTypesOutputSchema;
|
|
83
|
+
|
|
84
|
+
protected readonly cache = new Map<string, ElementTypes>();
|
|
85
|
+
|
|
86
|
+
protected createResult({ sessionId, diagramType }: ElementTypesInput): McpToolResult {
|
|
87
|
+
const resolved = diagramType ?? this.resolveDiagramType(sessionId);
|
|
88
|
+
const { nodeTypes, edgeTypes } = this.getElementTypes(resolved);
|
|
89
|
+
return this.success(this.summarizeElementTypes(resolved, nodeTypes, edgeTypes), {
|
|
90
|
+
diagramType: resolved,
|
|
91
|
+
nodeTypes,
|
|
92
|
+
edgeTypes
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Builds the LLM-facing summary. Override to customize per-adopter wording. */
|
|
97
|
+
protected summarizeElementTypes(
|
|
98
|
+
diagramType: string,
|
|
99
|
+
nodeTypes: ElementTypes['nodeTypes'],
|
|
100
|
+
edgeTypes: ElementTypes['edgeTypes']
|
|
101
|
+
): string {
|
|
102
|
+
return (
|
|
103
|
+
`Diagram type '${diagramType}': ${nodeTypes.length} node type(s), ${edgeTypes.length} edge type(s); ` +
|
|
104
|
+
`full details in structuredContent.\n` +
|
|
105
|
+
`- Node types: ${nodeTypes.map(entry => entry.id).join(', ') || '(none)'}\n` +
|
|
106
|
+
`- Edge types: ${edgeTypes.map(entry => entry.id).join(', ') || '(none)'}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected resolveDiagramType(sessionId: string | undefined): string {
|
|
111
|
+
if (!sessionId) {
|
|
112
|
+
throw new McpToolError('Either `diagramType` or `sessionId` must be provided.');
|
|
113
|
+
}
|
|
114
|
+
const session = this.clientSessionManager.getSession(sessionId);
|
|
115
|
+
if (!session) {
|
|
116
|
+
throw new McpToolError(`Unknown sessionId: ${sessionId}`);
|
|
117
|
+
}
|
|
118
|
+
return session.diagramType;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected getElementTypes(diagramType: string): ElementTypes {
|
|
122
|
+
let types = this.cache.get(diagramType);
|
|
123
|
+
if (!types) {
|
|
124
|
+
types = this.harvestElementTypes(diagramType);
|
|
125
|
+
this.cache.set(diagramType, types);
|
|
126
|
+
}
|
|
127
|
+
return types;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Runs session-open initializers on a throwaway container so registries (e.g. `OperationHandlerRegistry`) are populated for the provider to scrape. */
|
|
131
|
+
protected harvestElementTypes(diagramType: string): ElementTypes {
|
|
132
|
+
const modules = this.diagramModules.get(diagramType);
|
|
133
|
+
if (!modules) {
|
|
134
|
+
throw new McpToolError(`Unknown diagram type: ${diagramType}`);
|
|
135
|
+
}
|
|
136
|
+
const tempContainer = this.serverContainer.createChild();
|
|
137
|
+
try {
|
|
138
|
+
const placeholderSessionModule = createClientSessionModule({
|
|
139
|
+
clientId: 'mcp-element-types-temp',
|
|
140
|
+
glspClient: { process: () => {} },
|
|
141
|
+
clientActionKinds: []
|
|
142
|
+
});
|
|
143
|
+
tempContainer.load(...modules, placeholderSessionModule);
|
|
144
|
+
const initializers = tempContainer.getAll<ClientSessionInitializer>(ClientSessionInitializer);
|
|
145
|
+
initializers.forEach(initializer => initializer.initialize());
|
|
146
|
+
return tempContainer.get<ElementTypesProvider>(ElementTypesProvider).get();
|
|
147
|
+
} finally {
|
|
148
|
+
tempContainer.unbindAll();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { ActionDispatcher, GetSelectionAction } from '@eclipse-glsp/server';
|
|
18
|
+
import { inject, injectable } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import { McpDiagramScopedInputSchema, AbstractMcpDiagramToolHandler, McpToolResult, requestActionOrFail } from '../../server';
|
|
21
|
+
|
|
22
|
+
export const GetSelectionInputSchema = McpDiagramScopedInputSchema;
|
|
23
|
+
export type GetSelectionInput = z.infer<typeof GetSelectionInputSchema>;
|
|
24
|
+
|
|
25
|
+
export const GetSelectionOutputSchema = z.object({
|
|
26
|
+
selectedElementIds: z.array(z.string()).describe('Aliased ids of the elements currently selected on the client.')
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** Round-trips a sprotty {@link GetSelectionAction} via `ActionDispatcher.requestUntil` and awaits the matching `SelectionResult`. */
|
|
30
|
+
@injectable()
|
|
31
|
+
export class GetSelectionMcpToolHandler extends AbstractMcpDiagramToolHandler<GetSelectionInput> {
|
|
32
|
+
/** Timeout (in ms) for awaiting the selection response from the client. Override via subclass + rebind. */
|
|
33
|
+
protected readonly timeoutMs: number = 5000;
|
|
34
|
+
|
|
35
|
+
static readonly NAME = 'get-selection';
|
|
36
|
+
readonly name = GetSelectionMcpToolHandler.NAME;
|
|
37
|
+
override readonly title = 'Get Selected Diagram Elements';
|
|
38
|
+
readonly description =
|
|
39
|
+
'Get the element IDs of all elements currently selected in the user-facing diagram UI. ' +
|
|
40
|
+
'Use this only when the user explicitly references their selection ("the selected node", "what I have highlighted"); ' +
|
|
41
|
+
'do not call it speculatively. Pairs with `set-selection` for write-side selection control. ' +
|
|
42
|
+
'Returns an empty list when nothing is selected.';
|
|
43
|
+
readonly inputSchema = GetSelectionInputSchema;
|
|
44
|
+
override readonly outputSchema = GetSelectionOutputSchema;
|
|
45
|
+
|
|
46
|
+
@inject(ActionDispatcher) protected actionDispatcher: ActionDispatcher;
|
|
47
|
+
|
|
48
|
+
protected async createResult(_params: GetSelectionInput): Promise<McpToolResult> {
|
|
49
|
+
const response = await requestActionOrFail(this.actionDispatcher, GetSelectionAction.create(), this.timeoutMs, this.name);
|
|
50
|
+
const selectedElementIds = this.encodeIds(response.selectedElementsIDs);
|
|
51
|
+
const selectedIdsStr = selectedElementIds.map(id => `- ${id}`).join('\n');
|
|
52
|
+
return this.success(`Following element IDs are selected:\n${selectedIdsStr}`, { selectedElementIds });
|
|
53
|
+
}
|
|
54
|
+
}
|