@loop_ouroboros/mcp-hub-lite 1.3.0 → 1.3.2

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 (136) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +410 -331
  3. package/dist/client/assets/{HomeView-Bi2bkUKf.js → HomeView-DplI3V-h.js} +1 -1
  4. package/dist/client/assets/{ResourceDetailView-DyuSovH9.js → ResourceDetailView-CeHPn99Y.js} +1 -1
  5. package/dist/client/assets/ResourcesView-C1ObRhYS.js +1 -0
  6. package/dist/client/assets/{ServerDashboard-BGyyZAti.js → ServerDashboard-D7wG4Gvt.js} +1 -1
  7. package/dist/client/assets/ServerDetail-G23phOcJ.js +2 -0
  8. package/dist/client/assets/{ServerListView-yQPVJFHG.js → ServerListView-BFiZLtPO.js} +1 -1
  9. package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-C8gQlxGE.js → ServerStatusTags.vue_vue_type_script_setup_true_lang-Deb_SbFw.js} +1 -1
  10. package/dist/client/assets/SettingsView-QBFLZ6fP.js +1 -0
  11. package/dist/client/assets/ToolCallDialog-DYS-ADCL.js +1 -0
  12. package/dist/client/assets/ToolsView-DYwgtm7W.js +1 -0
  13. package/dist/client/assets/_baseClone-DQno9YO3.js +1 -0
  14. package/dist/client/assets/{el-form-item-DfWq_kSy.js → el-form-item-DF0zzQdH.js} +2 -2
  15. package/dist/client/assets/el-input-C_p2Qw42.js +1 -0
  16. package/dist/client/assets/el-loading-BaenpNzU.js +1 -0
  17. package/dist/client/assets/el-overlay-MbIUXSQ7.js +1 -0
  18. package/dist/client/assets/el-radio-group-COnCjCcz.js +1 -0
  19. package/dist/client/assets/el-skeleton-item-qj0eQP4s.js +1 -0
  20. package/dist/client/assets/el-switch-BZbXqB3_.js +1 -0
  21. package/dist/client/assets/el-tab-pane-w7RltRLd.js +1 -0
  22. package/dist/client/assets/el-table-column-OD8zhFcD.js +1 -0
  23. package/dist/client/assets/index-DwhULJXZ.js +2 -0
  24. package/dist/client/assets/{index-Bzz3tYbS.css → index-UtsV0Cvh.css} +1 -1
  25. package/dist/client/assets/{omit-BIIebEYo.js → omit-BAJQlviJ.js} +1 -1
  26. package/dist/client/assets/raf-B1Ry7ruA.js +1 -0
  27. package/dist/client/assets/{vue-vendor-Dwcr0jep.js → vue-vendor-ClSvefnQ.js} +1 -1
  28. package/dist/client/index.html +3 -3
  29. package/dist/server/shared/models/constants.d.ts +5 -0
  30. package/dist/server/shared/models/constants.d.ts.map +1 -1
  31. package/dist/server/shared/models/constants.js +4 -0
  32. package/dist/server/shared/models/server.model.d.ts +14 -0
  33. package/dist/server/shared/models/server.model.d.ts.map +1 -1
  34. package/dist/server/shared/models/server.model.js +27 -4
  35. package/dist/server/src/api/mcp/gateway.d.ts +10 -6
  36. package/dist/server/src/api/mcp/gateway.d.ts.map +1 -1
  37. package/dist/server/src/api/mcp/gateway.js +235 -69
  38. package/dist/server/src/api/web/hub-tools.d.ts.map +1 -1
  39. package/dist/server/src/api/web/hub-tools.js +2 -2
  40. package/dist/server/src/api/web/search.d.ts +1 -1
  41. package/dist/server/src/api/web/search.d.ts.map +1 -1
  42. package/dist/server/src/api/web/search.js +18 -16
  43. package/dist/server/src/api/web/sessions.d.ts +1 -27
  44. package/dist/server/src/api/web/sessions.d.ts.map +1 -1
  45. package/dist/server/src/api/web/sessions.js +8 -97
  46. package/dist/server/src/app.d.ts.map +1 -1
  47. package/dist/server/src/app.js +5 -0
  48. package/dist/server/src/cli/commands/status.js +39 -1
  49. package/dist/server/src/cli/commands/use-guide.d.ts +0 -8
  50. package/dist/server/src/cli/commands/use-guide.d.ts.map +1 -1
  51. package/dist/server/src/cli/commands/use-guide.js +28 -170
  52. package/dist/server/src/cli/server.d.ts +10 -0
  53. package/dist/server/src/cli/server.d.ts.map +1 -1
  54. package/dist/server/src/cli/server.js +31 -1
  55. package/dist/server/src/models/system-tools.constants.d.ts +1 -0
  56. package/dist/server/src/models/system-tools.constants.d.ts.map +1 -1
  57. package/dist/server/src/server/dev-server.js +2 -0
  58. package/dist/server/src/server/runner.d.ts.map +1 -1
  59. package/dist/server/src/server/runner.js +2 -0
  60. package/dist/server/src/services/connection/connection-manager.d.ts +2 -0
  61. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  62. package/dist/server/src/services/connection/connection-manager.js +14 -7
  63. package/dist/server/src/services/gateway/gateway.service.d.ts +13 -0
  64. package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
  65. package/dist/server/src/services/gateway/gateway.service.js +72 -0
  66. package/dist/server/src/services/gateway/global-transport.d.ts +20 -10
  67. package/dist/server/src/services/gateway/global-transport.d.ts.map +1 -1
  68. package/dist/server/src/services/gateway/global-transport.js +50 -34
  69. package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
  70. package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +22 -6
  71. package/dist/server/src/services/gateway/request-handlers/resources-handler.d.ts.map +1 -1
  72. package/dist/server/src/services/gateway/request-handlers/resources-handler.js +5 -1
  73. package/dist/server/src/services/gateway/request-handlers/system-tools-handler.d.ts.map +1 -1
  74. package/dist/server/src/services/gateway/request-handlers/system-tools-handler.js +3 -2
  75. package/dist/server/src/services/gateway/session-manager.d.ts +101 -0
  76. package/dist/server/src/services/gateway/session-manager.d.ts.map +1 -0
  77. package/dist/server/src/services/gateway/session-manager.js +256 -0
  78. package/dist/server/src/services/hub-tools/resource-generator.d.ts +1 -1
  79. package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
  80. package/dist/server/src/services/hub-tools/resource-generator.js +11 -9
  81. package/dist/server/src/services/hub-tools/system-tool-definitions.d.ts.map +1 -1
  82. package/dist/server/src/services/hub-tools/system-tool-definitions.js +7 -0
  83. package/dist/server/src/services/hub-tools.service.d.ts +1 -1
  84. package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
  85. package/dist/server/src/services/hub-tools.service.js +23 -15
  86. package/dist/server/src/services/system-tool-handler.js +1 -1
  87. package/dist/server/src/utils/json-utils.d.ts +9 -0
  88. package/dist/server/src/utils/json-utils.d.ts.map +1 -1
  89. package/dist/server/src/utils/json-utils.js +19 -0
  90. package/dist/server/src/utils/logger/index.d.ts +1 -1
  91. package/dist/server/src/utils/logger/index.d.ts.map +1 -1
  92. package/dist/server/src/utils/logger/index.js +1 -1
  93. package/dist/server/src/utils/logger/log-context.d.ts +1 -0
  94. package/dist/server/src/utils/logger/log-context.d.ts.map +1 -1
  95. package/dist/server/src/utils/logger/log-formatter.d.ts.map +1 -1
  96. package/dist/server/src/utils/logger/log-formatter.js +25 -11
  97. package/dist/server/src/utils/logger/log-output.d.ts +17 -1
  98. package/dist/server/src/utils/logger/log-output.d.ts.map +1 -1
  99. package/dist/server/src/utils/logger/log-output.js +46 -40
  100. package/dist/server/src/utils/logger/logger.d.ts.map +1 -1
  101. package/dist/server/src/utils/logger/logger.js +18 -2
  102. package/dist/server/src/utils/request-context.d.ts +8 -70
  103. package/dist/server/src/utils/request-context.d.ts.map +1 -1
  104. package/dist/server/src/utils/request-context.js +11 -70
  105. package/dist/server/src/utils/search-matcher.d.ts +6 -0
  106. package/dist/server/src/utils/search-matcher.d.ts.map +1 -0
  107. package/dist/server/src/utils/search-matcher.js +24 -0
  108. package/dist/server/tests/unit/config/config.schema.test.js +2 -1
  109. package/dist/server/tests/unit/server/runner.test.js +14 -7
  110. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts +2 -0
  111. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts.map +1 -0
  112. package/dist/server/tests/unit/services/gateway-session-mode.test.js +174 -0
  113. package/dist/server/tests/unit/services/hub-tools.service.test.js +299 -4
  114. package/dist/server/tests/unit/utils/config.test.js +14 -7
  115. package/dist/server/tests/unit/utils/log-output.test.d.ts +2 -0
  116. package/dist/server/tests/unit/utils/log-output.test.d.ts.map +1 -0
  117. package/dist/server/tests/unit/utils/log-output.test.js +198 -0
  118. package/dist/server/vitest.config.d.ts.map +1 -1
  119. package/dist/server/vitest.config.js +0 -2
  120. package/package.json +1 -1
  121. package/dist/client/assets/ResourcesView-CU0VbNy5.js +0 -1
  122. package/dist/client/assets/ServerDetail-bcQ8BVXR.js +0 -2
  123. package/dist/client/assets/SettingsView-B1DxbFP3.js +0 -1
  124. package/dist/client/assets/ToolCallDialog-DEapCO06.js +0 -1
  125. package/dist/client/assets/ToolsView-DA0u_bCw.js +0 -1
  126. package/dist/client/assets/_baseClone-B991Lvrt.js +0 -1
  127. package/dist/client/assets/el-input-5YzZrwir.js +0 -1
  128. package/dist/client/assets/el-loading-DE3FcxNH.js +0 -1
  129. package/dist/client/assets/el-overlay-BTeTueuN.js +0 -1
  130. package/dist/client/assets/el-radio-group-Y1E2bxIW.js +0 -1
  131. package/dist/client/assets/el-skeleton-item-DhgR50Jx.js +0 -1
  132. package/dist/client/assets/el-switch-fF--nMSD.js +0 -1
  133. package/dist/client/assets/el-tab-pane-rvS_KTwP.js +0 -1
  134. package/dist/client/assets/el-table-column-B1O8mY47.js +0 -1
  135. package/dist/client/assets/index-DkqV9kH4.js +0 -2
  136. package/dist/client/assets/raf-Cj-gATZv.js +0 -1
