@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,56 @@
|
|
|
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 { LayoutEngine, LayoutOperation } from '@eclipse-glsp/server';
|
|
18
|
+
import { inject, injectable, optional } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import { McpDiagramScopedInputSchema, McpToolResult, OperationMcpDiagramToolHandler } from '../../server';
|
|
21
|
+
|
|
22
|
+
export const LayoutInputSchema = McpDiagramScopedInputSchema;
|
|
23
|
+
export type LayoutInput = z.infer<typeof LayoutInputSchema>;
|
|
24
|
+
|
|
25
|
+
export const LayoutOutputSchema = z.object({
|
|
26
|
+
applied: z.boolean().describe('Always true on success — surfaced for parity with other operations.')
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** Not registered by default: requires an adopter-supplied `LayoutEngine` to bind, which only some GLSP servers ship. */
|
|
30
|
+
@injectable()
|
|
31
|
+
export class LayoutMcpToolHandler extends OperationMcpDiagramToolHandler<LayoutInput> {
|
|
32
|
+
static readonly NAME = 'layout';
|
|
33
|
+
readonly name = LayoutMcpToolHandler.NAME;
|
|
34
|
+
override readonly title = 'Auto-Layout Diagram';
|
|
35
|
+
readonly description =
|
|
36
|
+
"Trigger automatic layout computation for the given session's diagram, repositioning all nodes and " +
|
|
37
|
+
'rerouting all edges according to the configured layout engine. ' +
|
|
38
|
+
'Use this only when the user explicitly asks for "automatic layout" or similar — it overwrites every ' +
|
|
39
|
+
'manual position in the diagram and is generally a destructive change for hand-tuned layouts. ' +
|
|
40
|
+
'For targeted positional edits prefer `modify-nodes` / `modify-edges`. ' +
|
|
41
|
+
'Adopters who do not bind a `LayoutEngine` will not see this tool registered.';
|
|
42
|
+
readonly inputSchema = LayoutInputSchema;
|
|
43
|
+
override readonly outputSchema = LayoutOutputSchema;
|
|
44
|
+
|
|
45
|
+
@inject(LayoutEngine) @optional() protected layoutEngine?: LayoutEngine;
|
|
46
|
+
|
|
47
|
+
/** Skip-bind when no `LayoutEngine` is bound — every dispatch would otherwise no-op. */
|
|
48
|
+
override canRegister(): boolean {
|
|
49
|
+
return this.layoutEngine !== undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected async createResult(_params: LayoutInput): Promise<McpToolResult> {
|
|
53
|
+
await this.actionDispatcher.dispatch(LayoutOperation.create());
|
|
54
|
+
return this.success('Automatic layout applied', { applied: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
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 { ChangeRoutingPointsOperation, GEdge, ReconnectEdgeOperation } from '@eclipse-glsp/server';
|
|
18
|
+
import { injectable } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import {
|
|
21
|
+
ElementIdentitySchema,
|
|
22
|
+
McpDiagramScopedInputSchema,
|
|
23
|
+
McpToolError,
|
|
24
|
+
McpToolResult,
|
|
25
|
+
OperationMcpDiagramToolHandler,
|
|
26
|
+
elementId as elementIdSchema,
|
|
27
|
+
position
|
|
28
|
+
} from '../../server';
|
|
29
|
+
import { formatNoticeList } from '../../util';
|
|
30
|
+
|
|
31
|
+
/** Single edge-modification entry. Strict so an LLM-typoed field surfaces as a validation error instead of being silently dropped. */
|
|
32
|
+
export const ModifyEdgeSpecSchema = z.strictObject({
|
|
33
|
+
elementId: elementIdSchema,
|
|
34
|
+
sourceElementId: z.string().optional().describe('ID of the source element (must exist in the diagram)'),
|
|
35
|
+
targetElementId: z.string().optional().describe('ID of the target element (must exist in the diagram)'),
|
|
36
|
+
routingPoints: z
|
|
37
|
+
.array(position)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe(
|
|
40
|
+
'Optional array of routing point coordinates that allow for a complex edge path. ' +
|
|
41
|
+
'Using an empty array removes all routing points.'
|
|
42
|
+
)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const ModifyEdgesInputSchema = McpDiagramScopedInputSchema.extend({
|
|
46
|
+
edges: z
|
|
47
|
+
.array(ModifyEdgeSpecSchema)
|
|
48
|
+
.min(1)
|
|
49
|
+
.describe('Array of edge changes — each entry needs `elementId` plus the fields to update. Must include at least one change.')
|
|
50
|
+
});
|
|
51
|
+
export type ModifyEdgesInput = z.infer<typeof ModifyEdgesInputSchema>;
|
|
52
|
+
|
|
53
|
+
export const ModifyEdgesOutputSchema = z.object({
|
|
54
|
+
modifiedEdges: z.array(ElementIdentitySchema).describe('Identity of each edge whose change request was dispatched.'),
|
|
55
|
+
dispatchedCommands: z.number().int().describe('Number of underlying GLSP operations dispatched.'),
|
|
56
|
+
errors: z.array(z.string()).describe('Per-input failure messages; absent or empty when every input succeeded.')
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
@injectable()
|
|
60
|
+
export class ModifyEdgesMcpToolHandler extends OperationMcpDiagramToolHandler<ModifyEdgesInput> {
|
|
61
|
+
static readonly NAME = 'modify-edges';
|
|
62
|
+
readonly name = ModifyEdgesMcpToolHandler.NAME;
|
|
63
|
+
override readonly title = 'Modify Diagram Edges';
|
|
64
|
+
readonly description =
|
|
65
|
+
'Modify one or more existing edges by reconnecting their source/target endpoints or rewriting their routing points. ' +
|
|
66
|
+
'Reconnection (provide both `sourceElementId` and `targetElementId`) and routing-point edits are mutually exclusive ' +
|
|
67
|
+
'per change entry — a reconnect recomputes the path from scratch and ignores `routingPoints`. ' +
|
|
68
|
+
'Pass an empty `routingPoints` array to remove all routing points (snap to a straight line). ' +
|
|
69
|
+
'This operation modifies the diagram state and requires user approval. ' +
|
|
70
|
+
'For nodes (position/size/text), use `modify-nodes` instead.';
|
|
71
|
+
readonly inputSchema = ModifyEdgesInputSchema;
|
|
72
|
+
override readonly outputSchema = ModifyEdgesOutputSchema;
|
|
73
|
+
|
|
74
|
+
protected async createResult({ edges }: ModifyEdgesInput): Promise<McpToolResult> {
|
|
75
|
+
const elements = this.lookupElements(edges, change => change.elementId);
|
|
76
|
+
|
|
77
|
+
// Type-validate so non-edge ids surface a clear error instead of "model element not found"
|
|
78
|
+
// from the dispatched operation handler. Aliases are sequential across all element kinds,
|
|
79
|
+
// so an LLM passing an arbitrary id may hit a node here.
|
|
80
|
+
const wrongType = elements
|
|
81
|
+
.filter(([, element]) => !(element instanceof GEdge))
|
|
82
|
+
.map(([change, element]) => `'${change.elementId}' (type '${element.type}')`);
|
|
83
|
+
if (wrongType.length) {
|
|
84
|
+
throw new McpToolError(`modify-edges accepts edges only — got: ${wrongType.join(', ')}. Use modify-nodes for nodes.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Dispatch in parallel via `allSettled` so one failed edge surfaces in `errors`
|
|
88
|
+
// instead of rejecting the whole call and losing the other outcomes.
|
|
89
|
+
const dispatched: Array<{ promise: Promise<void>; realId: string; inputId: string }> = [];
|
|
90
|
+
const errors: string[] = [];
|
|
91
|
+
const modifiedRealIds = new Set<string>();
|
|
92
|
+
elements.forEach(([change]) => {
|
|
93
|
+
const { routingPoints } = change;
|
|
94
|
+
const realId = this.aliasService.lookup(change.elementId);
|
|
95
|
+
const sourceElementId = change.sourceElementId ? this.aliasService.lookup(change.sourceElementId) : undefined;
|
|
96
|
+
const targetElementId = change.targetElementId ? this.aliasService.lookup(change.targetElementId) : undefined;
|
|
97
|
+
|
|
98
|
+
if ((sourceElementId && !targetElementId) || (!sourceElementId && targetElementId)) {
|
|
99
|
+
errors.push(`Both source and target ID are required for input: ${JSON.stringify(change)}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (sourceElementId && targetElementId) {
|
|
104
|
+
const source = this.modelState.index.find(sourceElementId);
|
|
105
|
+
if (!source) {
|
|
106
|
+
errors.push(`Source element not found: ${sourceElementId}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const target = this.modelState.index.find(targetElementId);
|
|
110
|
+
if (!target) {
|
|
111
|
+
errors.push(`Target element not found: ${targetElementId}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const operation = ReconnectEdgeOperation.create({ edgeElementId: realId, sourceElementId, targetElementId });
|
|
116
|
+
dispatched.push({ promise: this.actionDispatcher.dispatch(operation), realId, inputId: change.elementId });
|
|
117
|
+
modifiedRealIds.add(realId);
|
|
118
|
+
// Routing-point changes are skipped during a reconnect — the edge's path is recomputed from scratch.
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (routingPoints) {
|
|
123
|
+
const operation = ChangeRoutingPointsOperation.create([{ elementId: realId, newRoutingPoints: routingPoints }]);
|
|
124
|
+
dispatched.push({ promise: this.actionDispatcher.dispatch(operation), realId, inputId: change.elementId });
|
|
125
|
+
modifiedRealIds.add(realId);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const results = await Promise.allSettled(dispatched.map(entry => entry.promise));
|
|
130
|
+
results.forEach((result, i) => {
|
|
131
|
+
if (result.status === 'rejected') {
|
|
132
|
+
const { realId, inputId } = dispatched[i];
|
|
133
|
+
const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
134
|
+
errors.push(`Failed to modify edge '${inputId}': ${reason}`);
|
|
135
|
+
// The dispatch failed, so this id was *not* modified — drop it from the success list.
|
|
136
|
+
modifiedRealIds.delete(realId);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const modifiedEdges = [...modifiedRealIds]
|
|
141
|
+
.map(realId => this.describeElement(realId))
|
|
142
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== undefined);
|
|
143
|
+
return this.success(
|
|
144
|
+
`Successfully modified ${edges.length - errors.length} edge(s) (in ${dispatched.length} commands)${formatNoticeList('errors', errors)}`,
|
|
145
|
+
{ modifiedEdges, dispatchedCommands: dispatched.length, errors }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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, ChangeBoundsOperation, GEdge, GShapeElement } from '@eclipse-glsp/server';
|
|
18
|
+
import { injectable } from 'inversify';
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import {
|
|
21
|
+
ElementIdentitySchema,
|
|
22
|
+
McpDiagramScopedInputSchema,
|
|
23
|
+
McpToolError,
|
|
24
|
+
McpToolResult,
|
|
25
|
+
OperationMcpDiagramToolHandler,
|
|
26
|
+
elementId,
|
|
27
|
+
position
|
|
28
|
+
} from '../../server';
|
|
29
|
+
import { formatNoticeList } from '../../util';
|
|
30
|
+
|
|
31
|
+
/** Strict — any unknown field on the size object surfaces as a validation error. */
|
|
32
|
+
export const NodeSizeSchema = z.strictObject({
|
|
33
|
+
width: z.number().positive().describe('Width of the element in diagram space (must be > 0).'),
|
|
34
|
+
height: z.number().positive().describe('Height of the element in diagram space (must be > 0).')
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** Single node-modification entry. Strict so an LLM-typoed field surfaces as a validation error instead of being silently dropped. */
|
|
38
|
+
export const ModifyNodeSpecSchema = z.strictObject({
|
|
39
|
+
elementId,
|
|
40
|
+
position: position.optional().describe('Position where the node should be moved to (absolute diagram coordinates)'),
|
|
41
|
+
size: NodeSizeSchema.optional().describe('New size of the node'),
|
|
42
|
+
text: z.string().optional().describe("Label text to use instead (given that the element's type allows for labels).")
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const ModifyNodesInputSchema = McpDiagramScopedInputSchema.extend({
|
|
46
|
+
nodes: z
|
|
47
|
+
.array(ModifyNodeSpecSchema)
|
|
48
|
+
.min(1)
|
|
49
|
+
.describe('Array of node changes — each entry needs `elementId` plus the fields to update. Must include at least one change.')
|
|
50
|
+
});
|
|
51
|
+
export type ModifyNodesInput = z.infer<typeof ModifyNodesInputSchema>;
|
|
52
|
+
|
|
53
|
+
export const ModifyNodesOutputSchema = z.object({
|
|
54
|
+
modifiedNodes: z
|
|
55
|
+
.array(ElementIdentitySchema)
|
|
56
|
+
.describe('Identity of each node whose change request was dispatched (post-modification labels).'),
|
|
57
|
+
dispatchedCommands: z.number().int().describe('Number of underlying GLSP operations dispatched (a single change may yield several).'),
|
|
58
|
+
warnings: z
|
|
59
|
+
.array(z.string())
|
|
60
|
+
.describe(
|
|
61
|
+
'Soft notices for inputs whose change applied with caveats (e.g. `text` supplied for a node whose type has no editable label).'
|
|
62
|
+
)
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
@injectable()
|
|
66
|
+
export class ModifyNodesMcpToolHandler extends OperationMcpDiagramToolHandler<ModifyNodesInput> {
|
|
67
|
+
static readonly NAME = 'modify-nodes';
|
|
68
|
+
readonly name = ModifyNodesMcpToolHandler.NAME;
|
|
69
|
+
override readonly title = 'Modify Diagram Nodes';
|
|
70
|
+
readonly description =
|
|
71
|
+
'Modify one or more existing nodes by changing their position, size, and/or label text. ' +
|
|
72
|
+
'When modifying position or size, absolutely consider the visual alignment with other nodes — ' +
|
|
73
|
+
'use `query-elements` (inspect mode) first to understand the layout. ' +
|
|
74
|
+
'Each change entry can include any combination of `position`, `size`, and `text`; omitted fields keep their current value. ' +
|
|
75
|
+
'This operation modifies the diagram state and requires user approval. ' +
|
|
76
|
+
'For edges (reconnect / routing-points), use `modify-edges` instead.';
|
|
77
|
+
readonly inputSchema = ModifyNodesInputSchema;
|
|
78
|
+
override readonly outputSchema = ModifyNodesOutputSchema;
|
|
79
|
+
|
|
80
|
+
protected async createResult({ nodes }: ModifyNodesInput): Promise<McpToolResult> {
|
|
81
|
+
const elements = this.lookupElements(nodes, change => change.elementId);
|
|
82
|
+
|
|
83
|
+
// Reject edge ids — they have no `position`/`size` semantics and would fail downstream
|
|
84
|
+
// with a misleading "model element not found" error from the operation handler. Aliases
|
|
85
|
+
// are sequential across all element kinds, so an LLM passing an arbitrary id may hit an edge.
|
|
86
|
+
const wrongType = elements.filter(([, element]) => element instanceof GEdge).map(([change]) => `'${change.elementId}'`);
|
|
87
|
+
if (wrongType.length) {
|
|
88
|
+
throw new McpToolError(`modify-nodes does not accept edges — got: ${wrongType.join(', ')}. Use modify-edges for edges.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Reject any other non-shape kinds (labels, compartments, ports, custom kinds) — they
|
|
92
|
+
// reach the index too and would silently produce no-op or undefined-bounds operations.
|
|
93
|
+
const nonShape = elements.filter(([, element]) => !(element instanceof GShapeElement)).map(([change]) => `'${change.elementId}'`);
|
|
94
|
+
if (nonShape.length) {
|
|
95
|
+
throw new McpToolError(
|
|
96
|
+
`modify-nodes only accepts shape elements — got: ${nonShape.join(', ')}. ` +
|
|
97
|
+
'Use `query-elements` (inspect mode) to find shape ids, or pick the parent shape.'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Modifications are independent of each other — dispatch in parallel.
|
|
102
|
+
const promises: Promise<void>[] = [];
|
|
103
|
+
const warnings: string[] = [];
|
|
104
|
+
elements.forEach(([change, element]) => {
|
|
105
|
+
// Guaranteed non-null and shape-typed by the missing-elements / nonShape checks above.
|
|
106
|
+
const resolved = element as GShapeElement;
|
|
107
|
+
const { size, position, text } = change;
|
|
108
|
+
const realId = this.aliasService.lookup(change.elementId);
|
|
109
|
+
|
|
110
|
+
if (size || position) {
|
|
111
|
+
const newSize = size ?? resolved.size;
|
|
112
|
+
const newPosition = position ?? resolved.position;
|
|
113
|
+
|
|
114
|
+
const operation = ChangeBoundsOperation.create([{ elementId: realId, newSize, newPosition }]);
|
|
115
|
+
promises.push(this.actionDispatcher.dispatch(operation));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (text) {
|
|
119
|
+
const labelId = this.labelProvider.getLabel(resolved)?.id;
|
|
120
|
+
if (labelId) {
|
|
121
|
+
promises.push(this.actionDispatcher.dispatch(ApplyLabelEditOperation.create({ labelId, text })));
|
|
122
|
+
} else {
|
|
123
|
+
warnings.push(
|
|
124
|
+
`Ignored \`text\` for '${change.elementId}' (type '${resolved.type}') — this element has no editable label.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await Promise.all(promises);
|
|
131
|
+
|
|
132
|
+
const modifiedNodes = nodes
|
|
133
|
+
.map(change => this.describeElement(change.elementId))
|
|
134
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== undefined);
|
|
135
|
+
return this.success(
|
|
136
|
+
`Successfully modified ${nodes.length} node(s) (in ${promises.length} commands)` + formatNoticeList('warnings', warnings),
|
|
137
|
+
{ modifiedNodes, dispatchedCommands: promises.length, warnings }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
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 { ClientId, GLabel, GModelElement, Logger, ModelState, NullLogger } from '@eclipse-glsp/server';
|
|
18
|
+
import { expect } from 'chai';
|
|
19
|
+
import { Container, ContainerModule } from 'inversify';
|
|
20
|
+
import { McpModelSerializer } from '../../resources/services/mcp-model-serializer';
|
|
21
|
+
import { DefaultMcpLabelProvider, McpElementsNotFoundError, McpIdAliasService, McpLabelProvider, McpToolResult } from '../../server';
|
|
22
|
+
import { QueryElementsInput, QueryElementsMcpToolHandler } from './query-elements-mcp-tool-handler';
|
|
23
|
+
|
|
24
|
+
function makeLabel(text: string): GLabel {
|
|
25
|
+
// Set the prototype so `child instanceof GLabel` checks in the handler return true.
|
|
26
|
+
return Object.assign(Object.create(GLabel.prototype), { id: 'label', type: 'label', text, children: [] }) as GLabel;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeElement(id: string, type: string, labelText?: string): GModelElement {
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
type,
|
|
33
|
+
children: labelText !== undefined ? [makeLabel(labelText)] : []
|
|
34
|
+
} as unknown as GModelElement;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeModelState(elements: GModelElement[]): ModelState {
|
|
38
|
+
const byId = new Map(elements.map(el => [el.id, el]));
|
|
39
|
+
return {
|
|
40
|
+
index: {
|
|
41
|
+
allIds: () => [...byId.keys()],
|
|
42
|
+
get: (id: string) => byId.get(id),
|
|
43
|
+
find: (id: string) => byId.get(id)
|
|
44
|
+
}
|
|
45
|
+
} as unknown as ModelState;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface CapturingSerializer extends McpModelSerializer {
|
|
49
|
+
/** Element arrays the handler passed to {@link serializeStructuredArray}, in call order. */
|
|
50
|
+
capturedArrays: GModelElement[][];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeCapturingSerializer(): CapturingSerializer {
|
|
54
|
+
const capturedArrays: GModelElement[][] = [];
|
|
55
|
+
return {
|
|
56
|
+
capturedArrays,
|
|
57
|
+
serialize: () => 'serialized',
|
|
58
|
+
serializeStructured: () => ({}),
|
|
59
|
+
serializeArray: elements => elements.map(el => `- ${el.id} (${el.type})`).join('\n'),
|
|
60
|
+
serializeStructuredArray: elements => {
|
|
61
|
+
capturedArrays.push(elements);
|
|
62
|
+
return { elements: elements.map(el => ({ id: el.id, type: el.type })) };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildHandler(elements: GModelElement[], serializer: McpModelSerializer = makeCapturingSerializer()): QueryElementsMcpToolHandler {
|
|
68
|
+
const container = new Container();
|
|
69
|
+
container.load(
|
|
70
|
+
new ContainerModule(bind => {
|
|
71
|
+
bind(Logger).toConstantValue(new NullLogger());
|
|
72
|
+
bind(ClientId).toConstantValue('test-session');
|
|
73
|
+
bind(ModelState).toConstantValue(makeModelState(elements));
|
|
74
|
+
bind(McpIdAliasService).toConstantValue({
|
|
75
|
+
lookup: (id: string) => id,
|
|
76
|
+
alias: (id: string) => id
|
|
77
|
+
} as McpIdAliasService);
|
|
78
|
+
bind(McpModelSerializer).toConstantValue(serializer);
|
|
79
|
+
bind(McpLabelProvider).to(DefaultMcpLabelProvider);
|
|
80
|
+
bind(QueryElementsMcpToolHandler).toSelf();
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
return container.get(QueryElementsMcpToolHandler);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ListStructured {
|
|
87
|
+
mode: 'list';
|
|
88
|
+
matches: { id: string; type: string; label?: string }[];
|
|
89
|
+
truncated: boolean;
|
|
90
|
+
}
|
|
91
|
+
interface InspectStructured {
|
|
92
|
+
mode: 'inspect';
|
|
93
|
+
elements: { id: string; type: string }[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function callCreateResult(handler: QueryElementsMcpToolHandler, params: QueryElementsInput): Promise<McpToolResult> {
|
|
97
|
+
return (handler as unknown as { createResult: (p: QueryElementsInput) => Promise<McpToolResult> }).createResult(params);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe('QueryElementsMcpToolHandler', () => {
|
|
101
|
+
describe('list mode (no `elementIds`)', () => {
|
|
102
|
+
it('filters by `types` (only matching types are returned)', async () => {
|
|
103
|
+
const handler = buildHandler([
|
|
104
|
+
makeElement('n1', 'task:manual', 'Build'),
|
|
105
|
+
makeElement('n2', 'task:automated', 'Deploy'),
|
|
106
|
+
makeElement('e1', 'edge')
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const result = await callCreateResult(handler, { sessionId: 's', types: ['task:manual'] });
|
|
110
|
+
const structured = result.structuredContent as unknown as ListStructured;
|
|
111
|
+
expect(structured.mode).to.equal('list');
|
|
112
|
+
expect(structured.matches).to.have.lengthOf(1);
|
|
113
|
+
expect(structured.matches[0].id).to.equal('n1');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('matches `labelMatch` case-insensitively against direct GLabel children', async () => {
|
|
117
|
+
const handler = buildHandler([
|
|
118
|
+
makeElement('n1', 'task', 'Build artifact'),
|
|
119
|
+
makeElement('n2', 'task', 'Deploy artifact'),
|
|
120
|
+
makeElement('n3', 'task', 'Validate spec')
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
const result = await callCreateResult(handler, { sessionId: 's', labelMatch: 'ARTIFACT' });
|
|
124
|
+
const ids = (result.structuredContent as unknown as ListStructured).matches.map(m => m.id);
|
|
125
|
+
expect(ids).to.have.members(['n1', 'n2']);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('combines `types` and `labelMatch` as AND', async () => {
|
|
129
|
+
const handler = buildHandler([
|
|
130
|
+
makeElement('n1', 'task:manual', 'Build artifact'),
|
|
131
|
+
makeElement('n2', 'task:automated', 'Build artifact')
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const result = await callCreateResult(handler, {
|
|
135
|
+
sessionId: 's',
|
|
136
|
+
types: ['task:manual'],
|
|
137
|
+
labelMatch: 'build'
|
|
138
|
+
});
|
|
139
|
+
const matches = (result.structuredContent as unknown as ListStructured).matches;
|
|
140
|
+
expect(matches).to.have.lengthOf(1);
|
|
141
|
+
expect(matches[0].id).to.equal('n1');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('caps results at `limit` and reports `truncated: true`', async () => {
|
|
145
|
+
const elements = Array.from({ length: 10 }, (_, i) => makeElement(`n${i}`, 'task'));
|
|
146
|
+
const handler = buildHandler(elements);
|
|
147
|
+
|
|
148
|
+
const result = await callCreateResult(handler, { sessionId: 's', limit: 3 });
|
|
149
|
+
const structured = result.structuredContent as unknown as ListStructured;
|
|
150
|
+
expect(structured.matches).to.have.lengthOf(3);
|
|
151
|
+
expect(structured.truncated).to.equal(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('surfaces a no-match message and empty matches when nothing matches', async () => {
|
|
155
|
+
const handler = buildHandler([makeElement('n1', 'task', 'foo')]);
|
|
156
|
+
|
|
157
|
+
const result = await callCreateResult(handler, { sessionId: 's', types: ['nonexistent'] });
|
|
158
|
+
const text = (result.content[0] as { text: string }).text;
|
|
159
|
+
expect(text).to.include('No elements matched');
|
|
160
|
+
expect((result.structuredContent as unknown as ListStructured).matches).to.deep.equal([]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('inspect mode (with `elementIds`)', () => {
|
|
165
|
+
it('resolves the requested ids and forwards the matching elements to the serializer', async () => {
|
|
166
|
+
const elements = [makeElement('n1', 'task:manual', 'Build'), makeElement('n2', 'task:automated', 'Deploy')];
|
|
167
|
+
const serializer = makeCapturingSerializer();
|
|
168
|
+
const handler = buildHandler(elements, serializer);
|
|
169
|
+
|
|
170
|
+
const result = await callCreateResult(handler, { sessionId: 's', elementIds: ['n1', 'n2'] });
|
|
171
|
+
|
|
172
|
+
// The handler delegated rendering to the injected serializer with the exact
|
|
173
|
+
// GModelElement instances looked up from the model index — not just the ids. Inspect
|
|
174
|
+
// mode also probes per-element to detect container expansion before rendering, so the
|
|
175
|
+
// final array call is what produces the structuredContent — assert on that one.
|
|
176
|
+
expect(serializer.capturedArrays.length).to.be.greaterThan(0);
|
|
177
|
+
const finalCall = serializer.capturedArrays[serializer.capturedArrays.length - 1];
|
|
178
|
+
expect(finalCall).to.deep.equal(elements);
|
|
179
|
+
|
|
180
|
+
const structured = result.structuredContent as unknown as InspectStructured;
|
|
181
|
+
expect(structured.mode).to.equal('inspect');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('throws McpElementsNotFoundError when an id is missing from the model', async () => {
|
|
185
|
+
const handler = buildHandler([makeElement('n1', 'task')]);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
await callCreateResult(handler, { sessionId: 's', elementIds: ['n1', 'unknown'] });
|
|
189
|
+
expect.fail('expected McpElementsNotFoundError');
|
|
190
|
+
} catch (err) {
|
|
191
|
+
expect(err).to.be.instanceOf(McpElementsNotFoundError);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('ignores `types` / `labelMatch` / `limit` when `elementIds` is set', async () => {
|
|
196
|
+
const handler = buildHandler([makeElement('n1', 'task:manual'), makeElement('n2', 'task:automated')]);
|
|
197
|
+
|
|
198
|
+
const result = await callCreateResult(handler, {
|
|
199
|
+
sessionId: 's',
|
|
200
|
+
elementIds: ['n1'],
|
|
201
|
+
types: ['task:automated'], // would exclude n1 in list mode
|
|
202
|
+
labelMatch: 'nope',
|
|
203
|
+
limit: 0 // intentionally would fail validation in list mode
|
|
204
|
+
});
|
|
205
|
+
const structured = result.structuredContent as unknown as InspectStructured;
|
|
206
|
+
expect(structured.mode).to.equal('inspect');
|
|
207
|
+
expect(structured.elements.map(e => e.id)).to.deep.equal(['n1']);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|