@eclipse-glsp/server-mcp 2.8.0-next.0 → 2.8.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) 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/prompts/handlers/describe-diagram-mcp-prompt-handler.js +3 -2
  8. package/lib/common/prompts/handlers/describe-diagram-mcp-prompt-handler.js.map +1 -1
  9. package/lib/common/prompts/handlers/suggest-improvements-mcp-prompt-handler.js +3 -2
  10. package/lib/common/prompts/handlers/suggest-improvements-mcp-prompt-handler.js.map +1 -1
  11. package/lib/common/resources/handlers/diagram-png-mcp-resource-handler.js +5 -4
  12. package/lib/common/resources/handlers/diagram-png-mcp-resource-handler.js.map +1 -1
  13. package/lib/common/resources/handlers/diagram-svg-mcp-resource-handler.js +4 -3
  14. package/lib/common/resources/handlers/diagram-svg-mcp-resource-handler.js.map +1 -1
  15. package/lib/common/resources/services/element-types-provider.d.ts.map +1 -1
  16. package/lib/common/resources/services/element-types-provider.js +1 -2
  17. package/lib/common/resources/services/element-types-provider.js.map +1 -1
  18. package/lib/common/resources/services/mcp-model-serializer.d.ts +1 -2
  19. package/lib/common/resources/services/mcp-model-serializer.d.ts.map +1 -1
  20. package/lib/common/resources/services/mcp-model-serializer.js +3 -5
  21. package/lib/common/resources/services/mcp-model-serializer.js.map +1 -1
  22. package/lib/common/server/abstract-mcp-server-launcher.d.ts +3 -4
  23. package/lib/common/server/abstract-mcp-server-launcher.d.ts.map +1 -1
  24. package/lib/common/server/abstract-mcp-server-launcher.js +9 -14
  25. package/lib/common/server/abstract-mcp-server-launcher.js.map +1 -1
  26. package/lib/common/server/glsp-mcp-server.d.ts.map +1 -1
  27. package/lib/common/server/glsp-mcp-server.js +2 -2
  28. package/lib/common/server/glsp-mcp-server.js.map +1 -1
  29. package/lib/common/server/lru-event-store.js +2 -3
  30. package/lib/common/server/lru-event-store.js.map +1 -1
  31. package/lib/common/server/mcp-diagram-handler-dispatcher.js +4 -8
  32. package/lib/common/server/mcp-diagram-handler-dispatcher.js.map +1 -1
  33. package/lib/common/server/mcp-handler-shared.d.ts +1 -2
  34. package/lib/common/server/mcp-handler-shared.d.ts.map +1 -1
  35. package/lib/common/server/mcp-handler-shared.js +1 -1
  36. package/lib/common/server/mcp-handler-shared.js.map +1 -1
  37. package/lib/common/server/mcp-id-alias-service.js +1 -2
  38. package/lib/common/server/mcp-id-alias-service.js.map +1 -1
  39. package/lib/common/server/mcp-log-level-registry.js +10 -10
  40. package/lib/common/server/mcp-log-level-registry.js.map +1 -1
  41. package/lib/common/server/mcp-mime-types.d.ts +1 -1
  42. package/lib/common/server/mcp-mime-types.d.ts.map +1 -1
  43. package/lib/common/server/mcp-options.d.ts +1 -1
  44. package/lib/common/server/mcp-options.d.ts.map +1 -1
  45. package/lib/common/server/mcp-progress-reporter.js +1 -2
  46. package/lib/common/server/mcp-progress-reporter.js.map +1 -1
  47. package/lib/common/server/mcp-resource-handler.js +5 -9
  48. package/lib/common/server/mcp-resource-handler.js.map +1 -1
  49. package/lib/common/server/mcp-tool-handler.js +1 -2
  50. package/lib/common/server/mcp-tool-handler.js.map +1 -1
  51. package/lib/common/tools/handlers/count-elements-mcp-tool-handler.js +4 -4
  52. package/lib/common/tools/handlers/count-elements-mcp-tool-handler.js.map +1 -1
  53. package/lib/common/tools/handlers/create-edges-mcp-tool-handler.js +4 -3
  54. package/lib/common/tools/handlers/create-edges-mcp-tool-handler.js.map +1 -1
  55. package/lib/common/tools/handlers/create-nodes-mcp-tool-handler.js +5 -5
  56. package/lib/common/tools/handlers/create-nodes-mcp-tool-handler.js.map +1 -1
  57. package/lib/common/tools/handlers/delete-elements-mcp-tool-handler.js +3 -2
  58. package/lib/common/tools/handlers/delete-elements-mcp-tool-handler.js.map +1 -1
  59. package/lib/common/tools/handlers/diagram-model-mcp-tool-handler.js +3 -2
  60. package/lib/common/tools/handlers/diagram-model-mcp-tool-handler.js.map +1 -1
  61. package/lib/common/tools/handlers/element-types-mcp-tool-handler.js +4 -3
  62. package/lib/common/tools/handlers/element-types-mcp-tool-handler.js.map +1 -1
  63. package/lib/common/tools/handlers/get-selection-mcp-tool-handler.js +3 -2
  64. package/lib/common/tools/handlers/get-selection-mcp-tool-handler.js.map +1 -1
  65. package/lib/common/tools/handlers/layout-mcp-tool-handler.js +3 -2
  66. package/lib/common/tools/handlers/layout-mcp-tool-handler.js.map +1 -1
  67. package/lib/common/tools/handlers/modify-edges-mcp-tool-handler.js +3 -2
  68. package/lib/common/tools/handlers/modify-edges-mcp-tool-handler.js.map +1 -1
  69. package/lib/common/tools/handlers/modify-nodes-mcp-tool-handler.js +6 -6
  70. package/lib/common/tools/handlers/modify-nodes-mcp-tool-handler.js.map +1 -1
  71. package/lib/common/tools/handlers/query-elements-mcp-tool-handler.js +7 -7
  72. package/lib/common/tools/handlers/query-elements-mcp-tool-handler.js.map +1 -1
  73. package/lib/common/tools/handlers/redo-mcp-tool-handler.js +3 -2
  74. package/lib/common/tools/handlers/redo-mcp-tool-handler.js.map +1 -1
  75. package/lib/common/tools/handlers/save-model-mcp-tool-handler.js +3 -2
  76. package/lib/common/tools/handlers/save-model-mcp-tool-handler.js.map +1 -1
  77. package/lib/common/tools/handlers/session-info-mcp-tool-handler.js +4 -4
  78. package/lib/common/tools/handlers/session-info-mcp-tool-handler.js.map +1 -1
  79. package/lib/common/tools/handlers/set-selection-mcp-tool-handler.js +5 -4
  80. package/lib/common/tools/handlers/set-selection-mcp-tool-handler.js.map +1 -1
  81. package/lib/common/tools/handlers/set-view-mcp-tool-handler.js +5 -4
  82. package/lib/common/tools/handlers/set-view-mcp-tool-handler.js.map +1 -1
  83. package/lib/common/tools/handlers/undo-mcp-tool-handler.js +3 -2
  84. package/lib/common/tools/handlers/undo-mcp-tool-handler.js.map +1 -1
  85. package/lib/common/tools/handlers/validate-diagram-mcp-tool-handler.js +0 -0
  86. package/lib/common/tools/handlers/validate-diagram-mcp-tool-handler.js.map +1 -1
  87. package/lib/common/util/markdown-util.js +1 -2
  88. package/lib/common/util/markdown-util.js.map +1 -1
  89. package/lib/node/modules/node-mcp-server-module.js +8 -8
  90. package/lib/node/modules/node-mcp-server-module.js.map +1 -1
  91. package/package.json +17 -17
  92. package/src/common/modules/abstract-mcp-server-module.ts +3 -3
  93. package/src/common/resources/services/element-types-provider.ts +2 -1
  94. package/src/common/resources/services/mcp-model-serializer.ts +1 -2
  95. package/src/common/server/abstract-mcp-server-launcher.ts +3 -3
  96. package/src/common/server/glsp-mcp-server.ts +2 -1
  97. package/src/common/server/mcp-handler-shared.ts +2 -4
  98. package/src/common/server/mcp-mime-types.ts +1 -1
  99. package/src/common/server/mcp-options.ts +1 -1
  100. package/src/common/server/mcp-resource-handler.ts +1 -1
  101. package/src/common/server/abstract-mcp-server-launcher.spec.ts +0 -458
  102. package/src/common/server/glsp-mcp-server.spec.ts +0 -73
  103. package/src/common/server/lru-event-store.spec.ts +0 -121
  104. package/src/common/server/mcp-diagram-handler-dispatcher.spec.ts +0 -231
  105. package/src/common/server/mcp-handler-shared.spec.ts +0 -53
  106. package/src/common/server/mcp-id-alias-service.spec.ts +0 -106
  107. package/src/common/server/mcp-log-level-registry.spec.ts +0 -75
  108. package/src/common/server/mcp-logger.spec.ts +0 -235
  109. package/src/common/server/mcp-progress-reporter.spec.ts +0 -103
  110. package/src/common/server/mcp-tool-handler.spec.ts +0 -182
  111. package/src/common/tools/handlers/count-elements-mcp-tool-handler.spec.ts +0 -101
  112. package/src/common/tools/handlers/create-edges-mcp-tool-handler.spec.ts +0 -198
  113. package/src/common/tools/handlers/create-nodes-mcp-tool-handler.spec.ts +0 -199
  114. package/src/common/tools/handlers/query-elements-mcp-tool-handler.spec.ts +0 -212
  115. package/src/common/tools/handlers/session-info-mcp-tool-handler.spec.ts +0 -152
  116. package/src/common/tools/handlers/set-selection-mcp-tool-handler.spec.ts +0 -120
  117. package/src/common/tools/tool-annotations.spec.ts +0 -141
  118. package/src/node/server/mcp-http-transport-e2e.spec.ts +0 -297
  119. package/src/node/server/node-mcp-server-launcher.spec.ts +0 -184
  120. package/src/node/server/raw-http.spec.ts +0 -59