@@ -5,9 +5,9 @@
5
5
  <link rel="icon" href="/favicon.ico" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>MCP Server Manager</title>
8
- <script type="module" crossorigin src="/assets/index-DkqV9kH4.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/vue-vendor-Dwcr0jep.js">
10
- <link rel="stylesheet" crossorigin href="/assets/index-Bzz3tYbS.css">
8
+ <script type="module" crossorigin src="/assets/index-DwhULJXZ.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/vue-vendor-ClSvefnQ.js">
10
+ <link rel="stylesheet" crossorigin href="/assets/index-UtsV0Cvh.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="app"></div>
@@ -1,3 +1,8 @@
1
1
  /** MCP Hub Lite gateway server name — used for system tool identification and gateway naming */
2
2
  export declare const MCP_HUB_LITE_SERVER = "mcp-hub-lite";
3
+ /** Session mode constants for gateway transport selection */
4
+ export declare const SESSION_MODE_STATELESS: "stateless";
5
+ export declare const SESSION_MODE_STATEFUL: "stateful";
6
+ export declare const SESSION_MODE_VALUES: readonly ["stateless", "stateful"];
7
+ export type SessionMode = (typeof SESSION_MODE_VALUES)[number];
3
8
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../shared/models/constants.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,eAAO,MAAM,mBAAmB,iBAAiB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../shared/models/constants.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD,6DAA6D;AAC7D,eAAO,MAAM,sBAAsB,EAAG,WAAoB,CAAC;AAC3D,eAAO,MAAM,qBAAqB,EAAG,UAAmB,CAAC;AACzD,eAAO,MAAM,mBAAmB,oCAA2D,CAAC;AAC5F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -1,2 +1,6 @@
1
1
  /** MCP Hub Lite gateway server name — used for system tool identification and gateway naming */
