@eclipse-glsp/server-mcp 2.8.0-next.1 → 2.8.0-next.12

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.
Files changed (190) hide show
  1. package/README.md +7 -7
  2. package/lib/browser/modules/browser-mcp-server-module.js +1 -1
  3. package/lib/browser/modules/browser-mcp-server-module.js.map +1 -1
  4. package/lib/common/modules/abstract-mcp-server-module.d.ts +1 -2
  5. package/lib/common/modules/abstract-mcp-server-module.d.ts.map +1 -1
  6. package/lib/common/modules/abstract-mcp-server-module.js.map +1 -1
  7. package/lib/common/modules/mcp-diagram-module.js +1 -1
  8. package/lib/common/modules/mcp-diagram-module.js.map +1 -1
  9. package/lib/common/prompts/handlers/describe-diagram-mcp-prompt-handler.js +3 -2
  10. package/lib/common/prompts/handlers/describe-diagram-mcp-prompt-handler.js.map +1 -1
  11. package/lib/common/prompts/handlers/suggest-improvements-mcp-prompt-handler.js +3 -2
  12. package/lib/common/prompts/handlers/suggest-improvements-mcp-prompt-handler.js.map +1 -1
  13. package/lib/common/resources/handlers/diagram-png-mcp-resource-handler.js +5 -4
  14. package/lib/common/resources/handlers/diagram-png-mcp-resource-handler.js.map +1 -1
  15. package/lib/common/resources/handlers/diagram-svg-mcp-resource-handler.js +4 -3
  16. package/lib/common/resources/handlers/diagram-svg-mcp-resource-handler.js.map +1 -1
  17. package/lib/common/resources/services/element-types-provider.d.ts.map +1 -1
  18. package/lib/common/resources/services/element-types-provider.js +1 -2
  19. package/lib/common/resources/services/element-types-provider.js.map +1 -1
  20. package/lib/common/resources/services/mcp-model-serializer.d.ts +1 -2
  21. package/lib/common/resources/services/mcp-model-serializer.d.ts.map +1 -1
  22. package/lib/common/resources/services/mcp-model-serializer.js +3 -5
  23. package/lib/common/resources/services/mcp-model-serializer.js.map +1 -1
  24. package/lib/common/server/abstract-mcp-server-launcher.d.ts +3 -4
  25. package/lib/common/server/abstract-mcp-server-launcher.d.ts.map +1 -1
  26. package/lib/common/server/abstract-mcp-server-launcher.js +9 -14
  27. package/lib/common/server/abstract-mcp-server-launcher.js.map +1 -1
  28. package/lib/common/server/glsp-mcp-server.d.ts.map +1 -1
  29. package/lib/common/server/glsp-mcp-server.js +2 -2
  30. package/lib/common/server/glsp-mcp-server.js.map +1 -1
  31. package/lib/common/server/lru-event-store.js +2 -3
  32. package/lib/common/server/lru-event-store.js.map +1 -1
  33. package/lib/common/server/mcp-diagram-handler-dispatcher.d.ts +20 -4
  34. package/lib/common/server/mcp-diagram-handler-dispatcher.d.ts.map +1 -1
  35. package/lib/common/server/mcp-diagram-handler-dispatcher.js +40 -13
  36. package/lib/common/server/mcp-diagram-handler-dispatcher.js.map +1 -1
  37. package/lib/common/server/mcp-handler-shared.d.ts +1 -2
  38. package/lib/common/server/mcp-handler-shared.d.ts.map +1 -1
  39. package/lib/common/server/mcp-handler-shared.js +1 -1
  40. package/lib/common/server/mcp-handler-shared.js.map +1 -1
  41. package/lib/common/server/mcp-id-alias-service.js +1 -2
  42. package/lib/common/server/mcp-id-alias-service.js.map +1 -1
  43. package/lib/common/server/mcp-input-schemas.d.ts +2 -0
  44. package/lib/common/server/mcp-input-schemas.d.ts.map +1 -1
  45. package/lib/common/server/mcp-input-schemas.js +3 -1
  46. package/lib/common/server/mcp-input-schemas.js.map +1 -1
  47. package/lib/common/server/mcp-log-level-registry.js +10 -10
  48. package/lib/common/server/mcp-log-level-registry.js.map +1 -1
  49. package/lib/common/server/mcp-mime-types.d.ts +1 -1
  50. package/lib/common/server/mcp-mime-types.d.ts.map +1 -1
  51. package/lib/common/server/mcp-options.d.ts +1 -1
  52. package/lib/common/server/mcp-options.d.ts.map +1 -1
  53. package/lib/common/server/mcp-progress-reporter.js +1 -2
  54. package/lib/common/server/mcp-progress-reporter.js.map +1 -1
  55. package/lib/common/server/mcp-resource-handler.d.ts +10 -1
  56. package/lib/common/server/mcp-resource-handler.d.ts.map +1 -1
  57. package/lib/common/server/mcp-resource-handler.js +17 -10
  58. package/lib/common/server/mcp-resource-handler.js.map +1 -1
  59. package/lib/common/server/mcp-tool-handler.d.ts +33 -16
  60. package/lib/common/server/mcp-tool-handler.d.ts.map +1 -1
  61. package/lib/common/server/mcp-tool-handler.js +19 -3
  62. package/lib/common/server/mcp-tool-handler.js.map +1 -1
  63. package/lib/common/tools/handlers/count-elements-mcp-tool-handler.d.ts +2 -1
  64. package/lib/common/tools/handlers/count-elements-mcp-tool-handler.d.ts.map +1 -1
  65. package/lib/common/tools/handlers/count-elements-mcp-tool-handler.js +4 -4
  66. package/lib/common/tools/handlers/count-elements-mcp-tool-handler.js.map +1 -1
  67. package/lib/common/tools/handlers/create-edges-mcp-tool-handler.d.ts +2 -1
  68. package/lib/common/tools/handlers/create-edges-mcp-tool-handler.d.ts.map +1 -1
  69. package/lib/common/tools/handlers/create-edges-mcp-tool-handler.js +14 -8
  70. package/lib/common/tools/handlers/create-edges-mcp-tool-handler.js.map +1 -1
  71. package/lib/common/tools/handlers/create-nodes-mcp-tool-handler.d.ts +2 -1
  72. package/lib/common/tools/handlers/create-nodes-mcp-tool-handler.d.ts.map +1 -1
  73. package/lib/common/tools/handlers/create-nodes-mcp-tool-handler.js +18 -10
  74. package/lib/common/tools/handlers/create-nodes-mcp-tool-handler.js.map +1 -1
  75. package/lib/common/tools/handlers/delete-elements-mcp-tool-handler.d.ts +2 -1
  76. package/lib/common/tools/handlers/delete-elements-mcp-tool-handler.d.ts.map +1 -1
  77. package/lib/common/tools/handlers/delete-elements-mcp-tool-handler.js +3 -2
  78. package/lib/common/tools/handlers/delete-elements-mcp-tool-handler.js.map +1 -1
  79. package/lib/common/tools/handlers/diagram-model-mcp-tool-handler.d.ts +2 -1
  80. package/lib/common/tools/handlers/diagram-model-mcp-tool-handler.d.ts.map +1 -1
  81. package/lib/common/tools/handlers/diagram-model-mcp-tool-handler.js +7 -4
  82. package/lib/common/tools/handlers/diagram-model-mcp-tool-handler.js.map +1 -1
  83. package/lib/common/tools/handlers/element-types-mcp-tool-handler.d.ts +2 -1
  84. package/lib/common/tools/handlers/element-types-mcp-tool-handler.d.ts.map +1 -1
  85. package/lib/common/tools/handlers/element-types-mcp-tool-handler.js +8 -5
  86. package/lib/common/tools/handlers/element-types-mcp-tool-handler.js.map +1 -1
  87. package/lib/common/tools/handlers/get-selection-mcp-tool-handler.d.ts +2 -1
  88. package/lib/common/tools/handlers/get-selection-mcp-tool-handler.d.ts.map +1 -1
  89. package/lib/common/tools/handlers/get-selection-mcp-tool-handler.js +3 -2
  90. package/lib/common/tools/handlers/get-selection-mcp-tool-handler.js.map +1 -1
  91. package/lib/common/tools/handlers/layout-mcp-tool-handler.d.ts +5 -2
  92. package/lib/common/tools/handlers/layout-mcp-tool-handler.d.ts.map +1 -1
  93. package/lib/common/tools/handlers/layout-mcp-tool-handler.js +8 -3
  94. package/lib/common/tools/handlers/layout-mcp-tool-handler.js.map +1 -1
  95. package/lib/common/tools/handlers/modify-edges-mcp-tool-handler.d.ts +2 -1
  96. package/lib/common/tools/handlers/modify-edges-mcp-tool-handler.d.ts.map +1 -1
  97. package/lib/common/tools/handlers/modify-edges-mcp-tool-handler.js +13 -9
  98. package/lib/common/tools/handlers/modify-edges-mcp-tool-handler.js.map +1 -1
  99. package/lib/common/tools/handlers/modify-nodes-mcp-tool-handler.d.ts +4 -1
  100. package/lib/common/tools/handlers/modify-nodes-mcp-tool-handler.d.ts.map +1 -1
  101. package/lib/common/tools/handlers/modify-nodes-mcp-tool-handler.js +57 -25
  102. package/lib/common/tools/handlers/modify-nodes-mcp-tool-handler.js.map +1 -1
  103. package/lib/common/tools/handlers/query-elements-mcp-tool-handler.d.ts +2 -1
  104. package/lib/common/tools/handlers/query-elements-mcp-tool-handler.d.ts.map +1 -1
  105. package/lib/common/tools/handlers/query-elements-mcp-tool-handler.js +7 -7
  106. package/lib/common/tools/handlers/query-elements-mcp-tool-handler.js.map +1 -1
  107. package/lib/common/tools/handlers/redo-mcp-tool-handler.d.ts +2 -1
  108. package/lib/common/tools/handlers/redo-mcp-tool-handler.d.ts.map +1 -1
  109. package/lib/common/tools/handlers/redo-mcp-tool-handler.js +10 -5
  110. package/lib/common/tools/handlers/redo-mcp-tool-handler.js.map +1 -1
  111. package/lib/common/tools/handlers/save-model-mcp-tool-handler.d.ts +2 -1
  112. package/lib/common/tools/handlers/save-model-mcp-tool-handler.d.ts.map +1 -1
  113. package/lib/common/tools/handlers/save-model-mcp-tool-handler.js +6 -3
  114. package/lib/common/tools/handlers/save-model-mcp-tool-handler.js.map +1 -1
  115. package/lib/common/tools/handlers/session-info-mcp-tool-handler.d.ts +2 -1
  116. package/lib/common/tools/handlers/session-info-mcp-tool-handler.d.ts.map +1 -1
  117. package/lib/common/tools/handlers/session-info-mcp-tool-handler.js +4 -4
  118. package/lib/common/tools/handlers/session-info-mcp-tool-handler.js.map +1 -1
  119. package/lib/common/tools/handlers/set-selection-mcp-tool-handler.d.ts +2 -1
  120. package/lib/common/tools/handlers/set-selection-mcp-tool-handler.d.ts.map +1 -1
  121. package/lib/common/tools/handlers/set-selection-mcp-tool-handler.js +10 -7
  122. package/lib/common/tools/handlers/set-selection-mcp-tool-handler.js.map +1 -1
  123. package/lib/common/tools/handlers/set-view-mcp-tool-handler.d.ts +2 -1
  124. package/lib/common/tools/handlers/set-view-mcp-tool-handler.d.ts.map +1 -1
  125. package/lib/common/tools/handlers/set-view-mcp-tool-handler.js +6 -5
  126. package/lib/common/tools/handlers/set-view-mcp-tool-handler.js.map +1 -1
  127. package/lib/common/tools/handlers/undo-mcp-tool-handler.d.ts +2 -1
  128. package/lib/common/tools/handlers/undo-mcp-tool-handler.d.ts.map +1 -1
  129. package/lib/common/tools/handlers/undo-mcp-tool-handler.js +10 -5
  130. package/lib/common/tools/handlers/undo-mcp-tool-handler.js.map +1 -1
  131. package/lib/common/tools/handlers/validate-diagram-mcp-tool-handler.d.ts +2 -1
  132. package/lib/common/tools/handlers/validate-diagram-mcp-tool-handler.d.ts.map +1 -1
  133. package/lib/common/tools/handlers/validate-diagram-mcp-tool-handler.js +0 -0
  134. package/lib/common/tools/handlers/validate-diagram-mcp-tool-handler.js.map +1 -1
  135. package/lib/common/util/markdown-util.js +1 -2
  136. package/lib/common/util/markdown-util.js.map +1 -1
  137. package/lib/node/modules/node-mcp-server-module.js +8 -8
  138. package/lib/node/modules/node-mcp-server-module.js.map +1 -1
  139. package/package.json +17 -17
  140. package/src/common/modules/abstract-mcp-server-module.ts +3 -3
  141. package/src/common/modules/mcp-diagram-module.ts +1 -1
  142. package/src/common/resources/services/element-types-provider.ts +2 -1
  143. package/src/common/resources/services/mcp-model-serializer.ts +1 -2
  144. package/src/common/server/abstract-mcp-server-launcher.ts +3 -3
  145. package/src/common/server/glsp-mcp-server.ts +2 -1
  146. package/src/common/server/mcp-diagram-handler-dispatcher.ts +56 -5
  147. package/src/common/server/mcp-handler-shared.ts +2 -4
  148. package/src/common/server/mcp-input-schemas.ts +3 -0
  149. package/src/common/server/mcp-mime-types.ts +1 -1
  150. package/src/common/server/mcp-options.ts +1 -1
  151. package/src/common/server/mcp-resource-handler.ts +14 -2
  152. package/src/common/server/mcp-tool-handler.ts +47 -19
  153. package/src/common/tools/handlers/count-elements-mcp-tool-handler.ts +2 -1
  154. package/src/common/tools/handlers/create-edges-mcp-tool-handler.ts +20 -8
  155. package/src/common/tools/handlers/create-nodes-mcp-tool-handler.ts +19 -8
  156. package/src/common/tools/handlers/delete-elements-mcp-tool-handler.ts +2 -1
  157. package/src/common/tools/handlers/diagram-model-mcp-tool-handler.ts +6 -3
  158. package/src/common/tools/handlers/element-types-mcp-tool-handler.ts +6 -3
  159. package/src/common/tools/handlers/get-selection-mcp-tool-handler.ts +2 -1
  160. package/src/common/tools/handlers/layout-mcp-tool-handler.ts +8 -2
  161. package/src/common/tools/handlers/modify-edges-mcp-tool-handler.ts +14 -8
  162. package/src/common/tools/handlers/modify-nodes-mcp-tool-handler.ts +63 -25
  163. package/src/common/tools/handlers/query-elements-mcp-tool-handler.ts +2 -1
  164. package/src/common/tools/handlers/redo-mcp-tool-handler.ts +9 -4
  165. package/src/common/tools/handlers/save-model-mcp-tool-handler.ts +5 -2
  166. package/src/common/tools/handlers/session-info-mcp-tool-handler.ts +2 -1
  167. package/src/common/tools/handlers/set-selection-mcp-tool-handler.ts +8 -5
  168. package/src/common/tools/handlers/set-view-mcp-tool-handler.ts +3 -2
  169. package/src/common/tools/handlers/undo-mcp-tool-handler.ts +9 -4
  170. package/src/common/tools/handlers/validate-diagram-mcp-tool-handler.ts +0 -0
  171. package/src/common/server/abstract-mcp-server-launcher.spec.ts +0 -458
  172. package/src/common/server/glsp-mcp-server.spec.ts +0 -73
  173. package/src/common/server/lru-event-store.spec.ts +0 -121
  174. package/src/common/server/mcp-diagram-handler-dispatcher.spec.ts +0 -231
  175. package/src/common/server/mcp-handler-shared.spec.ts +0 -53
  176. package/src/common/server/mcp-id-alias-service.spec.ts +0 -106
  177. package/src/common/server/mcp-log-level-registry.spec.ts +0 -75
  178. package/src/common/server/mcp-logger.spec.ts +0 -235
  179. package/src/common/server/mcp-progress-reporter.spec.ts +0 -103
  180. package/src/common/server/mcp-tool-handler.spec.ts +0 -182
  181. package/src/common/tools/handlers/count-elements-mcp-tool-handler.spec.ts +0 -101
  182. package/src/common/tools/handlers/create-edges-mcp-tool-handler.spec.ts +0 -198
  183. package/src/common/tools/handlers/create-nodes-mcp-tool-handler.spec.ts +0 -199
  184. package/src/common/tools/handlers/query-elements-mcp-tool-handler.spec.ts +0 -212
  185. package/src/common/tools/handlers/session-info-mcp-tool-handler.spec.ts +0 -152
  186. package/src/common/tools/handlers/set-selection-mcp-tool-handler.spec.ts +0 -120
  187. package/src/common/tools/tool-annotations.spec.ts +0 -141
  188. package/src/node/server/mcp-http-transport-e2e.spec.ts +0 -297
  189. package/src/node/server/node-mcp-server-launcher.spec.ts +0 -184
  190. package/src/node/server/raw-http.spec.ts +0 -59
