@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,389 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2025-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, ClientId, Logger, MaybePromise, ModelState, RequestAction, ResponseAction } from '@eclipse-glsp/server';
|
|
18
|
+
import { CompleteResourceTemplateCallback, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
19
|
+
import { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
|
|
20
|
+
import { Annotations, ListResourcesResult, Role } from '@modelcontextprotocol/sdk/types.js';
|
|
21
|
+
import { inject, injectable, interfaces } from 'inversify';
|
|
22
|
+
import { ZodObject, ZodRawShape } from 'zod/v4';
|
|
23
|
+
import { GLSPMcpServer } from './glsp-mcp-server';
|
|
24
|
+
import {
|
|
25
|
+
McpResourceContent,
|
|
26
|
+
McpResourceResult,
|
|
27
|
+
McpResourceResultContent,
|
|
28
|
+
McpToolError,
|
|
29
|
+
McpToolResult,
|
|
30
|
+
extractErrorMessage,
|
|
31
|
+
requestActionOrFail
|
|
32
|
+
} from './mcp-handler-shared';
|
|
33
|
+
import { McpIdAliasService } from './mcp-id-alias-service';
|
|
34
|
+
import { McpDiagramScopedInput } from './mcp-input-schemas';
|
|
35
|
+
import { McpMimeType } from './mcp-mime-types';
|
|
36
|
+
import { mcpRequestContext } from './mcp-request-context';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Multi-binding key for **server-scope** resource handlers — singletons that don't target a
|
|
40
|
+
* specific GLSP client session. For diagram-scope resources see
|
|
41
|
+
* {@link McpDiagramResourceHandlerConstructor}.
|
|
42
|
+
*
|
|
43
|
+
* @experimental
|
|
44
|
+
*/
|
|
45
|
+
export interface McpResourceHandler {
|
|
46
|
+
registerResource(server: GLSPMcpServer): void;
|
|
47
|
+
/** Optional tool fallback for clients that don't speak the resources protocol. */
|
|
48
|
+
registerToolAlternative?(server: GLSPMcpServer): void;
|
|
49
|
+
}
|
|
50
|
+
export const McpResourceHandler = Symbol('McpResourceHandler');
|
|
51
|
+
|
|
52
|
+
/** Static URI (string) or templated URI (`{ template: string }`). The base branches on shape. */
|
|
53
|
+
export type McpResourceUri = string | { template: string };
|
|
54
|
+
|
|
55
|
+
/** Shared infrastructure for both server- and diagram-scope resource handlers. */
|
|
56
|
+
@injectable()
|
|
57
|
+
abstract class BaseMcpResourceHandler {
|
|
58
|
+
@inject(Logger) protected logger: Logger;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resource identifier exposed to the MCP client. Also used to reference this resource from
|
|
62
|
+
* other handlers' prompt or description text — wire via `static readonly NAME = '…'` and
|
|
63
|
+
* `readonly name = ClassName.NAME` so those cross-references survive renames.
|
|
64
|
+
*/
|
|
65
|
+
abstract readonly name: string;
|
|
66
|
+
/** LLM-facing explanation surfaced in the resource catalog. Keep concise — clients pass this verbatim to the model. */
|
|
67
|
+
abstract readonly description: string;
|
|
68
|
+
/** MIME type of the resource body. Adopters typically use one of {@link McpMimeType}'s common values; any string the MCP SDK accepts is valid. */
|
|
69
|
+
abstract readonly mimeType: McpMimeType;
|
|
70
|
+
/** Static URI string for fixed resources, or `{ template: string }` for templated URIs (e.g. `glsp://diagrams/{sessionId}/model`). */
|
|
71
|
+
abstract readonly uri: McpResourceUri;
|
|
72
|
+
/** Optional human-friendly display name for UIs that render a friendlier label than `name`. */
|
|
73
|
+
readonly title?: string;
|
|
74
|
+
|
|
75
|
+
// ─── Resource annotations (MCP spec: server/resources#annotations) ────────────
|
|
76
|
+
// Surfaced as flat fields rather than a nested `annotations` object so adopters can
|
|
77
|
+
// override one hint with a one-line `override readonly priority = 0.8;` instead of
|
|
78
|
+
// re-declaring the whole triple. Mirror the {@link BaseMcpToolHandler} pattern.
|
|
79
|
+
// **Untrusted** unless from a trusted server — clients MUST treat these as advisory.
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Intended audience(s) — `"user"` (display the rendered content), `"assistant"` (use as model
|
|
83
|
+
* context), or both. Clients use it to filter/route the resource.
|
|
84
|
+
*/
|
|
85
|
+
readonly audience?: Role[];
|
|
86
|
+
/** Importance, 0.0–1.0. Clients use it to prioritize inclusion in context. */
|
|
87
|
+
readonly priority?: number;
|
|
88
|
+
/** ISO 8601 timestamp of last meaningful change. Omit when the server has no clean freshness signal. */
|
|
89
|
+
readonly lastModified?: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Assembles the {@link Annotations} object the SDK expects from the flat-field surface.
|
|
93
|
+
* Returns `undefined` when no field is set so `resources/list` entries don't carry an
|
|
94
|
+
* empty `annotations: {}`.
|
|
95
|
+
*/
|
|
96
|
+
toAnnotations(): Annotations | undefined {
|
|
97
|
+
if (this.audience === undefined && this.priority === undefined && this.lastModified === undefined) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
...(this.audience !== undefined ? { audience: this.audience } : {}),
|
|
102
|
+
...(this.priority !== undefined ? { priority: this.priority } : {}),
|
|
103
|
+
...(this.lastModified !== undefined ? { lastModified: this.lastModified } : {})
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Set to a `z.object({...})` schema to also expose the resource as a tool fallback (for MCP
|
|
109
|
+
* clients that don't speak the resources protocol). Field shape mirrors `inputSchema` on
|
|
110
|
+
* tool handlers — declarative, no extra method.
|
|
111
|
+
*/
|
|
112
|
+
readonly toolAlternativeInputSchema?: ZodObject<ZodRawShape>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Optional dual-emit output schema applied **only** in tool-alternative mode (when
|
|
116
|
+
* {@link toolAlternativeInputSchema} is set). When declared, the handler should populate
|
|
117
|
+
* `structured` on the returned {@link McpResourceContent} so the framework can forward it
|
|
118
|
+
* to `CallToolResult.structuredContent`. Resource-protocol reads ignore this — the spec
|
|
119
|
+
* has no equivalent slot on `ReadResourceResult`.
|
|
120
|
+
*/
|
|
121
|
+
readonly toolAlternativeOutputSchema?: ZodObject<ZodRawShape>;
|
|
122
|
+
|
|
123
|
+
/** Override for templated URIs — enumerate matching resources. */
|
|
124
|
+
list?(): MaybePromise<ListResourcesResult>;
|
|
125
|
+
/** Override for templated URIs — completers per template variable. */
|
|
126
|
+
complete?(): Record<string, CompleteResourceTemplateCallback>;
|
|
127
|
+
|
|
128
|
+
/** Resolve `this.uri` (which may be a fixed string or a template object) to a template string. */
|
|
129
|
+
protected uriTemplate(): string {
|
|
130
|
+
return typeof this.uri === 'string' ? this.uri : this.uri.template;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Replace `{key}` placeholders in the URI template with the supplied values, single-pass
|
|
135
|
+
* (no re-expansion of substituted text) and `encodeURIComponent`-escaped. Unmatched keys
|
|
136
|
+
* pass through verbatim so the caller can spot them.
|
|
137
|
+
*/
|
|
138
|
+
protected expandUriTemplate(vars: Record<string, string>): string {
|
|
139
|
+
return this.uriTemplate().replace(/\{(\w+)\}/g, (placeholder, key) => {
|
|
140
|
+
const value = vars[key];
|
|
141
|
+
return value === undefined ? placeholder : encodeURIComponent(value);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Catches `McpToolError` (→ surfaced as text content + isError) and unexpected errors. */
|
|
146
|
+
protected async execute(producer: () => MaybePromise<McpResourceContent>): Promise<ResourceExecutionResult> {
|
|
147
|
+
try {
|
|
148
|
+
return { ok: true, body: await producer() };
|
|
149
|
+
} catch (err: unknown) {
|
|
150
|
+
if (err instanceof McpToolError) {
|
|
151
|
+
return { ok: false, message: err.message };
|
|
152
|
+
}
|
|
153
|
+
const message = extractErrorMessage(err);
|
|
154
|
+
this.logger.error(`Unexpected error in resource '${this.name}': ${message}`, err);
|
|
155
|
+
return { ok: false, message };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Wraps the body returned by {@link createResult} with `uri` + `mimeType` for the SDK. */
|
|
160
|
+
toResourceResult(uri: string, result: ResourceExecutionResult): McpResourceResult {
|
|
161
|
+
if (!result.ok) {
|
|
162
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: result.message }], isError: true };
|
|
163
|
+
}
|
|
164
|
+
const content: McpResourceResultContent =
|
|
165
|
+
'text' in result.body
|
|
166
|
+
? { uri, mimeType: this.mimeType, text: result.body.text }
|
|
167
|
+
: { uri, mimeType: this.mimeType, blob: result.body.blob };
|
|
168
|
+
return { contents: [content], isError: false };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Converts the body to a `CallToolResult` for tool-alternative mode. Image MIMEs render as `image` content. */
|
|
172
|
+
toToolResult(result: ResourceExecutionResult): McpToolResult {
|
|
173
|
+
if (!result.ok) {
|
|
174
|
+
return { isError: true, content: [{ type: 'text', text: result.message }] };
|
|
175
|
+
}
|
|
176
|
+
const structuredContent = result.body.structured;
|
|
177
|
+
const baseContent: McpToolResult =
|
|
178
|
+
'text' in result.body
|
|
179
|
+
? { isError: false, content: [{ type: 'text', text: result.body.text }] }
|
|
180
|
+
: { isError: false, content: [{ type: 'image', data: result.body.blob, mimeType: this.mimeType }] };
|
|
181
|
+
return structuredContent ? { ...baseContent, structuredContent } : baseContent;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Builds the SDK `ResourceTemplate` for templated URIs (server-scope path). */
|
|
185
|
+
protected buildResourceTemplate(template: string): ResourceTemplate {
|
|
186
|
+
return new ResourceTemplate(template, {
|
|
187
|
+
list: this.list ? async extra => mcpRequestContext.run(extra, () => this.list!()) : undefined,
|
|
188
|
+
complete: this.complete?.()
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type ResourceExecutionResult = { ok: true; body: McpResourceContent } | { ok: false; message: string };
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Server-scope resource base — for resources that don't target a specific GLSP client session
|
|
197
|
+
* (e.g., a hypothetical adopter-supplied "global config" resource that returns the same data
|
|
198
|
+
* regardless of which diagram is open). The instance exists at boot, so `list`/`complete` can
|
|
199
|
+
* `@inject` server-scope deps directly. Bound under {@link McpResourceHandler}; the launcher
|
|
200
|
+
* invokes `registerResource(server)` once per MCP session.
|
|
201
|
+
*
|
|
202
|
+
* @experimental
|
|
203
|
+
*/
|
|
204
|
+
@injectable()
|
|
205
|
+
export abstract class AbstractMcpResourceHandler<T = Record<string, unknown>> extends BaseMcpResourceHandler implements McpResourceHandler {
|
|
206
|
+
/** Throw {@link McpToolError} for expected errors; the base wraps. */
|
|
207
|
+
protected abstract createResult(params: T): MaybePromise<McpResourceContent>;
|
|
208
|
+
|
|
209
|
+
registerResource(server: GLSPMcpServer): void {
|
|
210
|
+
const annotations = this.toAnnotations();
|
|
211
|
+
const config = {
|
|
212
|
+
title: this.title,
|
|
213
|
+
description: this.description,
|
|
214
|
+
mimeType: this.mimeType,
|
|
215
|
+
...(annotations ? { annotations } : {})
|
|
216
|
+
};
|
|
217
|
+
if (typeof this.uri === 'string') {
|
|
218
|
+
const uri = this.uri;
|
|
219
|
+
server.registerResource(this.name, uri, config, async (_uri, extra) =>
|
|
220
|
+
mcpRequestContext.run(extra, async () => this.toResourceResult(uri, await this.execute(() => this.createResult({} as T))))
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
server.registerResource(this.name, this.buildResourceTemplate(this.uri.template), config, async (uri, params, extra) =>
|
|
224
|
+
mcpRequestContext.run(extra, async () =>
|
|
225
|
+
this.toResourceResult(uri.toString(), await this.execute(() => this.createResult(toParams(params) as T)))
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** No-op when no {@link toolAlternativeInputSchema} is declared; otherwise registers as a tool. */
|
|
232
|
+
registerToolAlternative(server: GLSPMcpServer): void {
|
|
233
|
+
if (!this.toolAlternativeInputSchema) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.doRegisterToolAlternative(server);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected doRegisterToolAlternative(server: GLSPMcpServer): void {
|
|
240
|
+
const inputSchema = this.toolAlternativeInputSchema!;
|
|
241
|
+
// `.strict()` matches the tool-handler policy — see `BaseMcpToolHandler.toRegistrationConfig`
|
|
242
|
+
// for the full rationale (LLM-typoed fields surface as JSON-RPC validation errors instead
|
|
243
|
+
// of being silently stripped).
|
|
244
|
+
server.registerTool(
|
|
245
|
+
this.name,
|
|
246
|
+
{
|
|
247
|
+
title: this.title,
|
|
248
|
+
description: this.description,
|
|
249
|
+
inputSchema: inputSchema.strict(),
|
|
250
|
+
outputSchema: this.toolAlternativeOutputSchema
|
|
251
|
+
},
|
|
252
|
+
async (params, extra) =>
|
|
253
|
+
mcpRequestContext.run(extra, async () => this.toToolResult(await this.execute(() => this.createResult(params as T))))
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Diagram-scope resource base — for resources whose URI templates carry a `sessionId` (e.g.,
|
|
260
|
+
* `glsp://diagrams/{sessionId}/model`).
|
|
261
|
+
*
|
|
262
|
+
* Unlike the server-scope base, the diagram-scope handler does not register itself with the
|
|
263
|
+
* SDK. The launcher's dispatcher reads the per-diagram-type constructor list, registers a
|
|
264
|
+
* single resource entry per `name`, and routes incoming reads to the matching per-GLSP-session
|
|
265
|
+
* handler instance via {@link handleRead}. For `list`/`complete`, the launcher walks all open
|
|
266
|
+
* GLSP sessions and aggregates each instance's slice — see {@link glspSessionScopedComplete}
|
|
267
|
+
* for the cross-GLSP-session-pollution auto-guard.
|
|
268
|
+
*
|
|
269
|
+
* @experimental
|
|
270
|
+
*/
|
|
271
|
+
@injectable()
|
|
272
|
+
export abstract class AbstractMcpDiagramResourceHandler<
|
|
273
|
+
T extends McpDiagramScopedInput = McpDiagramScopedInput
|
|
274
|
+
> extends BaseMcpResourceHandler {
|
|
275
|
+
@inject(ClientId) protected clientId: string;
|
|
276
|
+
@inject(ModelState) protected modelState: ModelState;
|
|
277
|
+
@inject(McpIdAliasService) protected aliasService: McpIdAliasService;
|
|
278
|
+
@inject(ActionDispatcher) protected actionDispatcher: ActionDispatcher;
|
|
279
|
+
|
|
280
|
+
/** Throw {@link McpToolError} for expected errors; the base wraps. */
|
|
281
|
+
protected abstract createResult(params: T): MaybePromise<McpResourceContent>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Convenience for resource handlers that fulfil a read by initiating a `RequestAction`
|
|
285
|
+
* round-trip to the client (today: `diagram-png` → `RequestExportAction`). Wraps
|
|
286
|
+
* {@link requestActionOrFail} with `this.actionDispatcher` and a default label of
|
|
287
|
+
* `this.name`. Pass an explicit label only to disambiguate between multiple round-trips.
|
|
288
|
+
*/
|
|
289
|
+
protected requestAction<R extends ResponseAction>(request: RequestAction<R>, timeoutMs: number, label: string = this.name): Promise<R> {
|
|
290
|
+
return requestActionOrFail(this.actionDispatcher, request, timeoutMs, label);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Override to opt out of registration when a runtime dependency is missing. Default: `true`. */
|
|
294
|
+
canRegister(): boolean {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Default `list()` for the per-session-single-resource case (diagram-model, diagram-png).
|
|
300
|
+
* Emits one entry resolved against the URI template, named with the handler's title and
|
|
301
|
+
* the GLSP session id, described with the handler's `description`. Multi-resource
|
|
302
|
+
* handlers (e.g. `element-types`, which lists once per diagram type) override.
|
|
303
|
+
*/
|
|
304
|
+
override list(): ListResourcesResult {
|
|
305
|
+
return { resources: [this.toListingEntry()] };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Default `complete()` for templated URIs that include `{sessionId}`. Returns a single
|
|
310
|
+
* completer that resolves to the current GLSP session's id. Handlers with other template
|
|
311
|
+
* variables (e.g. `{diagramType}` on `element-types`) override.
|
|
312
|
+
*/
|
|
313
|
+
override complete(): Record<string, CompleteResourceTemplateCallback> {
|
|
314
|
+
if (this.uriTemplate().includes('{sessionId}')) {
|
|
315
|
+
return { sessionId: async () => [this.clientId] };
|
|
316
|
+
}
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Default per-session entry built from the handler's metadata. Override to customize. */
|
|
321
|
+
protected toListingEntry(): ListResourcesResult['resources'][number] {
|
|
322
|
+
const annotations = this.toAnnotations();
|
|
323
|
+
return {
|
|
324
|
+
uri: this.expandUriTemplate({ sessionId: this.clientId }),
|
|
325
|
+
name: `${this.title ?? this.name}: ${this.clientId}`,
|
|
326
|
+
description: this.description,
|
|
327
|
+
mimeType: this.mimeType,
|
|
328
|
+
...(annotations ? { annotations } : {})
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Public dispatch entry point invoked by {@link McpServerLauncher}'s SDK callback for
|
|
334
|
+
* resource reads. The launcher passes the URI it received from the SDK plus the URI-template
|
|
335
|
+
* variable values normalized into a flat record.
|
|
336
|
+
*/
|
|
337
|
+
async handleRead(uri: string, params: T): Promise<McpResourceResult> {
|
|
338
|
+
return this.toResourceResult(uri, await this.execute(() => this.createResult(params)));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Public dispatch entry point invoked by the launcher when the resource is exposed as a
|
|
343
|
+
* tool fallback (`McpServerOptions.resources === false`).
|
|
344
|
+
*/
|
|
345
|
+
async handleAsTool(params: T): Promise<McpToolResult> {
|
|
346
|
+
return this.toToolResult(await this.execute(() => this.createResult(params)));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Wraps adopter-provided {@link complete} callbacks with the cross-GLSP-session-pollution
|
|
351
|
+
* auto-guard: when a templated URI carries `{sessionId}` and the LLM has bound a specific
|
|
352
|
+
* session id, completers for OTHER variables should not leak data from sessions whose ids
|
|
353
|
+
* don't match. The wrapper auto-returns `[]` from a GLSP session whose id doesn't match
|
|
354
|
+
* `ctx.arguments.sessionId`. Adopters write completers as if they only see their own GLSP
|
|
355
|
+
* session's data — the framework enforces the guard.
|
|
356
|
+
*
|
|
357
|
+
* Invoked by the launcher's aggregator, not by adopters directly.
|
|
358
|
+
*/
|
|
359
|
+
glspSessionScopedComplete(): Record<string, CompleteResourceTemplateCallback> {
|
|
360
|
+
const raw = this.complete?.() ?? {};
|
|
361
|
+
const myId = this.clientId;
|
|
362
|
+
const isGlspSessionScoped = typeof this.uri === 'object' && this.uri.template.includes('{sessionId}');
|
|
363
|
+
const wrapped: Record<string, CompleteResourceTemplateCallback> = {};
|
|
364
|
+
for (const [variable, completer] of Object.entries(raw)) {
|
|
365
|
+
wrapped[variable] =
|
|
366
|
+
variable === 'sessionId' || !isGlspSessionScoped
|
|
367
|
+
? completer
|
|
368
|
+
: async (value, ctx) => (ctx?.arguments?.sessionId === myId ? completer(value, ctx) : []);
|
|
369
|
+
}
|
|
370
|
+
return wrapped;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Normalizes SDK `Variables` (each value is `string | string[]`) to a flat `Record<string, string>`. */
|
|
375
|
+
export function toParams(variables: Variables): Record<string, string> {
|
|
376
|
+
const out: Record<string, string> = {};
|
|
377
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
378
|
+
out[key] = Array.isArray(value) ? value[0] ?? '' : value;
|
|
379
|
+
}
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Multi-binding identifier for diagram-scope resource handler constructors. See
|
|
385
|
+
* {@link McpDiagramToolHandlerConstructor} for the lifecycle pattern — same shape, applied to
|
|
386
|
+
* resource handlers.
|
|
387
|
+
*/
|
|
388
|
+
export type McpDiagramResourceHandlerConstructor = interfaces.Newable<AbstractMcpDiagramResourceHandler<any>>;
|
|
389
|
+
export const McpDiagramResourceHandlerConstructor = Symbol('McpDiagramResourceHandlerConstructor');
|
|
@@ -0,0 +1,173 @@
|
|
|
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 { McpServerInitOptions } from '@eclipse-glsp/protocol';
|
|
18
|
+
import { expect } from 'chai';
|
|
19
|
+
import { version as packageVersion } from '../../package.json';
|
|
20
|
+
import { McpServerLauncher, SERVER_VERSION, assertLoopbackOrAcknowledged, isLoopbackHost, pickInitOptions } from './mcp-server-launcher';
|
|
21
|
+
|
|
22
|
+
describe('McpServerLauncher · SERVER_VERSION', () => {
|
|
23
|
+
it('matches the package.json version (no stale literal)', () => {
|
|
24
|
+
// Regression guard: the launcher used to hard-code '1.0.0'. Pull from package.json so
|
|
25
|
+
// adopters and MCP clients can tell builds apart via the `serverInfo.version` handshake
|
|
26
|
+
// field.
|
|
27
|
+
expect(SERVER_VERSION).to.equal(packageVersion);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('McpServerLauncher · buildCapabilities', () => {
|
|
32
|
+
/**
|
|
33
|
+
* Sidestep DI: build a stub whose shape matches the fields `buildCapabilities` reads, then
|
|
34
|
+
* invoke the prototype method against it. The method is protected, so we cast through.
|
|
35
|
+
*/
|
|
36
|
+
function buildCaps(
|
|
37
|
+
args: {
|
|
38
|
+
toolHandlers?: unknown[];
|
|
39
|
+
promptHandlers?: unknown[];
|
|
40
|
+
resourceHandlers?: unknown[];
|
|
41
|
+
hasDiagramTools?: boolean;
|
|
42
|
+
hasDiagramPrompts?: boolean;
|
|
43
|
+
hasDiagramResources?: boolean;
|
|
44
|
+
},
|
|
45
|
+
resourcesAsResources: boolean
|
|
46
|
+
): Record<string, unknown> {
|
|
47
|
+
const stub = {
|
|
48
|
+
toolHandlers: args.toolHandlers ?? [],
|
|
49
|
+
promptHandlers: args.promptHandlers ?? [],
|
|
50
|
+
resourceHandlers: args.resourceHandlers ?? [],
|
|
51
|
+
dispatcher: {
|
|
52
|
+
hasDiagramTools: () => args.hasDiagramTools ?? false,
|
|
53
|
+
hasDiagramPrompts: () => args.hasDiagramPrompts ?? false,
|
|
54
|
+
hasDiagramResources: () => args.hasDiagramResources ?? false
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const proto = McpServerLauncher.prototype as unknown as {
|
|
58
|
+
buildCapabilities(this: typeof stub, resourcesAsResources: boolean): Record<string, unknown>;
|
|
59
|
+
};
|
|
60
|
+
return proto.buildCapabilities.call(stub, resourcesAsResources);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
it('omits `tools`, `resources`, and `prompts` when nothing is bound (regression: resources/list -32601)', () => {
|
|
64
|
+
const caps = buildCaps({}, /* resourcesAsResources */ true);
|
|
65
|
+
expect(caps).to.have.property('logging');
|
|
66
|
+
expect(caps).to.not.have.property('tools');
|
|
67
|
+
expect(caps).to.not.have.property('resources');
|
|
68
|
+
expect(caps).to.not.have.property('prompts');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('declares `tools` with listChanged: false when at least one tool handler binds', () => {
|
|
72
|
+
const caps = buildCaps({ toolHandlers: [{}] }, true);
|
|
73
|
+
expect(caps.tools).to.deep.equal({ listChanged: false });
|
|
74
|
+
expect(caps).to.not.have.property('resources');
|
|
75
|
+
expect(caps).to.not.have.property('prompts');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('declares `prompts` when at least one prompt handler binds (server- or diagram-scope)', () => {
|
|
79
|
+
expect(buildCaps({ promptHandlers: [{}] }, true).prompts).to.deep.equal({ listChanged: false });
|
|
80
|
+
expect(buildCaps({ hasDiagramPrompts: true }, true).prompts).to.deep.equal({ listChanged: false });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('declares `resources` only in dataMode=resources; otherwise resources count toward `tools`', () => {
|
|
84
|
+
// Diagram-scope resources mutate per GLSP session add/remove → `listChanged: true` is honest.
|
|
85
|
+
const asResources = buildCaps({ hasDiagramResources: true }, true);
|
|
86
|
+
expect(asResources.resources).to.deep.equal({ listChanged: true });
|
|
87
|
+
expect(asResources).to.not.have.property('tools');
|
|
88
|
+
|
|
89
|
+
const asTools = buildCaps({ hasDiagramResources: true }, false);
|
|
90
|
+
expect(asTools.tools).to.deep.equal({ listChanged: false });
|
|
91
|
+
expect(asTools).to.not.have.property('resources');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('keeps resources.listChanged: false when only server-scope resources are bound (catalog static)', () => {
|
|
95
|
+
const caps = buildCaps({ resourceHandlers: [{}] }, true);
|
|
96
|
+
expect(caps.resources).to.deep.equal({ listChanged: false });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('McpServerLauncher · isLoopbackHost', () => {
|
|
101
|
+
it('treats 127.0.0.0/8, localhost, and ::1 as loopback', () => {
|
|
102
|
+
expect(isLoopbackHost('127.0.0.1')).to.equal(true);
|
|
103
|
+
expect(isLoopbackHost('127.55.0.1')).to.equal(true);
|
|
104
|
+
expect(isLoopbackHost('localhost')).to.equal(true);
|
|
105
|
+
expect(isLoopbackHost('::1')).to.equal(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('treats unspecified, LAN, and public addresses as non-loopback', () => {
|
|
109
|
+
expect(isLoopbackHost('0.0.0.0')).to.equal(false);
|
|
110
|
+
expect(isLoopbackHost('::')).to.equal(false);
|
|
111
|
+
expect(isLoopbackHost('192.168.1.1')).to.equal(false);
|
|
112
|
+
expect(isLoopbackHost('10.0.0.1')).to.equal(false);
|
|
113
|
+
expect(isLoopbackHost('203.0.113.5')).to.equal(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('McpServerLauncher · assertLoopbackOrAcknowledged (auth footgun)', () => {
|
|
118
|
+
it('passes silently for a loopback bind without acknowledgement', () => {
|
|
119
|
+
expect(() => assertLoopbackOrAcknowledged('127.0.0.1', undefined)).to.not.throw();
|
|
120
|
+
expect(() => assertLoopbackOrAcknowledged('localhost', undefined)).to.not.throw();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('throws an actionable error for a non-loopback bind without acknowledgement', () => {
|
|
124
|
+
expect(() => assertLoopbackOrAcknowledged('0.0.0.0', undefined))
|
|
125
|
+
.to.throw(Error)
|
|
126
|
+
.with.property('message')
|
|
127
|
+
.that.matches(/Refusing to bind/)
|
|
128
|
+
.and.matches(/0\.0\.0\.0/)
|
|
129
|
+
.and.matches(/acknowledgedNoAuth/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('passes for a non-loopback bind when acknowledgedNoAuth is true', () => {
|
|
133
|
+
expect(() => assertLoopbackOrAcknowledged('0.0.0.0', true)).to.not.throw();
|
|
134
|
+
expect(() => assertLoopbackOrAcknowledged('192.168.1.50', true)).to.not.throw();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('still throws for a non-loopback bind when acknowledgedNoAuth is false (explicit denial)', () => {
|
|
138
|
+
expect(() => assertLoopbackOrAcknowledged('0.0.0.0', false)).to.throw(/Refusing to bind/);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('McpServerLauncher · pickInitOptions (deploy/init split — defense-in-depth)', () => {
|
|
143
|
+
it('passes through every allowed init-side field unchanged', () => {
|
|
144
|
+
const picked = pickInitOptions({ dataMode: 'resources', agentPersona: 'X', eventStoreLimit: 50 });
|
|
145
|
+
expect(picked).to.deep.equal({ dataMode: 'resources', agentPersona: 'X', eventStoreLimit: 50 });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('omits init-side fields that the caller did not set (no `undefined` sneak-through)', () => {
|
|
149
|
+
const picked = pickInitOptions({ dataMode: 'tools' });
|
|
150
|
+
expect(picked).to.deep.equal({ dataMode: 'tools' });
|
|
151
|
+
expect(picked).to.not.have.property('agentPersona');
|
|
152
|
+
expect(picked).to.not.have.property('eventStoreLimit');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('strips deploy-only keys smuggled in via JSON wire payload (host, allowedHosts, allowedOrigins, acknowledgedNoAuth)', () => {
|
|
156
|
+
// Simulate a malicious/malformed wire payload: the static type rules these out, but
|
|
157
|
+
// JSON parsing does not, so the destructure-pick must drop them.
|
|
158
|
+
const wirePayload = JSON.parse(`{
|
|
159
|
+
"dataMode": "tools",
|
|
160
|
+
"host": "0.0.0.0",
|
|
161
|
+
"allowedHosts": ["evil.example.com"],
|
|
162
|
+
"allowedOrigins": ["https://evil.example.com"],
|
|
163
|
+
"acknowledgedNoAuth": true
|
|
164
|
+
}`) as McpServerInitOptions;
|
|
165
|
+
|
|
166
|
+
const picked = pickInitOptions(wirePayload);
|
|
167
|
+
expect(picked).to.deep.equal({ dataMode: 'tools' });
|
|
168
|
+
expect(picked).to.not.have.property('host');
|
|
169
|
+
expect(picked).to.not.have.property('allowedHosts');
|
|
170
|
+
expect(picked).to.not.have.property('allowedOrigins');
|
|
171
|
+
expect(picked).to.not.have.property('acknowledgedNoAuth');
|
|
172
|
+
});
|
|
173
|
+
});
|