2
2
  export const MCP_HUB_LITE_SERVER = 'mcp-hub-lite';
3
+ /** Session mode constants for gateway transport selection */
4
+ export const SESSION_MODE_STATELESS = 'stateless';
5
+ export const SESSION_MODE_STATEFUL = 'stateful';
6
+ export const SESSION_MODE_VALUES = [SESSION_MODE_STATELESS, SESSION_MODE_STATEFUL];
@@ -232,6 +232,7 @@ export declare const LoggingConfigSchema: z.ZodDefault<z.ZodObject<{
232
232
  mcpCommDebug: z.ZodDefault<z.ZodBoolean>;
233
233
  apiDebug: z.ZodDefault<z.ZodBoolean>;
234
234
  gatewayDebug: z.ZodDefault<z.ZodBoolean>;
235
+ showTraceContext: z.ZodDefault<z.ZodBoolean>;
235
236
  }, z.core.$strip>>;
236
237
  export type LoggingConfig = z.infer<typeof LoggingConfigSchema>;
237
238
  /**
@@ -280,6 +281,7 @@ export declare const SystemConfigSchema: z.ZodObject<{
280
281
  mcpCommDebug: z.ZodDefault<z.ZodBoolean>;
281
282
  apiDebug: z.ZodDefault<z.ZodBoolean>;
282
283
  gatewayDebug: z.ZodDefault<z.ZodBoolean>;
284
+ showTraceContext: z.ZodDefault<z.ZodBoolean>;
283
285
  }, z.core.$strip>>;
284
286
  startup: z.ZodOptional<z.ZodDefault<z.ZodObject<{
285
287
  startupDelay: z.ZodDefault<z.ZodNumber>;
@@ -287,6 +289,16 @@ export declare const SystemConfigSchema: z.ZodObject<{
287
289
  maxConnectRetries: z.ZodDefault<z.ZodNumber>;
288
290
  connectRetryDelay: z.ZodDefault<z.ZodNumber>;
289
291
  }, z.core.$strip>>>;
292
+ session: z.ZodOptional<z.ZodObject<{
293
+ sessionModeRules: z.ZodDefault<z.ZodOptional<z.ZodObject<{
294
+ stateful: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
295
+ stateless: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
296
+ }, z.core.$strip>>>;
297
+ defaultSessionMode: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
298
+ stateless: "stateless";
299
+ stateful: "stateful";
300
+ }>>>;
301
+ }, z.core.$strip>>;
290
302
  }, z.core.$strip>>;
291
303
  security: z.ZodDefault<z.ZodObject<{
292
304
  allowedNetworks: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -362,6 +374,8 @@ export declare const SystemConfigSchema: z.ZodObject<{
362
374
  }, z.core.$strip>>>;
363
375
  }, z.core.$strip>;
364
376
  export type SystemConfig = z.infer<typeof SystemConfigSchema>;
377
+ /** Session configuration extracted from SystemConfig.system.session */
378
+ export type GatewayConfig = SystemConfig['system']['session'];
365
379
  /**
366
380
  * Server instance runtime configuration (for connection manager)
367
381
  * Contains runtime-specific fields like timestamp, pid, etc.
@@ -1 +1 @@
1
- {"version":3,"file":"server.model.d.ts","sourceRoot":"","sources":["../../../../shared/models/server.model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.model.d.ts","sourceRoot":"","sources":["../../../../shared/models/server.model.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { SESSION_MODE_VALUES, SESSION_MODE_STATEFUL } from './constants.js';
2
3
  // ====== v1.1 Configuration Schema (Server Template + Instance Model) ======
3
4
  /**
4
5
  * Instance selection strategy constants for multi-instance servers
@@ -155,7 +156,8 @@ export const LoggingConfigSchema = z
155
156
  jsonPretty: z.boolean().default(true),
156
157
  mcpCommDebug: z.boolean().default(false),
157
158
  apiDebug: z.boolean().default(false),
158
- gatewayDebug: z.boolean().default(false)
159
+ gatewayDebug: z.boolean().default(false),
160
+ showTraceContext: z.boolean().default(true)
159
161
  })
160
162
  .default({
161
163
  level: 'info',
@@ -163,7 +165,8 @@ export const LoggingConfigSchema = z
163
165
  jsonPretty: true,
164
166
  mcpCommDebug: false,
165
167
  apiDebug: false,
166
- gatewayDebug: false
168
+ gatewayDebug: false,
169
+ showTraceContext: true
167
170
  });
168
171
  // ====== Security Configuration Schema ======
169
172
  /**
@@ -222,7 +225,19 @@ export const SystemConfigSchema = z.object({
222
225
  theme: z.enum(['light', 'dark', 'system']).default('system'),
223
226
  logging: LoggingConfigSchema,
224
227
  // Using .optional() without .default() - code should use startup ?? {defaults} for default value
225
- startup: StartupConfigSchema.optional()
228
+ startup: StartupConfigSchema.optional(),
229
+ session: z
230
+ .object({
231
+ sessionModeRules: z
232
+ .object({
233
+ stateful: z.array(z.string()).optional().default([]),
234
+ stateless: z.array(z.string()).optional().default([])
235
+ })
236
+ .optional()
237
+ .default({ stateful: [], stateless: [] }),
238
+ defaultSessionMode: z.enum(SESSION_MODE_VALUES).optional().default(SESSION_MODE_STATEFUL)
239
+ })
240
+ .optional()
226
241
  })
227
242
  .default({
228
243
  host: 'localhost',
@@ -235,13 +250,21 @@ export const SystemConfigSchema = z.object({
235
250
  jsonPretty: true,
236
251
  mcpCommDebug: false,
237
252
  apiDebug: false,
238
- gatewayDebug: false
253
+ gatewayDebug: false,
254
+ showTraceContext: true
239
255
  },
240
256
  startup: {
241
257
  startupDelay: 3000,
242
258
  readyTimeout: 120000,
243
259
  maxConnectRetries: 3,
244
260
  connectRetryDelay: 5000
261
+ },
262
+ session: {
263
+ sessionModeRules: {
264
+ stateful: [],
265
+ stateless: []
266
+ },
267
+ defaultSessionMode: SESSION_MODE_STATEFUL
245
268
  }
246
269
  }),
247
270
  security: SecurityConfigSchema,
@@ -1,12 +1,16 @@
1
1
  /**
2
- * MCP Gateway endpoint using Streamable HTTP Transport (stateless mode)
3
- * Handles all MCP protocol requests at /mcp endpoint
2
+ * MCP Gateway endpoint using stateful Streamable HTTP Transport.
3
+ *
4
+ * Each client session gets its own transport+server pair, identified by mcp-session-id header.
5
+ * POST without sessionId creates a new session.
6
+ * POST/GET/DELETE with sessionId routes to the existing session transport.
4
7
  */
