@eclipse-glsp/server-mcp 2.7.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 (278) hide show
  1. package/LICENSE +642 -0
  2. package/README.md +57 -0
  3. package/lib/index.d.ts +66 -0
  4. package/lib/index.d.ts.map +1 -0
  5. package/lib/index.js +82 -0
  6. package/lib/index.js.map +1 -0
  7. package/lib/modules/mcp-diagram-module.d.ts +123 -0
  8. package/lib/modules/mcp-diagram-module.d.ts.map +1 -0
  9. package/lib/modules/mcp-diagram-module.js +202 -0
  10. package/lib/modules/mcp-diagram-module.js.map +1 -0
  11. package/lib/modules/mcp-server-module.d.ts +143 -0
  12. package/lib/modules/mcp-server-module.d.ts.map +1 -0
  13. package/lib/modules/mcp-server-module.js +251 -0
  14. package/lib/modules/mcp-server-module.js.map +1 -0
  15. package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.d.ts +44 -0
  16. package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.d.ts.map +1 -0
  17. package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.js +102 -0
  18. package/lib/prompts/handlers/describe-diagram-mcp-prompt-handler.js.map +1 -0
  19. package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.d.ts +44 -0
  20. package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.d.ts.map +1 -0
  21. package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.js +99 -0
  22. package/lib/prompts/handlers/suggest-improvements-mcp-prompt-handler.js.map +1 -0
  23. package/lib/resources/handlers/diagram-png-mcp-resource-handler.d.ts +84 -0
  24. package/lib/resources/handlers/diagram-png-mcp-resource-handler.d.ts.map +1 -0
  25. package/lib/resources/handlers/diagram-png-mcp-resource-handler.js +177 -0
  26. package/lib/resources/handlers/diagram-png-mcp-resource-handler.js.map +1 -0
  27. package/lib/resources/handlers/diagram-svg-mcp-resource-handler.d.ts +55 -0
  28. package/lib/resources/handlers/diagram-svg-mcp-resource-handler.d.ts.map +1 -0
  29. package/lib/resources/handlers/diagram-svg-mcp-resource-handler.js +99 -0
  30. package/lib/resources/handlers/diagram-svg-mcp-resource-handler.js.map +1 -0
  31. package/lib/resources/services/element-types-provider.d.ts +65 -0
  32. package/lib/resources/services/element-types-provider.d.ts.map +1 -0
  33. package/lib/resources/services/element-types-provider.js +81 -0
  34. package/lib/resources/services/element-types-provider.js.map +1 -0
  35. package/lib/resources/services/mcp-model-serializer.d.ts +78 -0
  36. package/lib/resources/services/mcp-model-serializer.d.ts.map +1 -0
  37. package/lib/resources/services/mcp-model-serializer.js +188 -0
  38. package/lib/resources/services/mcp-model-serializer.js.map +1 -0
  39. package/lib/server/glsp-mcp-server.d.ts +82 -0
  40. package/lib/server/glsp-mcp-server.d.ts.map +1 -0
  41. package/lib/server/glsp-mcp-server.js +140 -0
  42. package/lib/server/glsp-mcp-server.js.map +1 -0
  43. package/lib/server/lru-event-store.d.ts +53 -0
  44. package/lib/server/lru-event-store.d.ts.map +1 -0
  45. package/lib/server/lru-event-store.js +100 -0
  46. package/lib/server/lru-event-store.js.map +1 -0
  47. package/lib/server/mcp-diagram-handler-dispatcher.d.ts +144 -0
  48. package/lib/server/mcp-diagram-handler-dispatcher.d.ts.map +1 -0
  49. package/lib/server/mcp-diagram-handler-dispatcher.js +382 -0
  50. package/lib/server/mcp-diagram-handler-dispatcher.js.map +1 -0
  51. package/lib/server/mcp-diagram-prompt-handler-registry.d.ts +33 -0
  52. package/lib/server/mcp-diagram-prompt-handler-registry.d.ts.map +1 -0
  53. package/lib/server/mcp-diagram-prompt-handler-registry.js +76 -0
  54. package/lib/server/mcp-diagram-prompt-handler-registry.js.map +1 -0
  55. package/lib/server/mcp-diagram-resource-handler-registry.d.ts +35 -0
  56. package/lib/server/mcp-diagram-resource-handler-registry.d.ts.map +1 -0
  57. package/lib/server/mcp-diagram-resource-handler-registry.js +94 -0
  58. package/lib/server/mcp-diagram-resource-handler-registry.js.map +1 -0
  59. package/lib/server/mcp-diagram-tool-handler-registry.d.ts +57 -0
  60. package/lib/server/mcp-diagram-tool-handler-registry.d.ts.map +1 -0
  61. package/lib/server/mcp-diagram-tool-handler-registry.js +111 -0
  62. package/lib/server/mcp-diagram-tool-handler-registry.js.map +1 -0
  63. package/lib/server/mcp-handler-shared.d.ts +142 -0
  64. package/lib/server/mcp-handler-shared.d.ts.map +1 -0
  65. package/lib/server/mcp-handler-shared.js +199 -0
  66. package/lib/server/mcp-handler-shared.js.map +1 -0
  67. package/lib/server/mcp-http-transport.d.ts +93 -0
  68. package/lib/server/mcp-http-transport.d.ts.map +1 -0
  69. package/lib/server/mcp-http-transport.js +350 -0
  70. package/lib/server/mcp-http-transport.js.map +1 -0
  71. package/lib/server/mcp-id-alias-service.d.ts +70 -0
  72. package/lib/server/mcp-id-alias-service.d.ts.map +1 -0
  73. package/lib/server/mcp-id-alias-service.js +85 -0
  74. package/lib/server/mcp-id-alias-service.js.map +1 -0
  75. package/lib/server/mcp-input-schemas.d.ts +73 -0
  76. package/lib/server/mcp-input-schemas.d.ts.map +1 -0
  77. package/lib/server/mcp-input-schemas.js +67 -0
  78. package/lib/server/mcp-input-schemas.js.map +1 -0
  79. package/lib/server/mcp-label-provider.d.ts +45 -0
  80. package/lib/server/mcp-label-provider.d.ts.map +1 -0
  81. package/lib/server/mcp-label-provider.js +42 -0
  82. package/lib/server/mcp-label-provider.js.map +1 -0
  83. package/lib/server/mcp-log-level-registry.d.ts +54 -0
  84. package/lib/server/mcp-log-level-registry.d.ts.map +1 -0
  85. package/lib/server/mcp-log-level-registry.js +80 -0
  86. package/lib/server/mcp-log-level-registry.js.map +1 -0
  87. package/lib/server/mcp-logger.d.ts +59 -0
  88. package/lib/server/mcp-logger.d.ts.map +1 -0
  89. package/lib/server/mcp-logger.js +104 -0
  90. package/lib/server/mcp-logger.js.map +1 -0
  91. package/lib/server/mcp-mime-types.d.ts +28 -0
  92. package/lib/server/mcp-mime-types.d.ts.map +1 -0
  93. package/lib/server/mcp-mime-types.js +18 -0
  94. package/lib/server/mcp-mime-types.js.map +1 -0
  95. package/lib/server/mcp-options.d.ts +39 -0
  96. package/lib/server/mcp-options.d.ts.map +1 -0
  97. package/lib/server/mcp-options.js +53 -0
  98. package/lib/server/mcp-options.js.map +1 -0
  99. package/lib/server/mcp-progress-reporter.d.ts +48 -0
  100. package/lib/server/mcp-progress-reporter.d.ts.map +1 -0
  101. package/lib/server/mcp-progress-reporter.js +66 -0
  102. package/lib/server/mcp-progress-reporter.js.map +1 -0
  103. package/lib/server/mcp-prompt-handler.d.ts +120 -0
  104. package/lib/server/mcp-prompt-handler.d.ts.map +1 -0
  105. package/lib/server/mcp-prompt-handler.js +131 -0
  106. package/lib/server/mcp-prompt-handler.js.map +1 -0
  107. package/lib/server/mcp-request-context.d.ts +37 -0
  108. package/lib/server/mcp-request-context.d.ts.map +1 -0
  109. package/lib/server/mcp-request-context.js +37 -0
  110. package/lib/server/mcp-request-context.js.map +1 -0
  111. package/lib/server/mcp-resource-handler.d.ts +212 -0
  112. package/lib/server/mcp-resource-handler.d.ts.map +1 -0
  113. package/lib/server/mcp-resource-handler.js +298 -0
  114. package/lib/server/mcp-resource-handler.js.map +1 -0
  115. package/lib/server/mcp-server-launcher.d.ts +143 -0
  116. package/lib/server/mcp-server-launcher.d.ts.map +1 -0
  117. package/lib/server/mcp-server-launcher.js +355 -0
  118. package/lib/server/mcp-server-launcher.js.map +1 -0
  119. package/lib/server/mcp-session.d.ts +44 -0
  120. package/lib/server/mcp-session.d.ts.map +1 -0
  121. package/lib/server/mcp-session.js +18 -0
  122. package/lib/server/mcp-session.js.map +1 -0
  123. package/lib/server/mcp-tool-handler.d.ts +259 -0
  124. package/lib/server/mcp-tool-handler.d.ts.map +1 -0
  125. package/lib/server/mcp-tool-handler.js +355 -0
  126. package/lib/server/mcp-tool-handler.js.map +1 -0
  127. package/lib/tools/handlers/count-elements-mcp-tool-handler.d.ts +47 -0
  128. package/lib/tools/handlers/count-elements-mcp-tool-handler.d.ts.map +1 -0
  129. package/lib/tools/handlers/count-elements-mcp-tool-handler.js +77 -0
  130. package/lib/tools/handlers/count-elements-mcp-tool-handler.js.map +1 -0
  131. package/lib/tools/handlers/create-edges-mcp-tool-handler.d.ts +113 -0
  132. package/lib/tools/handlers/create-edges-mcp-tool-handler.d.ts.map +1 -0
  133. package/lib/tools/handlers/create-edges-mcp-tool-handler.js +191 -0
  134. package/lib/tools/handlers/create-edges-mcp-tool-handler.js.map +1 -0
  135. package/lib/tools/handlers/create-nodes-mcp-tool-handler.d.ts +82 -0
  136. package/lib/tools/handlers/create-nodes-mcp-tool-handler.d.ts.map +1 -0
  137. package/lib/tools/handlers/create-nodes-mcp-tool-handler.js +124 -0
  138. package/lib/tools/handlers/create-nodes-mcp-tool-handler.js.map +1 -0
  139. package/lib/tools/handlers/delete-elements-mcp-tool-handler.d.ts +53 -0
  140. package/lib/tools/handlers/delete-elements-mcp-tool-handler.d.ts.map +1 -0
  141. package/lib/tools/handlers/delete-elements-mcp-tool-handler.js +74 -0
  142. package/lib/tools/handlers/delete-elements-mcp-tool-handler.js.map +1 -0
  143. package/lib/tools/handlers/diagram-model-mcp-tool-handler.d.ts +60 -0
  144. package/lib/tools/handlers/diagram-model-mcp-tool-handler.d.ts.map +1 -0
  145. package/lib/tools/handlers/diagram-model-mcp-tool-handler.js +79 -0
  146. package/lib/tools/handlers/diagram-model-mcp-tool-handler.js.map +1 -0
  147. package/lib/tools/handlers/element-types-mcp-tool-handler.d.ts +98 -0
  148. package/lib/tools/handlers/element-types-mcp-tool-handler.d.ts.map +1 -0
  149. package/lib/tools/handlers/element-types-mcp-tool-handler.js +156 -0
  150. package/lib/tools/handlers/element-types-mcp-tool-handler.js.map +1 -0
  151. package/lib/tools/handlers/get-selection-mcp-tool-handler.d.ts +44 -0
  152. package/lib/tools/handlers/get-selection-mcp-tool-handler.d.ts.map +1 -0
  153. package/lib/tools/handlers/get-selection-mcp-tool-handler.js +70 -0
  154. package/lib/tools/handlers/get-selection-mcp-tool-handler.js.map +1 -0
  155. package/lib/tools/handlers/layout-mcp-tool-handler.d.ts +44 -0
  156. package/lib/tools/handlers/layout-mcp-tool-handler.d.ts.map +1 -0
  157. package/lib/tools/handlers/layout-mcp-tool-handler.js +72 -0
  158. package/lib/tools/handlers/layout-mcp-tool-handler.js.map +1 -0
  159. package/lib/tools/handlers/modify-edges-mcp-tool-handler.d.ts +79 -0
  160. package/lib/tools/handlers/modify-edges-mcp-tool-handler.d.ts.map +1 -0
  161. package/lib/tools/handlers/modify-edges-mcp-tool-handler.js +138 -0
  162. package/lib/tools/handlers/modify-edges-mcp-tool-handler.js.map +1 -0
  163. package/lib/tools/handlers/modify-nodes-mcp-tool-handler.d.ts +93 -0
  164. package/lib/tools/handlers/modify-nodes-mcp-tool-handler.d.ts.map +1 -0
  165. package/lib/tools/handlers/modify-nodes-mcp-tool-handler.js +127 -0
  166. package/lib/tools/handlers/modify-nodes-mcp-tool-handler.js.map +1 -0
  167. package/lib/tools/handlers/query-elements-mcp-tool-handler.d.ts +103 -0
  168. package/lib/tools/handlers/query-elements-mcp-tool-handler.d.ts.map +1 -0
  169. package/lib/tools/handlers/query-elements-mcp-tool-handler.js +160 -0
  170. package/lib/tools/handlers/query-elements-mcp-tool-handler.js.map +1 -0
  171. package/lib/tools/handlers/redo-mcp-tool-handler.d.ts +46 -0
  172. package/lib/tools/handlers/redo-mcp-tool-handler.d.ts.map +1 -0
  173. package/lib/tools/handlers/redo-mcp-tool-handler.js +75 -0
  174. package/lib/tools/handlers/redo-mcp-tool-handler.js.map +1 -0
  175. package/lib/tools/handlers/save-model-mcp-tool-handler.d.ts +57 -0
  176. package/lib/tools/handlers/save-model-mcp-tool-handler.d.ts.map +1 -0
  177. package/lib/tools/handlers/save-model-mcp-tool-handler.js +93 -0
  178. package/lib/tools/handlers/save-model-mcp-tool-handler.js.map +1 -0
  179. package/lib/tools/handlers/session-info-mcp-tool-handler.d.ts +66 -0
  180. package/lib/tools/handlers/session-info-mcp-tool-handler.d.ts.map +1 -0
  181. package/lib/tools/handlers/session-info-mcp-tool-handler.js +109 -0
  182. package/lib/tools/handlers/session-info-mcp-tool-handler.js.map +1 -0
  183. package/lib/tools/handlers/set-selection-mcp-tool-handler.d.ts +61 -0
  184. package/lib/tools/handlers/set-selection-mcp-tool-handler.d.ts.map +1 -0
  185. package/lib/tools/handlers/set-selection-mcp-tool-handler.js +104 -0
  186. package/lib/tools/handlers/set-selection-mcp-tool-handler.js.map +1 -0
  187. package/lib/tools/handlers/set-view-mcp-tool-handler.d.ts +111 -0
  188. package/lib/tools/handlers/set-view-mcp-tool-handler.d.ts.map +1 -0
  189. package/lib/tools/handlers/set-view-mcp-tool-handler.js +144 -0
  190. package/lib/tools/handlers/set-view-mcp-tool-handler.js.map +1 -0
  191. package/lib/tools/handlers/undo-mcp-tool-handler.d.ts +46 -0
  192. package/lib/tools/handlers/undo-mcp-tool-handler.d.ts.map +1 -0
  193. package/lib/tools/handlers/undo-mcp-tool-handler.js +76 -0
  194. package/lib/tools/handlers/undo-mcp-tool-handler.js.map +1 -0
  195. package/lib/tools/handlers/validate-diagram-mcp-tool-handler.d.ts +67 -0
  196. package/lib/tools/handlers/validate-diagram-mcp-tool-handler.d.ts.map +1 -0
  197. package/lib/tools/handlers/validate-diagram-mcp-tool-handler.js +0 -0
  198. package/lib/tools/handlers/validate-diagram-mcp-tool-handler.js.map +1 -0
  199. package/lib/util/markdown-util.d.ts +20 -0
  200. package/lib/util/markdown-util.d.ts.map +1 -0
  201. package/lib/util/markdown-util.js +45 -0
  202. package/lib/util/markdown-util.js.map +1 -0
  203. package/lib/util/mcp-util.d.ts +22 -0
  204. package/lib/util/mcp-util.d.ts.map +1 -0
  205. package/lib/util/mcp-util.js +29 -0
  206. package/lib/util/mcp-util.js.map +1 -0
  207. package/package.json +63 -0
  208. package/src/index.ts +65 -0
  209. package/src/modules/mcp-diagram-module.ts +247 -0
  210. package/src/modules/mcp-server-module.ts +289 -0
  211. package/src/prompts/handlers/describe-diagram-mcp-prompt-handler.ts +88 -0
  212. package/src/prompts/handlers/suggest-improvements-mcp-prompt-handler.ts +85 -0
  213. package/src/resources/handlers/diagram-png-mcp-resource-handler.ts +177 -0
  214. package/src/resources/handlers/diagram-svg-mcp-resource-handler.ts +85 -0
  215. package/src/resources/services/element-types-provider.ts +105 -0
  216. package/src/resources/services/mcp-model-serializer.ts +211 -0
  217. package/src/server/glsp-mcp-server.spec.ts +73 -0
  218. package/src/server/glsp-mcp-server.ts +196 -0
  219. package/src/server/lru-event-store.spec.ts +121 -0
  220. package/src/server/lru-event-store.ts +112 -0
  221. package/src/server/mcp-diagram-handler-dispatcher.spec.ts +231 -0
  222. package/src/server/mcp-diagram-handler-dispatcher.ts +459 -0
  223. package/src/server/mcp-diagram-prompt-handler-registry.ts +59 -0
  224. package/src/server/mcp-diagram-resource-handler-registry.ts +73 -0
  225. package/src/server/mcp-diagram-tool-handler-registry.ts +97 -0
  226. package/src/server/mcp-handler-shared.spec.ts +53 -0
  227. package/src/server/mcp-handler-shared.ts +247 -0
  228. package/src/server/mcp-http-transport-e2e.spec.ts +151 -0
  229. package/src/server/mcp-http-transport.spec.ts +385 -0
  230. package/src/server/mcp-http-transport.ts +368 -0
  231. package/src/server/mcp-id-alias-service.spec.ts +106 -0
  232. package/src/server/mcp-id-alias-service.ts +104 -0
  233. package/src/server/mcp-input-schemas.ts +82 -0
  234. package/src/server/mcp-label-provider.ts +52 -0
  235. package/src/server/mcp-log-level-registry.spec.ts +75 -0
  236. package/src/server/mcp-log-level-registry.ts +90 -0
  237. package/src/server/mcp-logger.spec.ts +227 -0
  238. package/src/server/mcp-logger.ts +91 -0
  239. package/src/server/mcp-mime-types.ts +31 -0
  240. package/src/server/mcp-options.ts +43 -0
  241. package/src/server/mcp-progress-reporter.spec.ts +93 -0
  242. package/src/server/mcp-progress-reporter.ts +67 -0
  243. package/src/server/mcp-prompt-handler.ts +157 -0
  244. package/src/server/mcp-request-context.ts +39 -0
  245. package/src/server/mcp-resource-handler.ts +389 -0
  246. package/src/server/mcp-server-launcher.spec.ts +173 -0
  247. package/src/server/mcp-server-launcher.ts +369 -0
  248. package/src/server/mcp-session.ts +45 -0
  249. package/src/server/mcp-tool-handler.spec.ts +182 -0
  250. package/src/server/mcp-tool-handler.ts +431 -0
  251. package/src/server/raw-http.spec.ts +59 -0
  252. package/src/tools/handlers/count-elements-mcp-tool-handler.spec.ts +101 -0
  253. package/src/tools/handlers/count-elements-mcp-tool-handler.ts +68 -0
  254. package/src/tools/handlers/create-edges-mcp-tool-handler.spec.ts +198 -0
  255. package/src/tools/handlers/create-edges-mcp-tool-handler.ts +200 -0
  256. package/src/tools/handlers/create-nodes-mcp-tool-handler.spec.ts +199 -0
  257. package/src/tools/handlers/create-nodes-mcp-tool-handler.ts +126 -0
  258. package/src/tools/handlers/delete-elements-mcp-tool-handler.ts +69 -0
  259. package/src/tools/handlers/diagram-model-mcp-tool-handler.ts +68 -0
  260. package/src/tools/handlers/element-types-mcp-tool-handler.ts +152 -0
  261. package/src/tools/handlers/get-selection-mcp-tool-handler.ts +56 -0
  262. package/src/tools/handlers/layout-mcp-tool-handler.ts +58 -0
  263. package/src/tools/handlers/modify-edges-mcp-tool-handler.ts +142 -0
  264. package/src/tools/handlers/modify-nodes-mcp-tool-handler.ts +134 -0
  265. package/src/tools/handlers/query-elements-mcp-tool-handler.spec.ts +212 -0
  266. package/src/tools/handlers/query-elements-mcp-tool-handler.ts +157 -0
  267. package/src/tools/handlers/redo-mcp-tool-handler.ts +64 -0
  268. package/src/tools/handlers/save-model-mcp-tool-handler.ts +74 -0
  269. package/src/tools/handlers/session-info-mcp-tool-handler.spec.ts +152 -0
  270. package/src/tools/handlers/session-info-mcp-tool-handler.ts +98 -0
  271. package/src/tools/handlers/set-selection-mcp-tool-handler.spec.ts +120 -0
  272. package/src/tools/handlers/set-selection-mcp-tool-handler.ts +92 -0
  273. package/src/tools/handlers/set-view-mcp-tool-handler.ts +156 -0
  274. package/src/tools/handlers/undo-mcp-tool-handler.ts +63 -0
  275. package/src/tools/handlers/validate-diagram-mcp-tool-handler.ts +0 -0
  276. package/src/tools/tool-annotations.spec.ts +141 -0
  277. package/src/util/markdown-util.ts +44 -0
  278. package/src/util/mcp-util.ts +25 -0