@@ -1,297 +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 { ClientSessionManager, InitializeParameters, InitializeResult, Logger, NullLogger } from '@eclipse-glsp/server';
18
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
19
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
20
- import { expect } from 'chai';
21
- import { Container, ContainerModule } from 'inversify';
22
- import * as z from 'zod/v4';
23
- import { FullMcpServerConfiguration } from '../../common/server/abstract-mcp-server-launcher';
24
- import { DefaultGLSPMcpServer, GLSPMcpServerFactory } from '../../common/server/glsp-mcp-server';
25
- import { McpDiagramHandlerDispatcher } from '../../common/server/mcp-diagram-handler-dispatcher';
26
- import { DefaultMcpLogLevelRegistry, McpLogLevelRegistry } from '../../common/server/mcp-log-level-registry';
27
- import { McpServerDefaults, McpServerOptions } from '../../common/server/mcp-options';
28
- import { NodeMcpServerLauncher } from './node-mcp-server-launcher';
29
- import { rawHttpRequest } from './raw-http.spec';
30
-
31
- class StubDispatcher implements McpDiagramHandlerDispatcher {
32
- harvest(): void {
33
- /* no-op */
34
- }
35
- reset(): void {
36
- /* no-op */
37
- }
38
- hasDiagramTools(): boolean {
39
- return false;
40
- }
41
- hasDiagramResources(): boolean {
42
- return false;
43
- }
44
- hasDiagramPrompts(): boolean {
45
- return false;
46
- }
47
- registerAll(): void {
48
- /* no-op */
49
- }
50
- }
51
-
52
- class StubClientSessionManager {
53
- addListener(): boolean {
54
- return true;
55
- }
56
- removeListener(): boolean {
57
- return true;
58
- }
59
- getSessions(): unknown[] {
60
- return [];
61
- }
62
- getSession(): unknown {
63
- return undefined;
64
- }
65
- getSessionsByType(): unknown[] {
66
- return [];
67
- }
68
- }
69
-
70
- /**
71
- * Test subclass that registers a single `echo` tool on every fresh GLSP MCP server. We use this
72
- * to drive a real MCP SDK client through a full handshake without depending on the workflow
73
- * server's diagram handler stack.
74
- */
75
- class EchoLauncher extends NodeMcpServerLauncher {
76
- protected override createGlspMcpServer(config: FullMcpServerConfiguration): DefaultGLSPMcpServer {
77
- const server = super.createGlspMcpServer(config) as DefaultGLSPMcpServer;
78
- server.registerTool(
79
- 'echo',
80
- { description: 'Returns the supplied message verbatim.', inputSchema: { message: z.string() } },
81
- async ({ message }) => ({ content: [{ type: 'text', text: message }] })
82
- );
83
- return server;
84
- }
85
- }
86
-
87
- function buildLauncher(optionValues: McpServerOptions['values'] = {}): EchoLauncher {
88
- const container = new Container();
89
- container.load(
90
- new ContainerModule(bind => {
91
- bind(Logger).toConstantValue(new NullLogger());
92
- const opts = new McpServerOptions();
93
- opts.values = optionValues;
94
- bind(McpServerOptions).toConstantValue(opts);
95
- bind(McpServerDefaults).toConstantValue(optionValues);
96
- bind(McpDiagramHandlerDispatcher).toConstantValue(new StubDispatcher());
97
- bind(McpLogLevelRegistry).to(DefaultMcpLogLevelRegistry).inSingletonScope();
98
- bind(ClientSessionManager).toConstantValue(new StubClientSessionManager());
99
- const factory: GLSPMcpServerFactory = (mcpServer, options) => new DefaultGLSPMcpServer(mcpServer, options, new NullLogger());
100
- bind(GLSPMcpServerFactory).toConstantValue(factory);
101
- bind(EchoLauncher).toSelf().inSingletonScope();
102
- })
103
- );
104
- return container.get(EchoLauncher);
105
- }
106
-
107
- async function startLauncher(
108
- launcher: EchoLauncher,
109
- options: { port?: number; host?: string; route?: string; allowedHosts?: string[]; allowedOrigins?: string[] } = {}
110
- ): Promise<string> {
111
- const params: InitializeParameters = {
112
- applicationId: 'spec-app',
113
- clientSessionId: 'spec-session',
114
- protocolVersion: '1.0.0',
115
- args: {},
116
- mcpServer: {
117
- port: options.port ?? 0,
118
- route: options.route ?? '/mcp',
119
- name: 'spec',
120
- options: {}
121
- }
122
- } as unknown as InitializeParameters;
123
- // Inject the deploy-only fields directly on the merged options holder.
124
- launcher['mcpDefaults'] = {
125
- host: options.host ?? '127.0.0.1',
126
- allowedHosts: options.allowedHosts,
127
- allowedOrigins: options.allowedOrigins
128
- };
129
- const baseResult: InitializeResult = { protocolVersion: '1.0.0', serverActions: {} } as unknown as InitializeResult;
130
- const result = await launcher.initializeServer({} as never, params, baseResult);
131
- const url = (result as unknown as { mcpServer?: { url?: string } }).mcpServer?.url;
132
- if (!url) throw new Error('launcher did not announce a URL');
133
- return url;
134
- }
135
-
136
- /**
137
- * End-to-end smoke test: boot the launcher (which spins up the Hono-on-Node listener),
138
- * connect a real SDK MCP client over Streamable HTTP, and exercise a tool round-trip (`echo`).
139
- */
140
- describe('NodeMcpServerLauncher (e2e — real MCP SDK client over HTTP)', () => {
141
- let launcher: EchoLauncher | undefined;
142
- let client: Client | undefined;
143
-
144
- async function safeClose(target: Client | undefined): Promise<void> {
145
- if (!target) return;
146
- try {
147
- await target.close();
148
- } catch {
149
- /* ignore — best effort */
150
- }
151
- }
152
-
153
- afterEach(async () => {
154
- await safeClose(client);
155
- client = undefined;
156
- launcher?.dispose();
157
- launcher = undefined;
158
- });
159
-
160
- it('round-trips tools/list and tools/call against a registered echo tool', async () => {
161
- launcher = buildLauncher();
162
- const url = await startLauncher(launcher);
163
- expect(url, 'launcher must announce a URL after initializeServer').to.be.a('string');
164
-
165
- client = new Client({ name: 'wn-058-test-client', version: '1.0.0' });
166
- await client.connect(new StreamableHTTPClientTransport(new URL(url)));
167
-
168
- const tools = await client.listTools();
169
- expect(tools.tools.map(tool => tool.name)).to.include('echo');
170
-
171
- const result = await client.callTool({ name: 'echo', arguments: { message: 'hello GLSP' } });
172
- expect(result.isError).to.not.equal(true);
173
- expect(result.content).to.have.lengthOf(1);
174
- const [block] = result.content as Array<{ type: string; text?: string }>;
175
- expect(block.type).to.equal('text');
176
- expect(block.text).to.equal('hello GLSP');
177
- });
178
-
179
- it('DELETE /mcp terminates the session; subsequent POSTs with the same id return 404 (§ #5)', async () => {
180
- launcher = buildLauncher();
181
- const url = await startLauncher(launcher);
182
-
183
- client = new Client({ name: 'delete-smoke-test', version: '1.0.0' });
184
- const clientTransport = new StreamableHTTPClientTransport(new URL(url));
185
- await client.connect(clientTransport);
186
- const sessionId = clientTransport.sessionId;
187
- expect(sessionId, 'SDK transport should expose the minted session id after initialize').to.be.a('string');
188
-
189
- const port = Number(new URL(url).port);
190
-
191
- const deleteRes = await rawHttpRequest(port, 'DELETE', { 'mcp-session-id': sessionId! });
192
- expect(deleteRes.status, 'DELETE should succeed for an active session').to.be.lessThan(300);
193
-
194
- const followUp = await rawHttpRequest(
195
- port,
196
- 'POST',
197
- { 'mcp-session-id': sessionId! },
198
- { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }
199
- );
200
- expect(followUp.status).to.equal(404);
201
- const payload = JSON.parse(followUp.body);
202
- expect(payload.error.code).to.equal(-32001);
203
- });
204
- });
205
-
206
- describe('NodeMcpServerLauncher (raw HTTP wire validation)', () => {
207
- let launcher: EchoLauncher | undefined;
208
-
209
- afterEach(() => {
210
- launcher?.dispose();
211
- launcher = undefined;
212
- });
213
-
214
- async function startWith(values: McpServerOptions['values']): Promise<number> {
215
- launcher = buildLauncher(values);
216
- const url = await startLauncher(launcher, { allowedHosts: values.allowedHosts, allowedOrigins: values.allowedOrigins });
217
- return Number(new URL(url).port);
218
- }
219
-
220
- it('binds an HTTP listener on a resolvable loopback port', async () => {
221
- launcher = buildLauncher();
222
- const url = await startLauncher(launcher);
223
- expect(url).to.match(/^http:\/\/(127\.0\.0\.1|localhost):\d+\/mcp$/);
224
- });
225
-
226
- it('rejects a non-initialize POST without an Mcp-Session-Id header with 400 (§ #2)', async () => {
227
- const port = await startWith({});
228
- const res = await rawHttpRequest(port, 'POST', {}, { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} });
229
- expect(res.status).to.equal(400);
230
- const payload = JSON.parse(res.body);
231
- expect(payload.error.code).to.equal(-32000);
232
- expect(payload.error.message).to.match(/No valid session ID/);
233
- });
234
-
235
- it('rejects a POST with an unknown Mcp-Session-Id with 404 (§ #3)', async () => {
236
- const port = await startWith({});
237
- const res = await rawHttpRequest(
238
- port,
239
- 'POST',
240
- { 'mcp-session-id': 'no-such-session' },
241
- { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }
242
- );
243
- expect(res.status).to.equal(404);
244
- const payload = JSON.parse(res.body);
245
- expect(payload.error.code).to.equal(-32001);
246
- });
247
-
248
- it('rejects a non-initialize POST whose MCP-Protocol-Version is unsupported with HTTP 400', async () => {
249
- const port = await startWith({});
250
- const res = await rawHttpRequest(
251
- port,
252
- 'POST',
253
- { 'mcp-session-id': 'doesnt-matter', 'mcp-protocol-version': '1999-01-01' },
254
- { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }
255
- );
256
- expect(res.status).to.equal(400);
257
- const payload = JSON.parse(res.body);
258
- expect(payload.error.message).to.match(/Unsupported MCP-Protocol-Version/);
259
- });
260
-
261
- it('rejects requests with a Host header outside the allowlist', async () => {
262
- const port = await startWith({ allowedHosts: ['127.0.0.1', 'localhost'] });
263
- // POST init body so we go past session-id validation into the SDK transport's host check.
264
- const res = await rawHttpRequest(
265
- port,
266
- 'POST',
267
- { Host: 'evil.example' },
268
- {
269
- jsonrpc: '2.0',
270
- id: 1,
271
- method: 'initialize',
272
- params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'x', version: '1' } }
273
- }
274
- );
275
- expect(res.status).to.equal(403);
276
- });
277
-
278
- it('rejects start when the requested port is already in use with an actionable error', async () => {
279
- const firstLauncher = buildLauncher();
280
- const url = await startLauncher(firstLauncher);
281
- const taken = Number(new URL(url).port);
282
- const secondLauncher = buildLauncher();
283
- let error: Error | undefined;
284
- try {
285
- await startLauncher(secondLauncher, { port: taken });
286
- } catch (err: unknown) {
287
- error = err as Error;
288
- } finally {
289
- firstLauncher.dispose();
290
- secondLauncher.dispose();
291
- }
292
- expect(error, 'expected initializeServer to reject').to.not.equal(undefined);
293
- expect(error!.message).to.match(/127\.0\.0\.1:\d+/);
294
- expect(error!.message).to.include('mcpServer.port');
295
- expect(error!.message).to.match(/already in use/);
296
- });
297
- });
@@ -1,184 +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 { McpServerInitOptions } from '@eclipse-glsp/protocol';
18
- import { expect } from 'chai';
19
- import { version as packageVersion } from '../../../package.json';
20
- import { SERVER_VERSION, pickInitOptions } from '../../common/server/abstract-mcp-server-launcher';
21
- import { NodeMcpServerLauncher, assertLoopbackOrAcknowledged, isLoopbackHost } from './node-mcp-server-launcher';
22
-
23
- describe('NodeMcpServerLauncher · SERVER_VERSION', () => {
24
- it('matches the package.json version (no stale literal)', () => {
25
- // Regression guard: the launcher used to hard-code '1.0.0'. Pull from package.json so
26
- // adopters and MCP clients can tell builds apart via the `serverInfo.version` handshake
27
- // field.
28
- expect(SERVER_VERSION).to.equal(packageVersion);
29
- });
30
- });
31
-
32
- describe('NodeMcpServerLauncher · buildCapabilities', () => {
33
- /**
34
- * Sidestep DI: build a stub whose shape matches the fields `buildCapabilities` reads, then
35
- * invoke the prototype method against it. The method is protected, so we cast through.
36
- */
37
- function buildCaps(
38
- args: {
39
- toolHandlers?: unknown[];
40
- promptHandlers?: unknown[];
41
- resourceHandlers?: unknown[];
42
- hasDiagramTools?: boolean;
43
- hasDiagramPrompts?: boolean;
44
- hasDiagramResources?: boolean;
45
- },
46
- resourcesAsResources: boolean
47
- ): Record<string, unknown> {
48
- const stub = {
49
- toolHandlers: args.toolHandlers ?? [],
50
- promptHandlers: args.promptHandlers ?? [],
51
- resourceHandlers: args.resourceHandlers ?? [],
52
- dispatcher: {
53
- hasDiagramTools: () => args.hasDiagramTools ?? false,
54
- hasDiagramPrompts: () => args.hasDiagramPrompts ?? false,
55
- hasDiagramResources: () => args.hasDiagramResources ?? false
56
- }
57
- };
58
- const proto = NodeMcpServerLauncher.prototype as unknown as {
59
- buildCapabilities(this: typeof stub, resourcesAsResources: boolean): Record<string, unknown>;
60
- };
61
- return proto.buildCapabilities.call(stub, resourcesAsResources);
62
- }
63
-
64
- it('omits `tools`, `resources`, and `prompts` when nothing is bound (regression: resources/list -32601)', () => {
65
- const caps = buildCaps({}, /* resourcesAsResources */ true);
66
- expect(caps).to.have.property('logging');
67
- expect(caps).to.not.have.property('tools');
68
- expect(caps).to.not.have.property('resources');
69
- expect(caps).to.not.have.property('prompts');
70
- });
71
-
72
- it('declares `tools` with listChanged: false when at least one tool handler binds', () => {
73
- const caps = buildCaps({ toolHandlers: [{}] }, true);
74
- expect(caps.tools).to.deep.equal({ listChanged: false });
75
- expect(caps).to.not.have.property('resources');
76
- expect(caps).to.not.have.property('prompts');
77
- });
78
-
79
- it('declares `prompts` when at least one prompt handler binds (server- or diagram-scope)', () => {
80
- expect(buildCaps({ promptHandlers: [{}] }, true).prompts).to.deep.equal({ listChanged: false });
81
- expect(buildCaps({ hasDiagramPrompts: true }, true).prompts).to.deep.equal({ listChanged: false });
82
- });
83
-
84
- it('declares `resources` only in dataMode=resources; otherwise resources count toward `tools`', () => {
85
- // Diagram-scope resources mutate per GLSP session add/remove → `listChanged: true` is honest.
86
- const asResources = buildCaps({ hasDiagramResources: true }, true);
87
- expect(asResources.resources).to.deep.equal({ listChanged: true });
88
- expect(asResources).to.not.have.property('tools');
89
-
90
- const asTools = buildCaps({ hasDiagramResources: true }, false);
91
- expect(asTools.tools).to.deep.equal({ listChanged: false });
92
- expect(asTools).to.not.have.property('resources');
93
- });
94
-
95
- it('keeps resources.listChanged: false when only server-scope resources are bound (catalog static)', () => {
96
- const caps = buildCaps({ resourceHandlers: [{}] }, true);
97
- expect(caps.resources).to.deep.equal({ listChanged: false });
98
- });
99
- });
100
-
101
- describe('NodeMcpServerLauncher · isLoopbackHost', () => {
102
- it('treats 127.0.0.0/8, localhost, and ::1 as loopback', () => {
103
- expect(isLoopbackHost('127.0.0.1')).to.equal(true);
104
- expect(isLoopbackHost('127.55.0.1')).to.equal(true);
105
- expect(isLoopbackHost('localhost')).to.equal(true);
106
- expect(isLoopbackHost('::1')).to.equal(true);
107
- });
108
-
109
- it('treats unspecified, LAN, and public addresses as non-loopback', () => {
110
- expect(isLoopbackHost('0.0.0.0')).to.equal(false);
111
- expect(isLoopbackHost('::')).to.equal(false);
112
- expect(isLoopbackHost('192.168.1.1')).to.equal(false);
113
- expect(isLoopbackHost('10.0.0.1')).to.equal(false);
114
- expect(isLoopbackHost('203.0.113.5')).to.equal(false);
115
- });
116
- });
117
-
118
- describe('NodeMcpServerLauncher · assertLoopbackOrAcknowledged (auth footgun)', () => {
119
- it('passes silently for a loopback bind without acknowledgement', () => {
120
- expect(() => assertLoopbackOrAcknowledged('127.0.0.1', undefined)).to.not.throw();
121
- expect(() => assertLoopbackOrAcknowledged('localhost', undefined)).to.not.throw();
122
- });
123
-
124
- it('throws an actionable error for a non-loopback bind without acknowledgement', () => {
125
- expect(() => assertLoopbackOrAcknowledged('0.0.0.0', undefined))
126
- .to.throw(Error)
127
- .with.property('message')
128
- .that.matches(/Refusing to bind/)
129
- .and.matches(/0\.0\.0\.0/)
130
- .and.matches(/acknowledgedNoAuth/);
131
- });
132
-
133
- it('passes for a non-loopback bind when acknowledgedNoAuth is true', () => {
134
- expect(() => assertLoopbackOrAcknowledged('0.0.0.0', true)).to.not.throw();
135
- expect(() => assertLoopbackOrAcknowledged('192.168.1.50', true)).to.not.throw();
136
- });
137
-
138
- it('still throws for a non-loopback bind when acknowledgedNoAuth is false (explicit denial)', () => {
139
- expect(() => assertLoopbackOrAcknowledged('0.0.0.0', false)).to.throw(/Refusing to bind/);
140
- });
141
- });
142
-
143
- // Compile-time exhaustiveness check — fails the build if `McpServerInitOptions` grows or
144
- // shrinks while the `pickInitOptions` destructure doesn't follow.
145
- type _Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
146
- function assertPickInitKeysExhaustive(
147
- _: _Equal<keyof import('@eclipse-glsp/server').McpServerInitOptions, 'dataMode' | 'agentPersona' | 'eventStoreLimit'>
148
- ): void {
149
- /* type-only */
150
- }
151
- assertPickInitKeysExhaustive(true);
152
-
153
- describe('NodeMcpServerLauncher · pickInitOptions (deploy/init split — defense-in-depth)', () => {
154
- it('passes through every allowed init-side field unchanged', () => {
155
- const picked = pickInitOptions({ dataMode: 'resources', agentPersona: 'X', eventStoreLimit: 50 });
156
- expect(picked).to.deep.equal({ dataMode: 'resources', agentPersona: 'X', eventStoreLimit: 50 });
157
- });
158
-
159
- it('omits init-side fields that the caller did not set (no `undefined` sneak-through)', () => {
160
- const picked = pickInitOptions({ dataMode: 'tools' });
161
- expect(picked).to.deep.equal({ dataMode: 'tools' });
162
- expect(picked).to.not.have.property('agentPersona');
163
- expect(picked).to.not.have.property('eventStoreLimit');
164
- });
165
-
166
- it('strips deploy-only keys smuggled in via JSON wire payload (host, allowedHosts, allowedOrigins, acknowledgedNoAuth)', () => {
167
- // Simulate a malicious/malformed wire payload: the static type rules these out, but
168
- // JSON parsing does not, so the destructure-pick must drop them.
169
- const wirePayload = JSON.parse(`{
170
- "dataMode": "tools",
171
- "host": "0.0.0.0",
172
- "allowedHosts": ["evil.example.com"],
173
- "allowedOrigins": ["https://evil.example.com"],
174
- "acknowledgedNoAuth": true
175
- }`) as McpServerInitOptions;
176
-
177
- const picked = pickInitOptions(wirePayload);
178
- expect(picked).to.deep.equal({ dataMode: 'tools' });
179
- expect(picked).to.not.have.property('host');
180
- expect(picked).to.not.have.property('allowedHosts');
181
- expect(picked).to.not.have.property('allowedOrigins');
182
- expect(picked).to.not.have.property('acknowledgedNoAuth');
183
- });
184
- });
@@ -1,59 +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 * as http from 'http';
18
-
19
- export interface RawHttpResponse {
20
- status: number;
21
- contentType: string | undefined;
22
- body: string;
23
- }
24
-
25
- /**
26
- * Issue a raw HTTP request to a transport's listening port. Used by spec files that need
27
- * to bypass the SDK Client to assert wire-level behavior (status codes, error envelopes).
28
- */
29
- export function rawHttpRequest(
30
- port: number,
31
- method: 'POST' | 'GET' | 'DELETE',
32
- headers: http.OutgoingHttpHeaders,
33
- body?: unknown
34
- ): Promise<RawHttpResponse> {
35
- return new Promise((resolve, reject) => {
36
- const req = http.request(
37
- {
38
- hostname: '127.0.0.1',
39
- port,
40
- path: '/mcp',
41
- method,
42
- headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream', ...headers }
43
- },
44
- res => {
45
- const chunks: Buffer[] = [];
46
- res.on('data', chunk => chunks.push(chunk));
47
- res.on('end', () =>
48
- resolve({
49
- status: res.statusCode ?? 0,
50
- contentType: res.headers['content-type'],
51
- body: Buffer.concat(chunks).toString('utf8')
52
- })
53
- );
54
- }
55
- );
56
- req.on('error', reject);
57
- req.end(body !== undefined ? JSON.stringify(body) : undefined);
58
- });
59
- }