5
- import type { FastifyInstance } from 'fastify';
8
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
9
+ import type { SessionMode } from '../../../shared/models/constants.js';
6
10
  /**
7
- * MCP Gateway routes registration.
8
- *
9
- * @param fastify - Fastify instance to register routes on
11
+ * Resolves the effective session mode for a request.
12
+ * Priority: request header > UA keyword match > default (SESSION_MODE_STATEFUL).
10
13
  */
14
+ export declare function resolveSessionMode(request: FastifyRequest): SessionMode;
11
15
  export declare function mcpGatewayRoutes(fastify: FastifyInstance): Promise<void>;
12
16
  //# sourceMappingURL=gateway.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../../../src/api/mcp/gateway.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAU7E;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,iBAwG9D"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../../../src/api/mcp/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAC;AAmB7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAK/D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,WAAW,CAsBvE;AAgBD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,iBAiP9D"}
@@ -1,93 +1,259 @@
1
1
  /**
2
- * MCP Gateway endpoint using Streamable HTTP Transport (stateless mode)
3
- * Handles all MCP protocol requests at /mcp endpoint
2
+ * MCP Gateway endpoint using stateful Streamable HTTP Transport.
3
+ *
4
+ * Each client session gets its own transport+server pair, identified by mcp-session-id header.
5
+ * POST without sessionId creates a new session.
6
+ * POST/GET/DELETE with sessionId routes to the existing session transport.
4
7
  */