@@ -0,0 +1,211 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { GModelElement } from '@eclipse-glsp/graph';
18
+ import { Dimension, GModelSerializer, Logger, Point } from '@eclipse-glsp/server';
19
+ import { inject, injectable, postConstruct } from 'inversify';
20
+ import { McpStructuredContent } from '../../server/mcp-handler-shared';
21
+ import { McpIdAliasService } from '../../server/mcp-id-alias-service';
22
+ import { objectArrayToMarkdownTable } from '../../util/markdown-util';
23
+
24
+ export const McpModelSerializer = Symbol('McpModelSerializer');
25
+
26
+ /** Loose JSON-object shape mirroring `GModelSerializer.createSchema` output — open-keyed because
27
+ * GModel attributes vary per element type. Reads must narrow before use. */
28
+ export interface SerializedElement {
29
+ [key: string]: unknown;
30
+ }
31
+
32
+ /**
33
+ * Transforms a graphical model into a representation suitable for LLM consumption. Markdown and
34
+ * JSON are both reasonable formats; the default impl ({@link MarkdownMcpModelSerializer}) emits
35
+ * Markdown for {@link serialize}/{@link serializeArray} and an object payload for
36
+ * {@link serializeStructured} (the dual-emit `structuredContent` counterpart). Aliasing of element
37
+ * ids happens internally via the per-session {@link McpIdAliasService}.
38
+ *
39
+ * @experimental
40
+ */
41
+ export interface McpModelSerializer {
42
+ /** Serializes a single element (and its descendants). */
43
+ serialize(element: GModelElement): string;
44
+
45
+ /** Serializes an array of elements; duplicates introduced by hierarchy are removed. */
46
+ serializeArray(elements: GModelElement[]): string;
47
+
48
+ /**
49
+ * Structured-content counterpart of {@link serialize} for dual-emit
50
+ * (`CallToolResult.structuredContent`). The shape is intentionally open: `{ elements: [...] }`
51
+ * with each entry carrying `id` + `type` + adopter-specific attrs (passthrough).
52
+ */
53
+ serializeStructured(element: GModelElement): McpStructuredContent;
54
+
55
+ /** Structured-content counterpart of {@link serializeArray}. */
56
+ serializeStructuredArray(elements: GModelElement[]): McpStructuredContent;
57
+ }
58
+
59
+ /**
60
+ * Default {@link McpModelSerializer} — emits Markdown with one H1 section per element type
61
+ * followed by a table of all elements of that type. Flattens the GModel tree, drops keys with
62
+ * no LLM value (`cssClasses`, `revision`, `layout`, etc., see {@link keysToRemove}), truncates
63
+ * position+size to integers and derives a `bounds` rectangle so the LLM doesn't redo arithmetic.
64
+ *
65
+ * Generic: no control over element order or per-type attribute order, since no specific GLSP
66
+ * adopter is known. Adopters override (see workflow's `WorkflowMcpModelSerializer`) when
67
+ * semantic ordering matters.
68
+ */
69
+ @injectable()
70
+ export class MarkdownMcpModelSerializer implements McpModelSerializer {
71
+ @inject(GModelSerializer) protected gModelSerializer: GModelSerializer;
72
+ @inject(McpIdAliasService) protected aliasService: McpIdAliasService;
73
+ @inject(Logger) protected logger: Logger;
74
+
75
+ protected keysToRemove: string[] = [
76
+ 'cssClasses',
77
+ 'revision',
78
+ 'layout',
79
+ 'args',
80
+ 'layoutOptions',
81
+ 'alignment',
82
+ 'children',
83
+ 'routingPoints',
84
+ 'resizeLocations',
85
+ 'parent'
86
+ ];
87
+
88
+ /** Warn once per binding when the bare default is used — subclasses suppress via the constructor check. */
89
+ @postConstruct()
90
+ protected warnIfGenericDefault(): void {
91
+ if (this.constructor === MarkdownMcpModelSerializer) {
92
+ this.logger.warn(
93
+ 'Using generic MarkdownMcpModelSerializer; bind a diagram-specific subclass via ' +
94
+ 'DefaultMcpDiagramModule.bindModelSerializer() for richer LLM output.'
95
+ );
96
+ }
97
+ }
98
+
99
+ serialize(element: GModelElement): string {
100
+ return this.serializeArray([element]);
101
+ }
102
+
103
+ serializeArray(elements: GModelElement[]): string {
104
+ return Object.entries(this.buildAliasedTypeBuckets(elements))
105
+ .filter(([, bucket]) => bucket.length > 0)
106
+ .flatMap(([type, bucket]) => [`# ${type}`, objectArrayToMarkdownTable(bucket)])
107
+ .join('\n');
108
+ }
109
+
110
+ serializeStructured(element: GModelElement): McpStructuredContent {
111
+ return this.serializeStructuredArray([element]);
112
+ }
113
+
114
+ serializeStructuredArray(elements: GModelElement[]): McpStructuredContent {
115
+ return {
116
+ elements: Object.values(this.buildAliasedTypeBuckets(elements))
117
+ .filter(bucket => bucket.length > 0)
118
+ .flat()
119
+ };
120
+ }
121
+
122
+ /** Common pipeline: prepare, dedupe by id, group by type, alias ids — used by both renders. */
123
+ protected buildAliasedTypeBuckets(elements: GModelElement[]): Record<string, SerializedElement[]> {
124
+ const elementsByTypeArray = elements.map(element => this.prepareElement(element));
125
+ const result: Record<string, SerializedElement[]> = {};
126
+ const allKeys = new Set(elementsByTypeArray.flatMap(obj => Object.keys(obj)));
127
+ allKeys.forEach(key => {
128
+ const combined = elementsByTypeArray.flatMap(obj => obj[key] ?? []);
129
+ result[key] = Array.from(new Map(combined.map(item => [item.id, item])).values()).map(item => this.applyAlias(item));
130
+ });
131
+ return result;
132
+ }
133
+
134
+ protected applyAlias(element: SerializedElement): SerializedElement {
135
+ for (const field of ['id', 'sourceId', 'targetId', 'parentId'] as const) {
136
+ const value = element[field];
137
+ if (typeof value === 'string') {
138
+ element[field] = this.aliasService.alias(value);
139
+ }
140
+ }
141
+ return element;
142
+ }
143
+
144
+ protected prepareElement(element: GModelElement): Record<string, SerializedElement[]> {
145
+ const schema = this.gModelSerializer.createSchema(element) as unknown as SerializedElement;
146
+ const elements = this.flattenStructure(schema, element.parent?.id);
147
+
148
+ const result: Record<string, SerializedElement[]> = {};
149
+ elements.forEach(elem => {
150
+ this.removeKeys(elem);
151
+ this.combinePositionAndSize(elem);
152
+ const type = typeof elem.type === 'string' ? elem.type : 'unknown';
153
+ (result[type] ??= []).push(elem);
154
+ });
155
+
156
+ return result;
157
+ }
158
+
159
+ protected flattenStructure(element: SerializedElement, parentId?: string): SerializedElement[] {
160
+ const newElement: SerializedElement = { ...element };
161
+ const result: SerializedElement[] = [newElement];
162
+ const children = newElement.children;
163
+ if (Array.isArray(children)) {
164
+ const ownId = typeof newElement.id === 'string' ? newElement.id : undefined;
165
+ children.forEach(child =>
166
+ this.flattenStructure(child as SerializedElement, ownId).forEach(descendant => result.push(descendant))
167
+ );
168
+ }
169
+ newElement.parentId = parentId;
170
+ return result;
171
+ }
172
+
173
+ protected removeKeys(element: SerializedElement): void {
174
+ for (const key of Object.keys(element)) {
175
+ if (this.keysToRemove.includes(key)) {
176
+ delete element[key];
177
+ }
178
+ }
179
+ }
180
+
181
+ protected combinePositionAndSize(element: SerializedElement): void {
182
+ const position = element.position;
183
+ if (!Point.is(position)) {
184
+ return;
185
+ }
186
+ const x = Math.trunc(position.x);
187
+ const y = Math.trunc(position.y);
188
+ element.position = { x, y };
189
+
190
+ // Omit `size` and `bounds` when an element has no explicit geometry yet (e.g., a freshly
191
+ // created node whose `.size()` has not been applied). Emitting `{width: 0, height: 0}`
192
+ // would mislead an LLM consumer into "fixing" placeholder bounds. Visual rendering on
193
+ // the GLSP/sprotty client is unaffected — those nodes are laid out via CSS regardless.
194
+ const size = element.size;
195
+ if (!Dimension.is(size) || (Math.trunc(size.width) === 0 && Math.trunc(size.height) === 0)) {
196
+ delete element.size;
197
+ delete element.bounds;
198
+ return;
199
+ }
200
+
201
+ const width = Math.trunc(size.width);
202
+ const height = Math.trunc(size.height);
203
+ element.size = { width, height };
204
+ element.bounds = {
205
+ left: x,
206
+ right: x + width,
207
+ top: y,
208
+ bottom: y + height
209
+ };
210
+ }
211
+ }
@@ -0,0 +1,73 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { 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
+ });
@@ -0,0 +1,196 @@
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 { Disposable, Logger, McpServerOptions } from '@eclipse-glsp/server';
18
+ import {
19
+ McpServer,
20
+ RegisteredPrompt,
21
+ RegisteredResource,
22
+ RegisteredResourceTemplate,
23
+ RegisteredTool
24
+ } from '@modelcontextprotocol/sdk/server/mcp.js';
25
+ import { EmptyResultSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
26
+ import { injectable, unmanaged } from 'inversify';
27
+
28
+ export const GLSPMcpServer = Symbol('GLSPMcpServer');
29
+
30
+ /** Either a static resource or a templated resource registration. */
31
+ export type GLSPMcpResource = RegisteredResource | RegisteredResourceTemplate;
32
+
33
+ /**
34
+ * Cadence for server-initiated `ping` requests on the standalone SSE GET stream. Shorter than
35
+ * typical client-side SSE read timeouts (e.g. undici's 5-min `bodyTimeout`) so the stream stays
36
+ * alive across chat-idle periods.
37
+ */
38
+ const KEEP_ALIVE_INTERVAL_MS = 30_000;
39
+
40
+ /** Per-ping timeout — short, since a ping with no SSE GET stream open will never resolve. */
41
+ const KEEP_ALIVE_PING_TIMEOUT_MS = 5_000;
42
+
43
+ /**
44
+ * Curated, per-session view onto the underlying MCP server. Handlers see this during
45
+ * registration; the launcher disposes it on session-close and on server shutdown.
46
+ */
47
+ export interface GLSPMcpServer
48
+ extends Pick<McpServer, 'registerPrompt' | 'registerResource' | 'registerTool' | 'sendLoggingMessage' | 'isConnected' | 'connect'>,
49
+ Disposable {
50
+ readonly options: McpServerOptions;
51
+
52
+ /** Register a tool. Wraps the SDK's {@link McpServer.registerTool} and updates the local log. */
53
+ readonly registerTool: McpServer['registerTool'];
54
+ /** Register a resource. Wraps the SDK's {@link McpServer.registerResource} and updates the local log. */
55
+ readonly registerResource: McpServer['registerResource'];
56
+ /** Register a prompt. Wraps the SDK's {@link McpServer.registerPrompt} and updates the local log. */
57
+ readonly registerPrompt: McpServer['registerPrompt'];
58
+ /** Send an LLM-facing log message via the MCP `logging/message` notification. */
59
+ sendLoggingMessage: McpServer['sendLoggingMessage'];
60
+ /** True iff a transport is currently connected. */
61
+ isConnected: McpServer['isConnected'];
62
+ /** Attach the supplied transport. */
63
+ connect: McpServer['connect'];
64
+
65
+ listTools(): RegisteredTool[];
66
+ /** True iff a tool with this exact `name` has been registered on this server instance. */
67
+ hasTool(name: string): boolean;
68
+ listResources(): GLSPMcpResource[];
69
+ listPrompts(): RegisteredPrompt[];
70
+ /** Sends an MCP `ping` and resolves on empty result, rejects on transport timeout. */
71
+ ping(): Promise<void>;
72
+ /**
73
+ * Escape hatch to the underlying SDK `McpServer` for advanced APIs not covered by the
74
+ * stable surface. The returned value is the SDK type, not a stable GLSP surface.
75
+ */
76
+ getRawServer(): McpServer;
77
+ }
78
+
79
+ export const GLSPMcpServerFactory = Symbol('GLSPMcpServerFactory');
80
+
81
+ /**
82
+ * Factory that produces a {@link GLSPMcpServer} per MCP client session.
83
+ * Bound to {@link DefaultGLSPMcpServer} by default; rebind to swap the
84
+ * implementation across all sessions.
85
+ */
86
+ export type GLSPMcpServerFactory = (mcpServer: McpServer, options: McpServerOptions) => GLSPMcpServer;
87
+
88
+ @injectable()
89
+ export class DefaultGLSPMcpServer implements GLSPMcpServer {
90
+ protected readonly tools = new Map<string, RegisteredTool>();
91
+ protected readonly resources = new Map<string, GLSPMcpResource>();
92
+ protected readonly prompts = new Map<string, RegisteredPrompt>();
93
+ protected keepAliveTimer?: NodeJS.Timeout;
94
+
95
+ readonly registerTool: McpServer['registerTool'];
96
+ readonly registerResource: McpServer['registerResource'];
97
+ readonly registerPrompt: McpServer['registerPrompt'];
98
+
99
+ constructor(
100
+ @unmanaged() protected readonly mcpServer: McpServer,
101
+ @unmanaged() readonly options: McpServerOptions,
102
+ @unmanaged() protected readonly logger: Logger
103
+ ) {
104
+ // `register*` need an interception layer so the local registration log stays in
105
+ // sync; the Proxy preserves the SDK's generic signatures (which a wrapped method
106
+ // using `Parameters<>` would collapse, breaking adopter autocomplete on
107
+ // `inputSchema → handler arg shape`).
108
+ const { tools, resources, prompts } = this;
109
+ this.registerTool = new Proxy(mcpServer.registerTool.bind(mcpServer), {
110
+ apply(target, thisArg, args) {
111
+ const registered = Reflect.apply(target, thisArg, args);
112
+ if (typeof args[0] === 'string') {
113
+ tools.set(args[0], registered);
114
+ }
115
+ return registered;
116
+ }
117
+ });
118
+ this.registerResource = new Proxy(mcpServer.registerResource.bind(mcpServer), {
119
+ apply(target, thisArg, args) {
120
+ const registered = Reflect.apply(target, thisArg, args);
121
+ if (typeof args[0] === 'string') {
122
+ resources.set(args[0], registered);
123
+ }
124
+ return registered;
125
+ }
126
+ });
127
+ this.registerPrompt = new Proxy(mcpServer.registerPrompt.bind(mcpServer), {
128
+ apply(target, thisArg, args) {
129
+ const registered = Reflect.apply(target, thisArg, args);
130
+ if (typeof args[0] === 'string') {
131
+ prompts.set(args[0], registered);
132
+ }
133
+ return registered;
134
+ }
135
+ });
136
+ }
137
+
138
+ sendLoggingMessage(...args: Parameters<McpServer['sendLoggingMessage']>): ReturnType<McpServer['sendLoggingMessage']> {
139
+ return this.mcpServer.sendLoggingMessage(...args);
140
+ }
141
+
142
+ async connect(...args: Parameters<McpServer['connect']>): ReturnType<McpServer['connect']> {
143
+ await this.mcpServer.connect(...args);
144
+ // Suppress the expected `RequestTimeout` when no SSE GET stream is open; surface other
145
+ // failures via debug. `unref` so a forgotten timer can't pin the process.
146
+ this.keepAliveTimer = setInterval(
147
+ () =>
148
+ this.ping().catch(err => {
149
+ if (!(err instanceof McpError) || err.code !== ErrorCode.RequestTimeout) {
150
+ this.logger.debug('MCP keep-alive ping failed:', err);
151
+ }
152
+ }),
153
+ KEEP_ALIVE_INTERVAL_MS
154
+ );
155
+ this.keepAliveTimer.unref();
156
+ }
157
+
158
+ isConnected(): boolean {
159
+ return this.mcpServer.isConnected();
160
+ }
161
+
162
+ async ping(): Promise<void> {
163
+ // Bypass `Server.ping()` to pass a per-request timeout; SDK's no-arg wrapper uses the
164
+ // 60s default, which leaves keep-alive pings dangling for a full minute when no SSE GET
165
+ // stream is open.
166
+ await this.mcpServer.server.request({ method: 'ping' }, EmptyResultSchema, { timeout: KEEP_ALIVE_PING_TIMEOUT_MS });
167
+ }
168
+
169
+ listTools(): RegisteredTool[] {
170
+ return [...this.tools.values()];
171
+ }
172
+
173
+ hasTool(name: string): boolean {
174
+ return this.tools.has(name);
175
+ }
176
+
177
+ listResources(): GLSPMcpResource[] {
178
+ return [...this.resources.values()];
179
+ }
180
+
181
+ listPrompts(): RegisteredPrompt[] {
182
+ return [...this.prompts.values()];
183
+ }
184
+
185
+ getRawServer(): McpServer {
186
+ return this.mcpServer;
187
+ }
188
+
189
+ dispose(): void {
190
+ if (this.keepAliveTimer) {
191
+ clearInterval(this.keepAliveTimer);
192
+ this.keepAliveTimer = undefined;
193
+ }
194
+ this.mcpServer.close();
195
+ }
196
+ }
@@ -0,0 +1,121 @@
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
+ });