@@ -1,458 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
-
17
- import {
18
- ClientSessionManager,
19
- InitializeParameters,
20
- InitializeResult,
21
- Logger,
22
- NullLogger,
23
- McpInitializeResult
24
- } from '@eclipse-glsp/server';
25
- import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
26
- import { expect } from 'chai';
27
- import { Container, ContainerModule } from 'inversify';
28
- import * as z from 'zod/v4';
29
- import { AbstractMcpServerLauncher, FullMcpServerConfiguration, TransportEndpoint } from './abstract-mcp-server-launcher';
30
- import { DefaultGLSPMcpServer, GLSPMcpServerFactory } from './glsp-mcp-server';
31
- import { McpDiagramHandlerDispatcher } from './mcp-diagram-handler-dispatcher';
32
- import { DefaultMcpLogLevelRegistry, McpLogLevelRegistry } from './mcp-log-level-registry';
33
- import { McpServerDefaults, McpServerOptions } from './mcp-options';
34
-
35
- class StubDispatcher implements McpDiagramHandlerDispatcher {
36
- harvest(): void {
37
- /* no-op */
38
- }
39
- reset(): void {
40
- /* no-op */
41
- }
42
- hasDiagramTools(): boolean {
43
- return false;
44
- }
45
- hasDiagramResources(): boolean {
46
- return false;
47
- }
48
- hasDiagramPrompts(): boolean {
49
- return false;
50
- }
51
- registerAll(): void {
52
- /* no-op */
53
- }
54
- }
55
-
56
- class StubClientSessionManager {
57
- addListener(): boolean {
58
- return true;
59
- }
60
- removeListener(): boolean {
61
- return true;
62
- }
63
- getSessions(): unknown[] {
64
- return [];
65
- }
66
- getSession(): unknown {
67
- return undefined;
68
- }
69
- getSessionsByType(): unknown[] {
70
- return [];
71
- }
72
- disposeClientSession(): void {
73
- /* no-op */
74
- }
75
- addClientSession(): boolean {
76
- return true;
77
- }
78
- }
79
-
80
- interface RegisteredAuthCapture {
81
- authInfo?: AuthInfo;
82
- invoked: boolean;
83
- }
84
-
85
- /**
86
- * Test subclass: `bindTransport` is a no-op so the test drives `handleRequest` directly against
87
- * synthetic `Request` objects, asserting the routed `Response`.
88
- */
89
- class TestNodeMcpServerLauncher extends AbstractMcpServerLauncher {
90
- readonly authCapture: RegisteredAuthCapture = { invoked: false };
91
-
92
- protected async bindTransport(_config: FullMcpServerConfiguration): Promise<TransportEndpoint> {
93
- return {};
94
- }
95
-
96
- /**
97
- * Register an `echo` tool on each new GLSP MCP server so we can drive a tool/list + tools/call
98
- * round-trip and capture the `extra.authInfo` argument forwarded by the SDK.
99
- */
100
- protected override createGlspMcpServer(config: FullMcpServerConfiguration): DefaultGLSPMcpServer {
101
- const server = super.createGlspMcpServer(config) as DefaultGLSPMcpServer;
102
- const capture = this.authCapture;
103
- server.registerTool(
104
- 'echo',
105
- { description: 'Echoes back the supplied message.', inputSchema: { message: z.string() } },
106
- async ({ message }, extra) => {
107
- capture.invoked = true;
108
- capture.authInfo = extra?.authInfo;
109
- return { content: [{ type: 'text', text: message }] };
110
- }
111
- );
112
- return server;
113
- }
114
- }
115
-
116
- function buildLauncher(): TestNodeMcpServerLauncher {
117
- const container = new Container();
118
- container.load(
119
- new ContainerModule(bind => {
120
- bind(Logger).toConstantValue(new NullLogger());
121
- bind(McpServerOptions).toSelf().inSingletonScope();
122
- bind(McpServerDefaults).toConstantValue({});
123
- bind(McpDiagramHandlerDispatcher).toConstantValue(new StubDispatcher());
124
- bind(McpLogLevelRegistry).to(DefaultMcpLogLevelRegistry).inSingletonScope();
125
- bind(ClientSessionManager).toConstantValue(new StubClientSessionManager());
126
- const factory: GLSPMcpServerFactory = (mcpServer, options) => new DefaultGLSPMcpServer(mcpServer, options, new NullLogger());
127
- bind(GLSPMcpServerFactory).toConstantValue(factory);
128
- bind(TestNodeMcpServerLauncher).toSelf().inSingletonScope();
129
- })
130
- );
131
- return container.get(TestNodeMcpServerLauncher);
132
- }
133
-
134
- const INIT_BODY = {
135
- jsonrpc: '2.0',
136
- id: 1,
137
- method: 'initialize',
138
- params: {
139
- protocolVersion: '2025-06-18',
140
- capabilities: {},
141
- clientInfo: { name: 'spec-client', version: '0.0.1' }
142
- }
143
- };
144
-
145
- async function initLauncher(
146
- launcher: TestNodeMcpServerLauncher,
147
- overrides: Partial<{ route: string; port: number; host: string; name: string; options: Record<string, unknown> }> = {}
148
- ): Promise<InitializeResult> {
149
- const params: InitializeParameters = {
150
- applicationId: 'spec-app',
151
- clientSessionId: 'spec-session',
152
- protocolVersion: '1.0.0',
153
- args: {},
154
- mcpServer: {
155
- port: overrides.port ?? 0,
156
- host: overrides.host ?? '127.0.0.1',
157
- route: overrides.route ?? '/mcp',
158
- name: overrides.name ?? 'spec',
159
- options: overrides.options ?? {}
160
- }
161
- } as unknown as InitializeParameters;
162
- const baseResult: InitializeResult = { protocolVersion: '1.0.0', serverActions: {} } as unknown as InitializeResult;
163
- return launcher.initializeServer({} as never, params, baseResult);
164
- }
165
-
166
- /**
167
- * Read an SSE stream into a list of `data:` payloads. The Web-standard transport streams JSON-RPC
168
- * responses over `text/event-stream`; we only need to find the first complete event for assertions.
169
- */
170
- async function readSseFirstData(response: Response, timeoutMs = 2000): Promise<unknown> {
171
- const reader = response.body?.getReader();
172
- if (!reader) {
173
- throw new Error('Response has no body');
174
- }
175
- const decoder = new TextDecoder();
176
- let buffer = '';
177
- const deadline = Date.now() + timeoutMs;
178
- while (Date.now() < deadline) {
179
- const { value, done } = await reader.read();
180
- if (done) break;
181
- buffer += decoder.decode(value, { stream: true });
182
- for (const block of buffer.split('\n\n')) {
183
- const dataLine = block.split('\n').find(line => line.startsWith('data: '));
184
- if (dataLine) {
185
- reader.cancel().catch(() => undefined);
186
- return JSON.parse(dataLine.slice('data: '.length));
187
- }
188
- }
189
- }
190
- reader.cancel().catch(() => undefined);
191
- throw new Error('Timed out waiting for SSE data event');
192
- }
193
-
194
- function postInit(): Request {
195
- return new Request('http://test/mcp', {
196
- method: 'POST',
197
- headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
198
- body: JSON.stringify(INIT_BODY)
199
- });
200
- }
201
-
202
- function postJson(sessionId: string | undefined, body: unknown): Request {
203
- const headers: Record<string, string> = {
204
- 'content-type': 'application/json',
205
- accept: 'application/json, text/event-stream'
206
- };
207
- if (sessionId !== undefined) headers['mcp-session-id'] = sessionId;
208
- return new Request('http://test/mcp', { method: 'POST', headers, body: JSON.stringify(body) });
209
- }
210
-
211
- describe('AbstractMcpServerLauncher.handleRequest', () => {
212
- let launcher: TestNodeMcpServerLauncher;
213
-
214
- beforeEach(async () => {
215
- launcher = buildLauncher();
216
- await initLauncher(launcher);
217
- });
218
-
219
- afterEach(() => {
220
- launcher.dispose();
221
- });
222
-
223
- it('POST initialize creates a session and returns 200 with mcp-session-id header', async () => {
224
- const response = await launcher.handleRequest(postInit());
225
- expect(response.status).to.equal(200);
226
- expect(response.headers.get('mcp-session-id'), 'must echo a session id after initialize').to.be.a('string');
227
- });
228
-
229
- it('POST to a known session dispatches to its transport (tools/call round-trip)', async () => {
230
- const initResponse = await launcher.handleRequest(postInit());
231
- const sessionId = initResponse.headers.get('mcp-session-id')!;
232
- // Read+discard the init SSE response so the transport is ready for the next POST.
233
- await readSseFirstData(initResponse).catch(() => undefined);
234
-
235
- // Per MCP spec, send `notifications/initialized` after the initialize handshake.
236
- await launcher.handleRequest(postJson(sessionId, { jsonrpc: '2.0', method: 'notifications/initialized', params: {} }));
237
-
238
- const callResponse = await launcher.handleRequest(
239
- postJson(sessionId, {
240
- jsonrpc: '2.0',
241
- id: 2,
242
- method: 'tools/call',
243
- params: { name: 'echo', arguments: { message: 'hello' } }
244
- })
245
- );
246
- expect(callResponse.status).to.equal(200);
247
- const payload = (await readSseFirstData(callResponse)) as { result?: { content?: Array<{ text?: string }> } };
248
- expect(payload.result?.content?.[0]?.text).to.equal('hello');
249
- });
250
-
251
- it('POST without session id, non-initialize body → 400 with JSON-RPC error envelope', async () => {
252
- const response = await launcher.handleRequest(postJson(undefined, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }));
253
- expect(response.status).to.equal(400);
254
- const payload = (await response.json()) as { jsonrpc: string; error: { code: number; message: string }; id: unknown };
255
- expect(payload.jsonrpc).to.equal('2.0');
256
- // eslint-disable-next-line no-null/no-null
257
- expect(payload.id).to.equal(null);
258
- expect(payload.error.code).to.equal(-32000);
259
- expect(payload.error.message).to.match(/No valid session ID/);
260
- });
261
-
262
- it('POST with unknown session id → 404 with JSON-RPC error envelope', async () => {
263
- const response = await launcher.handleRequest(
264
- postJson('not-a-real-session', { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} })
265
- );
266
- expect(response.status).to.equal(404);
267
- const payload = (await response.json()) as { error: { code: number } };
268
- expect(payload.error.code).to.equal(-32001);
269
- });
270
-
271
- it('GET with accept: text/event-stream on a known session → 200, text/event-stream', async () => {
272
- const initResponse = await launcher.handleRequest(postInit());
273
- const sessionId = initResponse.headers.get('mcp-session-id')!;
274
- await readSseFirstData(initResponse).catch(() => undefined);
275
-
276
- const getResponse = await launcher.handleRequest(
277
- new Request('http://test/mcp', {
278
- method: 'GET',
279
- headers: { accept: 'text/event-stream', 'mcp-session-id': sessionId }
280
- })
281
- );
282
- expect(getResponse.status).to.equal(200);
283
- expect(getResponse.headers.get('content-type')).to.match(/text\/event-stream/);
284
- getResponse.body?.cancel().catch(() => undefined);
285
- });
286
-
287
- it('GET on unknown session → 404', async () => {
288
- const response = await launcher.handleRequest(
289
- new Request('http://test/mcp', {
290
- method: 'GET',
291
- headers: { accept: 'text/event-stream', 'mcp-session-id': 'nope' }
292
- })
293
- );
294
- expect(response.status).to.equal(404);
295
- });
296
-
297
- it('DELETE on known session fires onSessionClosed; subsequent POSTs return 404', async () => {
298
- const closedIds: string[] = [];
299
- launcher.onSessionClosed(sessionId => closedIds.push(sessionId));
300
-
301
- const initResponse = await launcher.handleRequest(postInit());
302
- const sessionId = initResponse.headers.get('mcp-session-id')!;
303
- await readSseFirstData(initResponse).catch(() => undefined);
304
-
305
- const deleteResponse = await launcher.handleRequest(
306
- new Request('http://test/mcp', { method: 'DELETE', headers: { 'mcp-session-id': sessionId } })
307
- );
308
- expect(deleteResponse.status).to.be.lessThan(300);
309
- expect(closedIds).to.include(sessionId);
310
-
311
- const followUp = await launcher.handleRequest(postJson(sessionId, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }));
312
- expect(followUp.status).to.equal(404);
313
- });
314
-
315
- it('Non-init POST with unsupported MCP-Protocol-Version → 400 with JSON-RPC error envelope', async () => {
316
- const response = await launcher.handleRequest(
317
- new Request('http://test/mcp', {
318
- method: 'POST',
319
- headers: {
320
- 'content-type': 'application/json',
321
- 'mcp-session-id': 'anything',
322
- 'mcp-protocol-version': '1999-01-01'
323
- },
324
- body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} })
325
- })
326
- );
327
- expect(response.status).to.equal(400);
328
- const payload = (await response.json()) as { error: { code: number; message: string } };
329
- expect(payload.error.code).to.equal(-32000);
330
- expect(payload.error.message).to.match(/Unsupported MCP-Protocol-Version/);
331
- });
332
-
333
- it('POST with no MCP-Protocol-Version header passes the version gate (falls through to session-id check)', async () => {
334
- // Spec: server defaults to `2025-03-26` when the header is absent. We assert by
335
- // sending no session id and observing the 400 "No valid session ID" — proving the
336
- // version gate didn't short-circuit.
337
- const response = await launcher.handleRequest(
338
- new Request('http://test/mcp', {
339
- method: 'POST',
340
- headers: { 'content-type': 'application/json' },
341
- body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} })
342
- })
343
- );
344
- expect(response.status).to.equal(400);
345
- const payload = (await response.json()) as { error: { message: string } };
346
- expect(payload.error.message).to.match(/No valid session ID/);
347
- });
348
-
349
- it('authInfo from handleRequest reaches the registered tool handler extra', async () => {
350
- const initResponse = await launcher.handleRequest(postInit());
351
- const sessionId = initResponse.headers.get('mcp-session-id')!;
352
- await readSseFirstData(initResponse).catch(() => undefined);
353
-
354
- await launcher.handleRequest(postJson(sessionId, { jsonrpc: '2.0', method: 'notifications/initialized', params: {} }));
355
-
356
- const authInfo: AuthInfo = { token: 'bearer-xyz', clientId: 'spec-client', scopes: ['mcp:tools'] };
357
- const callResponse = await launcher.handleRequest(
358
- postJson(sessionId, {
359
- jsonrpc: '2.0',
360
- id: 3,
361
- method: 'tools/call',
362
- params: { name: 'echo', arguments: { message: 'auth-check' } }
363
- }),
364
- { authInfo }
365
- );
366
- await readSseFirstData(callResponse).catch(() => undefined);
367
-
368
- expect(launcher.authCapture.invoked).to.equal(true);
369
- expect(launcher.authCapture.authInfo?.token).to.equal('bearer-xyz');
370
- expect(launcher.authCapture.authInfo?.clientId).to.equal('spec-client');
371
- });
372
-
373
- it('onSessionInitialized fires on initialize', async () => {
374
- const sessionIds: string[] = [];
375
- launcher.onSessionInitialized(session => sessionIds.push(session.sessionId));
376
-
377
- const response = await launcher.handleRequest(postInit());
378
- const sessionId = response.headers.get('mcp-session-id')!;
379
- expect(sessionIds).to.include(sessionId);
380
- });
381
-
382
- it('Route mismatch (POST /other when launcher route is /mcp) → 404', async () => {
383
- const response = await launcher.handleRequest(
384
- new Request('http://test/other', { method: 'POST', body: '{}', headers: { 'content-type': 'application/json' } })
385
- );
386
- expect(response.status).to.equal(404);
387
- });
388
-
389
- it('Unsupported method (PUT /mcp) → 405 with Allow header', async () => {
390
- const response = await launcher.handleRequest(new Request('http://test/mcp', { method: 'PUT' }));
391
- expect(response.status).to.equal(405);
392
- expect(response.headers.get('Allow')).to.equal('POST, GET, DELETE');
393
- });
394
- });
395
-
396
- describe('AbstractMcpServerLauncher · initializeServer lifecycle', () => {
397
- it('Idempotent initializeServer: second call reuses existing config without re-binding', async () => {
398
- const launcher = buildLauncher();
399
- let bindCount = 0;
400
- const original = (launcher as unknown as { bindTransport: () => Promise<TransportEndpoint> }).bindTransport.bind(launcher);
401
- (launcher as unknown as { bindTransport: () => Promise<TransportEndpoint> }).bindTransport = async () => {
402
- bindCount += 1;
403
- return original();
404
- };
405
-
406
- await initLauncher(launcher);
407
- await initLauncher(launcher);
408
- expect(bindCount).to.equal(1);
409
- launcher.dispose();
410
- });
411
-
412
- it('dispose() closes sessions and resets state; a subsequent initializeServer boots cleanly', async () => {
413
- const launcher = buildLauncher();
414
- await initLauncher(launcher);
415
- const firstResponse = await launcher.handleRequest(postInit());
416
- expect(firstResponse.headers.get('mcp-session-id')).to.be.a('string');
417
- await readSseFirstData(firstResponse).catch(() => undefined);
418
-
419
- launcher.dispose();
420
-
421
- // Re-init from the same instance — proves dispose() cleared the singleton state.
422
- await initLauncher(launcher, { route: '/mcp', name: 'spec-2' });
423
- const secondResponse = await launcher.handleRequest(postInit());
424
- expect(secondResponse.status).to.equal(200);
425
- expect(secondResponse.headers.get('mcp-session-id')).to.be.a('string');
426
- launcher.dispose();
427
- });
428
-
429
- it('honors the custom `route` from initialize params', async () => {
430
- const launcher = buildLauncher();
431
- await initLauncher(launcher, { route: '/custom' });
432
-
433
- const wrongRoute = await launcher.handleRequest(postInit());
434
- expect(wrongRoute.status).to.equal(404);
435
-
436
- const correctRoute = await launcher.handleRequest(
437
- new Request('http://test/custom', {
438
- method: 'POST',
439
- headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
440
- body: JSON.stringify(INIT_BODY)
441
- })
442
- );
443
- expect(correctRoute.status).to.equal(200);
444
- launcher.dispose();
445
- });
446
-
447
- it('attaches mcpServer.url to the returned InitializeResult when bindTransport reports a URL', async () => {
448
- const launcher = buildLauncher();
449
- // Override the test subclass's `bindTransport` to report a synthetic URL.
450
- (launcher as unknown as { bindTransport: () => Promise<TransportEndpoint> }).bindTransport = async () => ({
451
- url: 'http://announced.example/mcp'
452
- });
453
- const result = await initLauncher(launcher);
454
- expect(McpInitializeResult.is(result)).to.equal(true);
455
- expect(McpInitializeResult.getServer(result)?.url).to.equal('http://announced.example/mcp');
456
- launcher.dispose();
457
- });
458
- });
@@ -1,73 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
-
17
- import { NullLogger } from '@eclipse-glsp/server';
18
- import { McpServer, RegisteredResource } from '@modelcontextprotocol/sdk/server/mcp.js';
19
- import { expect } from 'chai';
20
- import * as sinon from 'sinon';
21
- import * as z from 'zod/v4';
22
- import { DefaultGLSPMcpServer } from './glsp-mcp-server';
23
-
24
- describe('DefaultGLSPMcpServer', () => {
25
- function makeServer(): { wrapper: DefaultGLSPMcpServer; sdk: McpServer } {
26
- const sdk = new McpServer({ name: 'test', version: '1.0.0' }, { capabilities: {} });
27
- const wrapper = new DefaultGLSPMcpServer(sdk, { dataMode: 'tools' }, new NullLogger());
28
- return { wrapper, sdk };
29
- }
30
-
31
- it('tracks every registerTool call so listTools returns the registered set', () => {
32
- const { wrapper } = makeServer();
33
- wrapper.registerTool('alpha', { description: 'first', inputSchema: { x: z.string() } }, async () => ({ content: [] }));
34
- wrapper.registerTool('beta', { description: 'second' }, async () => ({ content: [] }));
35
-
36
- const names = wrapper.listTools().map(tool => tool.description);
37
- expect(names).to.have.members(['first', 'second']);
38
- expect(wrapper.listTools()).to.have.lengthOf(2);
39
- });
40
-
41
- it('tracks registerResource (static URI) and listResources reflects it by name', () => {
42
- const { wrapper } = makeServer();
43
- wrapper.registerResource('my-resource', 'glsp://test', { title: 'res', mimeType: 'text/plain' }, async () => ({
44
- contents: [{ uri: 'glsp://test', text: 'ok' }]
45
- }));
46
-
47
- const resources = wrapper.listResources();
48
- expect(resources).to.have.lengthOf(1);
49
- expect((resources[0] as RegisteredResource).name).to.equal('my-resource');
50
- expect((resources[0] as RegisteredResource).title).to.equal('res');
51
- });
52
-
53
- it('tracks registerPrompt and listPrompts reflects it by description', () => {
54
- const { wrapper } = makeServer();
55
- wrapper.registerPrompt('describe', { description: 'desc' }, async () => ({ messages: [] }));
56
-
57
- const prompts = wrapper.listPrompts();
58
- expect(prompts).to.have.lengthOf(1);
59
- expect(prompts[0].description).to.equal('desc');
60
- });
61
-
62
- it('getRawServer() returns the exact SDK instance passed in (escape hatch identity)', () => {
63
- const { wrapper, sdk } = makeServer();
64
- expect(wrapper.getRawServer()).to.equal(sdk);
65
- });
66
-
67
- it('dispose() invokes close() on the underlying SDK server', () => {
68
- const { wrapper, sdk } = makeServer();
69
- const closeSpy = sinon.spy(sdk, 'close');
70
- wrapper.dispose();
71
- expect(closeSpy.calledOnce).to.be.true;
72
- });
73
- });
@@ -1,121 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
-
17
- import { Logger } from '@eclipse-glsp/server';
18
- import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
19
- import { expect } from 'chai';
20
- import { LruEventStore } from './lru-event-store';
21
-
22
- function jsonRpc(id: number): JSONRPCMessage {
23
- return { jsonrpc: '2.0', id, result: { value: id } };
24
- }
25
-
26
- describe('LruEventStore', () => {
27
- it('rejects an invalid limit (< 1)', () => {
28
- expect(() => new LruEventStore(0)).to.throw(/limit must be >= 1/);
29
- expect(() => new LruEventStore(-5)).to.throw(/limit must be >= 1/);
30
- });
31
-
32
- it('storeEvent returns a streamId-prefixed event id', async () => {
33
- const store = new LruEventStore();
34
- const id = await store.storeEvent('stream-A', jsonRpc(1));
35
- expect(id.split('_')[0]).to.equal('stream-A');
36
- });
37
-
38
- it('evicts the oldest event once the cap is exceeded', async () => {
39
- const store = new LruEventStore(3);
40
- const id1 = await store.storeEvent('s', jsonRpc(1));
41
- const id2 = await store.storeEvent('s', jsonRpc(2));
42
- await store.storeEvent('s', jsonRpc(3));
43
-
44
- expect(store.size).to.equal(3);
45
-
46
- // Adding a 4th event evicts the oldest (id1).
47
- const id4 = await store.storeEvent('s', jsonRpc(4));
48
- expect(store.size).to.equal(3);
49
-
50
- // Replay since id1 → can't find it (evicted) → returns ''.
51
- const replayed: string[] = [];
52
- const streamId = await store.replayEventsAfter(id1, {
53
- send: async eventId => {
54
- replayed.push(eventId);
55
- }
56
- });
57
- expect(streamId).to.equal(''); // id1 has been evicted
58
- expect(replayed).to.have.lengthOf(0);
59
-
60
- // Replay since id2 still works — id3 (still in store) and id4 are sent in order.
61
- const replayedFromId2: string[] = [];
62
- await store.replayEventsAfter(id2, {
63
- send: async eventId => {
64
- replayedFromId2.push(eventId);
65
- }
66
- });
67
- expect(replayedFromId2).to.have.lengthOf(2);
68
- expect(replayedFromId2[1]).to.equal(id4);
69
- });
70
-
71
- it('replays only events from the same stream after the lastEventId', async () => {
72
- const store = new LruEventStore();
73
- const a1 = await store.storeEvent('A', jsonRpc(1));
74
- await store.storeEvent('B', jsonRpc(2)); // different stream — must be skipped
75
- const a3 = await store.storeEvent('A', jsonRpc(3));
76
- const a4 = await store.storeEvent('A', jsonRpc(4));
77
-
78
- const replayed: Array<{ id: string; msg: JSONRPCMessage }> = [];
79
- const streamId = await store.replayEventsAfter(a1, {
80
- send: async (id, msg) => {
81
- replayed.push({ id, msg });
82
- }
83
- });
84
-
85
- expect(streamId).to.equal('A');
86
- expect(replayed.map(e => e.id)).to.deep.equal([a3, a4]);
87
- });
88
-
89
- it('returns "" when lastEventId is unknown', async () => {
90
- const store = new LruEventStore();
91
- await store.storeEvent('A', jsonRpc(1));
92
-
93
- const streamId = await store.replayEventsAfter('A_999_xxx', {
94
- send: async () => {
95
- throw new Error('should not be called');
96
- }
97
- });
98
-
99
- expect(streamId).to.equal('');
100
- });
101
-
102
- it('logs a cap-eviction warn when replaying an evicted last-event-id', async () => {
103
- const warns: string[] = [];
104
- const logger = { warn: (message: string) => warns.push(message) } as unknown as Logger;
105
- const store = new LruEventStore(2, logger);
106
- const evictedId = await store.storeEvent('s', jsonRpc(1));
107
- await store.storeEvent('s', jsonRpc(2));
108
- await store.storeEvent('s', jsonRpc(3));
109
-
110
- const streamId = await store.replayEventsAfter(evictedId, {
111
- send: async () => {
112
- throw new Error('should not be called');
113
- }
114
- });
115
-
116
- expect(streamId).to.equal('');
117
- expect(warns).to.have.lengthOf(1);
118
- expect(warns[0]).to.match(/Replay miss/);
119
- expect(warns[0]).to.include('Cap is 2');
120
- });
121
- });