8
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
9
+ import { randomUUID } from 'crypto';
5
10
  import { logger, LOG_MODULES } from '../../utils/logger/index.js';
6
11
  import { stringifyForLogging, getMcpCommDebugSetting, getGatewayDebugSetting } from '../../utils/json-utils.js';
7
12
  import { wrapReplyForDebug } from './debug-response-wrapper.js';
8
- import { createSessionTransport } from '../../services/gateway/global-transport.js';
13
+ import { setupTransportLogging, createPerRequestTransport } from '../../services/gateway/global-transport.js';
14
+ import { sessionManager } from '../../services/gateway/session-manager.js';
15
+ import { gateway } from '../../services/gateway/gateway.service.js';
16
+ import { runWithRequestContext } from '../../utils/request-context.js';
17
+ import { configManager } from '../../config/config-manager.js';
18
+ import { SESSION_MODE_STATEFUL, SESSION_MODE_STATELESS } from '../../../shared/models/constants.js';
19
+ const MCP_SESSION_ID = 'mcp-session-id';
20
+ const MCP_SESSION_MODE = 'x-mcp-session-mode';
9
21
  /**
10
- * MCP Gateway routes registration.
11
- *
12
- * @param fastify - Fastify instance to register routes on
22
+ * Resolves the effective session mode for a request.
23
+ * Priority: request header > UA keyword match > default (SESSION_MODE_STATEFUL).
24
+ */
25
+ export function resolveSessionMode(request) {
26
+ // 1. Request header override (highest priority)
27
+ const header = request.headers[MCP_SESSION_MODE];
28
+ if (header === SESSION_MODE_STATELESS || header === SESSION_MODE_STATEFUL)
29
+ return header;
30
+ // 2. UA keyword matching (case-insensitive)
31
+ const ua = request.headers['user-agent'] || '';
32
+ const config = configManager.getConfig();
33
+ const rules = config?.system?.session?.sessionModeRules;
34
+ if (rules && ua) {
35
+ const uaLower = ua.toLowerCase();
36
+ for (const pattern of rules.stateless || []) {
37
+ if (uaLower.includes(pattern.toLowerCase()))
38
+ return SESSION_MODE_STATELESS;
39
+ }
40
+ for (const pattern of rules.stateful || []) {
41
+ if (uaLower.includes(pattern.toLowerCase()))
42
+ return SESSION_MODE_STATEFUL;
43
+ }
44
+ }
45
+ // 3. Default fallback
46
+ return config?.system?.session?.defaultSessionMode ?? SESSION_MODE_STATEFUL;
47
+ }
48
+ /**
49
+ * Inject trace context into transport so that deferred send/onmessage wrappers
50
+ * can read it when ALS context is lost (MCP SDK defers send() calls internally).
13
51
  */
52
+ function injectTransportTrace(transport, sessionId, traceId) {
53
+ const ctx = transport;
54
+ ctx._traceSessionId = sessionId || undefined;
55
+ ctx._traceId = traceId;
56
+ }
14
57
  export async function mcpGatewayRoutes(fastify) {
15
- // GET /mcp not used, this is a Streamable HTTP server
16
- fastify.get('/mcp', (_request, reply) => {
17
- reply.code(405).send({
18
- jsonrpc: '2.0',
19
- error: {
20
- code: -32000,
21
- message: 'GET /mcp is not supported. This is a Streamable HTTP MCP server — send POST /mcp with JSON-RPC requests directly.'
22
- },
23
- id: null
24
- });
25
- });
26
- // POST /mcp — JSON-RPC request handling
27
- const handlePostRequest = async (request, reply) => {
58
+ const logRequest = (request) => {
28
59
  if (getMcpCommDebugSetting()) {
29
- let initialLogMsg = `MCP Gateway ${request.method} ${request.url}`;
30
- initialLogMsg += `\nRequest headers: ${stringifyForLogging(request.headers)}`;
60
+ let msg = `MCP Gateway ${request.method} ${request.url}`;
61
+ msg += `\nRequest headers: ${stringifyForLogging(request.headers)}`;
31
62
  if (request.body) {
32
63
  try {
33
- const preview = stringifyForLogging(request.body);
34
- initialLogMsg += `\nBody: ${preview}`;
64
+ msg += `\nBody: ${stringifyForLogging(request.body)}`;
35
65
  }
36
66
  catch {
37
- initialLogMsg += `\nBody: [Unserializable]`;
67
+ msg += '\nBody: [Unserializable]';
38
68
  }
39
69
  }
40
- logger.debug(initialLogMsg, LOG_MODULES.COMMUNICATION);
70
+ logger.debug(msg, LOG_MODULES.COMMUNICATION);
41
71
  }
42
- reply.header('Content-Type', 'application/json');
43
- wrapReplyForDebug(reply, '');
44
- reply.hijack();
45
- try {
46
- if (getGatewayDebugSetting()) {
47
- logger.debug(`About to create session transport for MCP request`, LOG_MODULES.GATEWAY);
48
- }
49
- const { transport, server } = await createSessionTransport();
50
- if (getGatewayDebugSetting()) {
51
- logger.debug(`Created session transport successfully, handling MCP request`, LOG_MODULES.GATEWAY);
52
- }
53
- try {
54
- await transport.handleRequest(request.raw, reply.raw, request.body);
55
- if (getGatewayDebugSetting()) {
56
- logger.debug(`Successfully handled MCP request with server: ${server.constructor.name}`, LOG_MODULES.GATEWAY);
72
+ };
73
+ const sendError = (reply, statusCode, code, message, id = null) => {
74
+ if (!reply.raw.headersSent) {
75
+ reply.raw.writeHead(statusCode, { 'Content-Type': 'application/json' });
76
+ reply.raw.end(JSON.stringify({ jsonrpc: '2.0', error: { code, message }, id }));
77
+ }
78
+ };
79
+ fastify.all('/mcp', {
80
+ bodyLimit: 10 * 1024 * 1024,
81
+ handler: async (request, reply) => {
82
+ const sessionId = request.headers[MCP_SESSION_ID] || '';
83
+ const traceId = randomUUID();
84
+ await runWithRequestContext({ sessionId: sessionId || undefined, traceId }, async () => {
85
+ logRequest(request);
86
+ reply.header('Content-Type', 'application/json');
87
+ wrapReplyForDebug(reply, sessionId);
88
+ reply.hijack();
89
+ const sessionMode = resolveSessionMode(request);
90
+ if (sessionMode === SESSION_MODE_STATELESS) {
91
+ // Stateless mode: per-request transport, no session tracking, no SSE
92
+ if (request.method !== 'POST') {
93
+ sendError(reply, 405, -32000, 'GET/DELETE not supported in stateless session mode');
94
+ return;
95
+ }
96
+ try {
97
+ const { transport } = await createPerRequestTransport();
98
+ injectTransportTrace(transport, undefined, traceId);
99
+ await transport.handleRequest(request.raw, reply.raw, request.body);
100
+ if (getGatewayDebugSetting()) {
101
+ logger.debug('Handled MCP request in stateless mode', LOG_MODULES.GATEWAY);
102
+ }
103
+ }
104
+ catch (error) {
105
+ const msg = error instanceof Error ? error.message : String(error);
106
+ const stack = error instanceof Error ? error.stack : 'No stack available';
107
+ logger.error(`Stateless MCP error: ${msg}`, LOG_MODULES.GATEWAY);
108
+ logger.error(`Stack: ${stack}`, LOG_MODULES.GATEWAY);
109
+ if (!reply.raw.headersSent) {
110
+ reply.raw.writeHead(500, { 'Content-Type': 'application/json' });
111
+ reply.raw.end(JSON.stringify({
112
+ jsonrpc: '2.0',
113
+ error: { code: -32000, message: 'Internal Server Error' },
114
+ id: null
115
+ }));
116
+ }
117
+ }
118
+ return;
57
119
  }
58
- }
59
- finally {
60
- if (getGatewayDebugSetting()) {
61
- logger.debug(`Session transport request completed, resources will be GC'd`, LOG_MODULES.GATEWAY);
120
+ try {
121
+ // Existing session — route to its transport
122
+ if (sessionId) {
123
+ const session = sessionManager.getSession(sessionId);
124
+ if (!session) {
125
+ sendError(reply, 404, -32001, 'Session not found');
126
+ return;
127
+ }
128
+ sessionManager.updateSessionMethod(sessionId, request.method);
129
+ // Track SSE stream to prevent stale cleanup while GET is active
130
+ if (request.method === 'GET') {
131
+ sessionManager.markSseOpened(sessionId);
132
+ }
133
+ injectTransportTrace(session.transport, sessionId, traceId);
134
+ await session.transport.handleRequest(request.raw, reply.raw, request.method === 'POST' ? request.body : undefined);
135
+ if (request.method === 'GET') {
136
+ sessionManager.markSseClosed(sessionId);
137
+ }
138
+ if (getGatewayDebugSetting()) {
139
+ logger.debug(`Handled MCP ${request.method} for session ${sessionId}`, LOG_MODULES.GATEWAY);
140
+ }
141
+ return;
142
+ }
143
+ // New session — POST only (initialize)
144
+ if (request.method !== 'POST') {
145
+ sendError(reply, 400, -32000, 'Missing mcp-session-id header');
146
+ return;
147
+ }
148
+ // Create new stateful transport for this session
149
+ const transport = new StreamableHTTPServerTransport({
150
+ sessionIdGenerator: () => randomUUID(),
151
+ onsessionclosed: (id) => {
152
+ sessionManager.removeSession(id);
153
+ }
154
+ });
155
+ setupTransportLogging(transport);
156
+ const server = gateway.createConnectionServer();
157
+ await server.connect(transport);
158
+ injectTransportTrace(transport, sessionId || undefined, traceId);
159
+ await transport.handleRequest(request.raw, reply.raw, request.body);
160
+ // After handleRequest completes, sessionId is available if init succeeded
161
+ const newSessionId = transport.sessionId;
162
+ if (newSessionId) {
163
+ sessionManager.addSession(newSessionId, transport, server);
164
+ }
165
+ if (getGatewayDebugSetting()) {
166
+ logger.debug(`Created new session ${newSessionId} via MCP POST`, LOG_MODULES.GATEWAY);
167
+ }
62
168
  }
63
- }
64
- }
65
- catch (error) {
66
- const errorMessage = error instanceof Error ? error.message : String(error);
67
- const errorStack = error instanceof Error ? error.stack : 'No stack available';
68
- logger.error(`Error handling MCP request: ${errorMessage}`, LOG_MODULES.GATEWAY);
69
- logger.error(`Full error stack: ${errorStack}`, LOG_MODULES.GATEWAY);
70
- logger.error(`Request body that caused error: ${stringifyForLogging(request.body)}`, LOG_MODULES.GATEWAY);
71
- if (!reply.raw.headersSent) {
72
- reply.raw.writeHead(500, { 'Content-Type': 'application/json' });
73
- reply.raw.end(JSON.stringify({
74
- jsonrpc: '2.0',
75
- error: {
76
- code: -32000,
77
- message: 'Internal Server Error'
78
- },
79
- id: null
80
- }));
81
- }
169
+ catch (error) {
170
+ const errorMessage = error instanceof Error ? error.message : String(error);
171
+ const errorStack = error instanceof Error ? error.stack : 'No stack available';
172
+ logger.error(`Error handling MCP request: ${errorMessage}`, LOG_MODULES.GATEWAY);
173
+ logger.error(`Full error stack: ${errorStack}`, LOG_MODULES.GATEWAY);
174
+ logger.error(`Request body: ${stringifyForLogging(request.body)}`, LOG_MODULES.GATEWAY);
175
+ if (!reply.raw.headersSent) {
176
+ reply.raw.writeHead(500, { 'Content-Type': 'application/json' });
177
+ reply.raw.end(JSON.stringify({
178
+ jsonrpc: '2.0',
179
+ error: { code: -32000, message: 'Internal Server Error' },
180
+ id: null
181
+ }));
182
+ }
183
+ }
184
+ });
82
185
  }
83
- };
84
- fastify.post('/mcp', {
85
- bodyLimit: 10 * 1024 * 1024, // 10MB limit
86
- handler: handlePostRequest
87
186
  });
88
187
  // Subpath fallback
89
- fastify.post('/mcp/*', {
188
+ fastify.all('/mcp/*', {
90
189
  bodyLimit: 10 * 1024 * 1024,
91
- handler: handlePostRequest
190
+ handler: async (request, reply) => {
191
+ const sessionId = request.headers[MCP_SESSION_ID] || '';
192
+ const traceId = randomUUID();
193
+ await runWithRequestContext({ sessionId: sessionId || undefined, traceId }, async () => {
194
+ logRequest(request);
195
+ reply.header('Content-Type', 'application/json');
196
+ wrapReplyForDebug(reply, sessionId);
197
+ reply.hijack();
198
+ const sessionMode = resolveSessionMode(request);
199
+ if (sessionMode === SESSION_MODE_STATELESS) {
200
+ if (request.method !== 'POST') {
201
+ sendError(reply, 405, -32000, 'GET/DELETE not supported in stateless session mode');
202
+ return;
203
+ }
204
+ try {
205
+ const { transport } = await createPerRequestTransport();
206
+ injectTransportTrace(transport, undefined, traceId);
207
+ await transport.handleRequest(request.raw, reply.raw, request.body);
208
+ }
209
+ catch (error) {
210
+ const msg = error instanceof Error ? error.message : String(error);
211
+ logger.error(`Stateless MCP subpath error: ${msg}`, LOG_MODULES.GATEWAY);
212
+ sendError(reply, 500, -32000, 'Internal Server Error');
213
+ }
214
+ return;
215
+ }
216
+ if (!sessionId) {
217
+ if (!reply.raw.headersSent) {
218
+ reply.raw.writeHead(400, { 'Content-Type': 'application/json' });
219
+ reply.raw.end(JSON.stringify({
220
+ jsonrpc: '2.0',
221
+ error: { code: -32000, message: 'Missing mcp-session-id header' },
222
+ id: null
223
+ }));
224
+ }
225
+ return;
226
+ }
227
+ const session = sessionManager.getSession(sessionId);
228
+ if (!session) {
229
+ if (!reply.raw.headersSent) {
230
+ reply.raw.writeHead(404, { 'Content-Type': 'application/json' });
231
+ reply.raw.end(JSON.stringify({
232
+ jsonrpc: '2.0',
233
+ error: { code: -32001, message: 'Session not found' },
234
+ id: null
235
+ }));
236
+ }
237
+ return;
238
+ }
239
+ try {
240
+ sessionManager.updateSessionMethod(sessionId, request.method);
241
+ injectTransportTrace(session.transport, sessionId, traceId);
242
+ await session.transport.handleRequest(request.raw, reply.raw, request.method === 'POST' ? request.body : undefined);
243
+ }
244
+ catch (error) {
245
+ const errorMessage = error instanceof Error ? error.message : String(error);
246
+ logger.error(`Error handling MCP subpath request: ${errorMessage}`, LOG_MODULES.GATEWAY);
247
+ if (!reply.raw.headersSent) {
248
+ reply.raw.writeHead(500, { 'Content-Type': 'application/json' });
249
+ reply.raw.end(JSON.stringify({
250
+ jsonrpc: '2.0',
251
+ error: { code: -32000, message: 'Internal Server Error' },
252
+ id: null
253
+ }));
254
+ }
255
+ }
256
+ });
257
+ }
92
258
  });
93
259
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hub-tools.d.ts","sourceRoot":"","sources":["../../../../../src/api/web/hub-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAqB1C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,eAAe,iBAmL/D"}
1
+ {"version":3,"file":"hub-tools.d.ts","sourceRoot":"","sources":["../../../../../src/api/web/hub-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAqB1C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,eAAe,iBAsL/D"}
@@ -168,13 +168,13 @@ export async function webHubToolsRoutes(fastify) {
168
168
  });
169
169
  // GET /web/hub-tools/search - Search tools across all connected servers
170
170
  fastify.get('/web/hub-tools/search', async (request, reply) => {
171
- const { q } = request.query;
171
+ const { q, limit } = request.query;
172
172
  if (!q || typeof q !== 'string' || q.trim().length === 0) {
173
173
  return reply.code(400).send({
174
174
  error: 'Bad Request',
175
175
  message: 'Query parameter "q" is required'
176
176
  });
177
177
  }
178
- return hubToolsService.searchTools(q.trim());
178
+ return hubToolsService.searchTools(q.trim(), limit);
179
179
  });
180
180
  }
@@ -4,7 +4,7 @@ import { FastifyInstance } from 'fastify';
4
4
  *
5
5
  * Returns aggregated tools from the gateway cache with wrapped inputSchema
6
6
  * (including serverName, toolName, toolArgs, and requestOptions fields).
7
- * Uses straightforward string matching on tool name and description.
7
+ * Uses tokenized matching on tool name and description, sorted by match count.
8
8
  *
9
9
  * @param fastify - The Fastify instance to register routes on
10
10
  * @returns Promise that resolves when all routes are registered
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/api/web/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG1C;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,iBAoD7D"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/api/web/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI1C;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,iBAoD7D"}
@@ -1,33 +1,36 @@
1
1
  import { MCP_HUB_LITE_SERVER } from '../../../shared/models/constants.js';
2
+ import { countMatchingTokens } from '../../utils/search-matcher.js';
2
3
  /**
3
4
  * Tool Search API Routes
4
5
  *
5
6
  * Returns aggregated tools from the gateway cache with wrapped inputSchema
6
7
  * (including serverName, toolName, toolArgs, and requestOptions fields).
7
- * Uses straightforward string matching on tool name and description.
8
+ * Uses tokenized matching on tool name and description, sorted by match count.
8
9
  *
9
10
  * @param fastify - The Fastify instance to register routes on
10
11
  * @returns Promise that resolves when all routes are registered
11
12
  */
12
13
  export async function webSearchRoutes(fastify) {
13
- // GET /web/search - Search for tools with simple string matching
14
+ // GET /web/search - Search for tools with tokenized matching
14
15
  fastify.get('/web/search', async (request) => {
15
- const { q, limit = 50 } = request.query;
16
+ const { q, limit = 5 } = request.query;
17
+ const effectiveLimit = Math.min(Math.max(1, limit), 10);
16
18
  // Dynamic import to avoid circular dependency at module init time
17
19
  const { getExternalGatewayTools } = await import('../../services/gateway/tool-list-generator.js');
18
20
  const gatewayTools = getExternalGatewayTools();
19
21
  const query = q?.toLowerCase() || '';
20
- // Filter by search query
21
- const queryMatched = gatewayTools.filter((tool) => {
22
+ // Score, filter, sort, and slice by token match count
23
+ const scored = gatewayTools
24
+ .map((tool) => {
22
25
  if (!query)
23
- return true;
24
- // Match against tool name (gateway-resolved name)
25
- const nameMatch = tool.name.toLowerCase().includes(query);
26
- // Match against description (contains "[From serverName] original description")
27
- const descMatch = tool.description?.toLowerCase().includes(query);
28
- return nameMatch || descMatch;
29
- });
30
- const mappedResults = queryMatched.map((tool) => {
26
+ return { tool, matchCount: Number.MAX_SAFE_INTEGER };
27
+ const matchCount = countMatchingTokens(query, [tool.name, tool.description || '']);
28
+ return { tool, matchCount };
29
+ })
30
+ .filter((item) => item.matchCount > 0)
31
+ .sort((a, b) => b.matchCount - a.matchCount)
32
+ .slice(0, effectiveLimit);
33
+ const results = scored.map(({ tool }) => {
31
34
  // Extract serverName from description format "[From serverName] ..."
32
35
  const descMatch = tool.description?.match(/^\[From\s+(.+?)\]/);
33
36
  const serverName = descMatch ? descMatch[1] : MCP_HUB_LITE_SERVER;
@@ -38,12 +41,11 @@ export async function webSearchRoutes(fastify) {
38
41
  inputSchema: tool.inputSchema
39
42
  };
40
43
  });
41
- const results = mappedResults.slice(0, limit);
42
44
  return {
43
45
  results,
44
46
  pagination: {
45
- total: mappedResults.length,
46
- limit,
47
+ total: results.length,
48
+ limit: effectiveLimit,
47
49
  returned: results.length
48
50
  },
49
51
  metadata: {