@loop_ouroboros/mcp-hub-lite 1.3.0 → 1.3.1

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/CHANGELOG.md +49 -0
  2. package/README.md +405 -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/sessions.d.ts +1 -27
  39. package/dist/server/src/api/web/sessions.d.ts.map +1 -1
  40. package/dist/server/src/api/web/sessions.js +8 -97
  41. package/dist/server/src/app.d.ts.map +1 -1
  42. package/dist/server/src/app.js +5 -0
  43. package/dist/server/src/cli/commands/status.js +39 -1
  44. package/dist/server/src/cli/commands/use-guide.d.ts +0 -8
  45. package/dist/server/src/cli/commands/use-guide.d.ts.map +1 -1
  46. package/dist/server/src/cli/commands/use-guide.js +28 -170
  47. package/dist/server/src/cli/server.d.ts +10 -0
  48. package/dist/server/src/cli/server.d.ts.map +1 -1
  49. package/dist/server/src/cli/server.js +31 -1
  50. package/dist/server/src/server/dev-server.js +2 -0
  51. package/dist/server/src/server/runner.d.ts.map +1 -1
  52. package/dist/server/src/server/runner.js +2 -0
  53. package/dist/server/src/services/connection/connection-manager.d.ts +2 -0
  54. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  55. package/dist/server/src/services/connection/connection-manager.js +14 -7
  56. package/dist/server/src/services/gateway/gateway.service.d.ts +13 -0
  57. package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
  58. package/dist/server/src/services/gateway/gateway.service.js +72 -0
  59. package/dist/server/src/services/gateway/global-transport.d.ts +20 -10
  60. package/dist/server/src/services/gateway/global-transport.d.ts.map +1 -1
  61. package/dist/server/src/services/gateway/global-transport.js +50 -34
  62. package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
  63. package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +22 -6
  64. package/dist/server/src/services/gateway/request-handlers/resources-handler.d.ts.map +1 -1
  65. package/dist/server/src/services/gateway/request-handlers/resources-handler.js +5 -1
  66. package/dist/server/src/services/gateway/session-manager.d.ts +101 -0
  67. package/dist/server/src/services/gateway/session-manager.d.ts.map +1 -0
  68. package/dist/server/src/services/gateway/session-manager.js +256 -0
  69. package/dist/server/src/services/hub-tools/resource-generator.d.ts +1 -1
  70. package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
  71. package/dist/server/src/services/hub-tools/resource-generator.js +11 -9
  72. package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
  73. package/dist/server/src/services/hub-tools.service.js +3 -1
  74. package/dist/server/src/utils/json-utils.d.ts +9 -0
  75. package/dist/server/src/utils/json-utils.d.ts.map +1 -1
  76. package/dist/server/src/utils/json-utils.js +19 -0
  77. package/dist/server/src/utils/logger/index.d.ts +1 -1
  78. package/dist/server/src/utils/logger/index.d.ts.map +1 -1
  79. package/dist/server/src/utils/logger/index.js +1 -1
  80. package/dist/server/src/utils/logger/log-context.d.ts +1 -0
  81. package/dist/server/src/utils/logger/log-context.d.ts.map +1 -1
  82. package/dist/server/src/utils/logger/log-formatter.d.ts.map +1 -1
  83. package/dist/server/src/utils/logger/log-formatter.js +25 -11
  84. package/dist/server/src/utils/logger/log-output.d.ts +17 -1
  85. package/dist/server/src/utils/logger/log-output.d.ts.map +1 -1
  86. package/dist/server/src/utils/logger/log-output.js +46 -40
  87. package/dist/server/src/utils/logger/logger.d.ts.map +1 -1
  88. package/dist/server/src/utils/logger/logger.js +18 -2
  89. package/dist/server/src/utils/request-context.d.ts +8 -70
  90. package/dist/server/src/utils/request-context.d.ts.map +1 -1
  91. package/dist/server/src/utils/request-context.js +11 -70
  92. package/dist/server/tests/unit/config/config.schema.test.js +2 -1
  93. package/dist/server/tests/unit/server/runner.test.js +14 -7
  94. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts +2 -0
  95. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts.map +1 -0
  96. package/dist/server/tests/unit/services/gateway-session-mode.test.js +174 -0
  97. package/dist/server/tests/unit/services/hub-tools.service.test.js +4 -4
  98. package/dist/server/tests/unit/utils/config.test.js +14 -7
  99. package/dist/server/tests/unit/utils/log-output.test.d.ts +2 -0
  100. package/dist/server/tests/unit/utils/log-output.test.d.ts.map +1 -0
  101. package/dist/server/tests/unit/utils/log-output.test.js +198 -0
  102. package/dist/server/vitest.config.d.ts.map +1 -1
  103. package/dist/server/vitest.config.js +0 -2
  104. package/package.json +1 -1
  105. package/dist/client/assets/ResourcesView-CU0VbNy5.js +0 -1
  106. package/dist/client/assets/ServerDetail-bcQ8BVXR.js +0 -2
  107. package/dist/client/assets/SettingsView-B1DxbFP3.js +0 -1
  108. package/dist/client/assets/ToolCallDialog-DEapCO06.js +0 -1
  109. package/dist/client/assets/ToolsView-DA0u_bCw.js +0 -1
  110. package/dist/client/assets/_baseClone-B991Lvrt.js +0 -1
  111. package/dist/client/assets/el-input-5YzZrwir.js +0 -1
  112. package/dist/client/assets/el-loading-DE3FcxNH.js +0 -1
  113. package/dist/client/assets/el-overlay-BTeTueuN.js +0 -1
  114. package/dist/client/assets/el-radio-group-Y1E2bxIW.js +0 -1
  115. package/dist/client/assets/el-skeleton-item-DhgR50Jx.js +0 -1
  116. package/dist/client/assets/el-switch-fF--nMSD.js +0 -1
  117. package/dist/client/assets/el-tab-pane-rvS_KTwP.js +0 -1
  118. package/dist/client/assets/el-table-column-B1O8mY47.js +0 -1
  119. package/dist/client/assets/index-DkqV9kH4.js +0 -2
  120. package/dist/client/assets/raf-Cj-gATZv.js +0 -1
@@ -87,9 +87,29 @@ export function logNotificationMessage(message, context, serverId) {
87
87
  logStorage.append(serverId, logLevel, `[${serverName}] ${messageContent}`);
88
88
  }
89
89
  }
90
+ /**
91
+ * Check if JSON string contains data:image/*;base64 data URIs.
92
+ * These commonly appear in serverInfo.icons[].src and other metadata fields.
93
+ *
94
+ * @param data - JSON string to check
95
+ * @returns true if it contains data:image data URIs
96
+ */
97
+ export function hasDataUriImage(data) {
98
+ return /data:image\/[^;]+;base64,/.test(data);
99
+ }
100
+ /**
101
+ * Simplify data URI images by replacing base64 payload with placeholder.
102
+ * Matches all data:image/*;base64,... patterns and truncates the payload.
103
+ *
104
+ * @param data - JSON string containing data URIs
105
+ * @returns JSON string with base64 payloads replaced
106
+ */
107
+ export function simplifyDataUriImages(data) {
108
+ return data.replace(/(data:image\/[^;]+;base64,)[A-Za-z0-9+/=]+/g, '$1[Truncated]');
109
+ }
90
110
  /**
91
111
  * Format an MCP message for logging, with simplification for tools/list,
92
- * resources/list, capabilities responses, and image content.
112
+ * resources/list, capabilities responses, image content, and data URI images.
93
113
  *
94
114
  * @param message - The MCP message object to format
95
115
  * @returns Formatted log message string
@@ -101,8 +121,20 @@ export function formatMcpMessageForLogging(message) {
101
121
  if (isToolsListResponse(rawJson)) {
102
122
  const simplified = simplifyToolsListResponse(rawJson);
103
123
  if (simplified === null) {
104
- // Could not simplify, use pretty JSON formatting
105
- logMessage = stringifyForLogging(message);
124
+ // Could not simplify, fall through to other checks
125
+ if (hasDataUriImage(rawJson)) {
126
+ const truncated = simplifyDataUriImages(rawJson);
127
+ try {
128
+ const parsed = JSON.parse(truncated);
129
+ logMessage = stringifyForLogging(parsed);
130
+ }
131
+ catch {
132
+ logMessage = truncated;
133
+ }
134
+ }
135
+ else {
136
+ logMessage = stringifyForLogging(message);
137
+ }
106
138
  }
107
139
  else {
108
140
  logMessage = simplified;
@@ -118,6 +150,16 @@ export function formatMcpMessageForLogging(message) {
118
150
  logMessage = simplified;
119
151
  }
120
152
  }
153
+ else if (hasDataUriImage(rawJson)) {
154
+ const simplified = simplifyDataUriImages(rawJson);
155
+ try {
156
+ const parsed = JSON.parse(simplified);
157
+ logMessage = stringifyForLogging(parsed);
158
+ }
159
+ catch {
160
+ logMessage = simplified;
161
+ }
162
+ }
121
163
  else {
122
164
  logMessage = stringifyForLogging(message);
123
165
  }
@@ -192,7 +234,7 @@ export function simplifyImageContent(data) {
192
234
  'data' in item) {
193
235
  return {
194
236
  ...item,
195
- data: '[BinaryImageData]'
237
+ data: '[Truncated]'
196
238
  };
197
239
  }
198
240
  return item;
@@ -243,14 +285,6 @@ export function isToolsListResponse(data) {
243
285
  if ('resources' in result) {
244
286
  return true;
245
287
  }
246
- if ('capabilities' in result &&
247
- typeof result.capabilities === 'object' &&
248
- result.capabilities !== null) {
249
- const capabilities = result.capabilities;
250
- if ('tools' in capabilities || 'resources' in capabilities) {
251
- return true;
252
- }
253
- }
254
288
  }
255
289
  }
256
290
  }
@@ -301,34 +335,6 @@ export function simplifyToolsListResponse(data) {
301
335
  // No resources, don't simplify
302
336
  return null;
303
337
  }
304
- if ('capabilities' in result &&
305
- typeof result.capabilities === 'object' &&
306
- result.capabilities !== null) {
307
- const capabilities = result.capabilities;
308
- let toolsCount = 0;
309
- let resourcesCount = 0;
310
- if ('tools' in capabilities &&
311
- typeof capabilities.tools === 'object' &&
312
- capabilities.tools !== null) {
313
- toolsCount = Object.keys(capabilities.tools).length;
314
- }
315
- if ('resources' in capabilities &&
316
- typeof capabilities.resources === 'object' &&
317
- capabilities.resources !== null) {
318
- resourcesCount = Object.keys(capabilities.resources).length;
319
- }
320
- if (toolsCount > 0 && resourcesCount > 0) {
321
- return `Returned ${toolsCount} tools and ${resourcesCount} resources`;
322
- }
323
- else if (toolsCount > 0) {
324
- return `Returned ${toolsCount} tools`;
325
- }
326
- else if (resourcesCount > 0) {
327
- return `Returned ${resourcesCount} resources`;
328
- }
329
- // No tools or resources, don't simplify
330
- return null;
331
- }
332
338
  }
333
339
  }
334
340
  }
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../../../src/utils/logger/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAc5C,qBAAa,MAAM;IACjB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,SAAS,CAA8B;gBAEnC,KAAK,GAAE,QAAiB;IAoBpC;;;;;;;;;OASG;IACI,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAIzC;;;;;;;;;;;;OAYG;IACI,YAAY,CAAC,aAAa,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAO5E,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAIvC;;;;;;;;;OASG;IACI,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAItC;;;OAGG;IACH,SAAS,CAAC,YAAY,EAAE,QAAQ,GAAG,OAAO;IAK1C;;OAEG;IACH,OAAO,CAAC,GAAG;IAuDX;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAKhD;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK/C;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK/C;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAKhD,OAAO,CAAC,qBAAqB;IAkB7B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B;;;OAGG;IACH,SAAS,CACP,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,GACvC,IAAI;IAuBP;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAuD3B;;;OAGG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;OAGG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;OAGG;IACH,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM;IAIvF;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM;IAIhF;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAInC;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAInC;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM;IAIvC;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAG/B"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../../../src/utils/logger/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAmB5C,qBAAa,MAAM;IACjB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,SAAS,CAA8B;gBAEnC,KAAK,GAAE,QAAiB;IAoBpC;;;;;;;;;OASG;IACI,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAIzC;;;;;;;;;;;;OAYG;IACI,YAAY,CAAC,aAAa,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAO5E,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAIvC;;;;;;;;;OASG;IACI,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAItC;;;OAGG;IACH,SAAS,CAAC,YAAY,EAAE,QAAQ,GAAG,OAAO;IAK1C;;OAEG;IACH,OAAO,CAAC,GAAG;IA8DX;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAKhD;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK/C;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAK/C;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAKhD,OAAO,CAAC,qBAAqB;IAkB7B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B;;;OAGG;IACH,SAAS,CACP,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,GACvC,IAAI;IAuBP;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA6D3B;;;OAGG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;OAGG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;OAGG;IACH,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM;IAIvF;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM;IAIhF;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAInC;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAInC;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM;IAIvC;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAG/B"}
@@ -30,7 +30,8 @@
30
30
  import { DevLogger } from './dev-logger.js';
31
31
  import { LOG_MODULES } from './log-modules.js';
32
32
  import { createColoredLogMessage, createLogMessage, formatError, formatTimestamp, formatLogLevel, formatPid, getCallerInfo, formatCallerInfo } from './log-formatter.js';
33
- import { setDevModeEnabled } from '../json-utils.js';
33
+ import { setDevModeEnabled, getShowTraceContextSetting } from '../json-utils.js';
34
+ import { getTraceIdFromContext, getSessionIdFromContext, isInRequestContext } from '../request-context.js';
34
35
  export class Logger {
35
36
  level = 'info';
36
37
  useStderr = false;
@@ -124,9 +125,17 @@ export class Logger {
124
125
  // Build context object
125
126
  const context = {
126
127
  ...(options?.module && { module: options.module }),
128
+ ...(options?.sessionId && { sessionId: options.sessionId }),
127
129
  ...(options?.traceId && { traceId: options.traceId }),
128
130
  ...(options?.spanId && { spanId: options.spanId })
129
131
  };
132
+ // Auto-inject trace context from ALS when showTraceContext enabled
133
+ if (getShowTraceContextSetting() && isInRequestContext()) {
134
+ if (!context.sessionId)
135
+ context.sessionId = getSessionIdFromContext() || '-';
136
+ if (!context.traceId)
137
+ context.traceId = getTraceIdFromContext() || '-';
138
+ }
130
139
  // Add caller info if enabled
131
140
  if (this.showCaller) {
132
141
  const callerInfo = getCallerInfo(4); // Skip 4 frames to get to the actual caller
@@ -241,7 +250,7 @@ export class Logger {
241
250
  const optionsIndex = args.findIndex((arg) => typeof arg === 'object' &&
242
251
  arg !== null &&
243
252
  !Array.isArray(arg) &&
244
- ('module' in arg || 'traceId' in arg || 'spanId' in arg));
253
+ ('module' in arg || 'sessionId' in arg || 'traceId' in arg || 'spanId' in arg));
245
254
  if (optionsIndex !== -1) {
246
255
  const options = args[optionsIndex];
247
256
  const restArgs = [...args.slice(0, optionsIndex), ...args.slice(optionsIndex + 1)];
@@ -287,6 +296,13 @@ export class Logger {
287
296
  ...context,
288
297
  serverName
289
298
  };
299
+ // Auto-inject trace context from ALS when showTraceContext enabled
300
+ if (getShowTraceContextSetting() && isInRequestContext()) {
301
+ if (!logContext.sessionId)
302
+ logContext.sessionId = getSessionIdFromContext() || '-';
303
+ if (!logContext.traceId)
304
+ logContext.traceId = getTraceIdFromContext() || '-';
305
+ }
290
306
  // Add caller info if enabled (only once per serverLog call would be better,
291
307
  // but for consistency with multi-line logging, we add it for each line)
292
308
  if (this.showCaller) {
@@ -1,71 +1,9 @@
1
- /**
2
- * Request context management using AsyncLocalStorage for request-scoped data.
3
- *
4
- * This module provides a way to store and access request-specific context data
5
- * across asynchronous operations without having to pass context objects through
6
- * every function call. It uses Node.js AsyncLocalStorage to maintain context
7
- * throughout the request lifecycle.
8
- *
9
- * The primary use case is storing session context information (sessionId, clientName,
10
- * cwd, project, etc.) that needs to be accessible from any part of the application
11
- * during request processing.
12
- *
13
- * @module utils/request-context
14
- */
15
- import { AsyncLocalStorage } from 'async_hooks';
16
- import type { SessionContext } from '../../shared/types/session-context.types.js';
17
- /**
18
- * AsyncLocalStorage instance for storing request context.
19
- *
20
- * This storage holds the SessionContext object for the current request and
21
- * makes it available throughout the entire request processing chain,
22
- * including asynchronous operations and nested function calls.
23
- *
24
- * @example
25
- * ```typescript
26
- * // In route handler
27
- * await requestContext.run(sessionContext, async () => {
28
- * // Any code executed here can access the session context
29
- * const ctx = getSessionContext();
30
- * console.log(ctx.sessionId);
31
- * });
32
- * ```
33
- */
34
- export declare const requestContext: AsyncLocalStorage<SessionContext>;
35
- /**
36
- * Retrieves the current request's session context.
37
- *
38
- * This function returns the SessionContext object stored in the AsyncLocalStorage
39
- * for the current request. It should only be called within a request context
40
- * that has been established using requestContext.run().
41
- *
42
- * @returns {SessionContext | undefined} The current request's session context, or undefined if not in a request context
43
- *
44
- * @example
45
- * ```typescript
46
- * const context = getSessionContext();
47
- * if (context) {
48
- * console.log(`Processing request for session: ${context.sessionId}`);
49
- * }
50
- * ```
51
- */
52
- export declare function getSessionContext(): SessionContext | undefined;
53
- /**
54
- * Retrieves the current request's working directory (cwd).
55
- *
56
- * This is a convenience function that extracts the cwd property from the
57
- * current request's session context. It's commonly used in file operations
58
- * that need to respect the session's current working directory.
59
- *
60
- * @returns {string | undefined} The current request's working directory, or undefined if not available
61
- *
62
- * @example
63
- * ```typescript
64
- * const cwd = getSessionCwd();
65
- * if (cwd) {
66
- * const fullPath = path.join(cwd, relativePath);
67
- * }
68
- * ```
69
- */
70
- export declare function getSessionCwd(): string | undefined;
1
+ export interface RequestContext {
2
+ sessionId?: string;
3
+ traceId?: string;
4
+ }
5
+ export declare function runWithRequestContext(context: RequestContext, fn: () => Promise<void>): Promise<void>;
6
+ export declare function getSessionIdFromContext(): string | undefined;
7
+ export declare function getTraceIdFromContext(): string | undefined;
8
+ export declare function isInRequestContext(): boolean;
71
9
  //# sourceMappingURL=request-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../../../src/utils/request-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAE7E;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,mCAA0C,CAAC;AAEtE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD"}
1
+ {"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../../../src/utils/request-context.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,EACvB,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GACtB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,SAAS,CAE5D;AAED,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAE1D;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C"}
@@ -1,73 +1,14 @@
1
- /**
2
- * Request context management using AsyncLocalStorage for request-scoped data.
3
- *
4
- * This module provides a way to store and access request-specific context data
5
- * across asynchronous operations without having to pass context objects through
6
- * every function call. It uses Node.js AsyncLocalStorage to maintain context
7
- * throughout the request lifecycle.
8
- *
9
- * The primary use case is storing session context information (sessionId, clientName,
10
- * cwd, project, etc.) that needs to be accessible from any part of the application
11
- * during request processing.
12
- *
13
- * @module utils/request-context
14
- */
15
1
  import { AsyncLocalStorage } from 'async_hooks';
16
- /**
17
- * AsyncLocalStorage instance for storing request context.
18
- *
19
- * This storage holds the SessionContext object for the current request and
20
- * makes it available throughout the entire request processing chain,
21
- * including asynchronous operations and nested function calls.
22
- *
23
- * @example
24
- * ```typescript
25
- * // In route handler
26
- * await requestContext.run(sessionContext, async () => {
27
- * // Any code executed here can access the session context
28
- * const ctx = getSessionContext();
29
- * console.log(ctx.sessionId);
30
- * });
31
- * ```
32
- */
33
- export const requestContext = new AsyncLocalStorage();
34
- /**
35
- * Retrieves the current request's session context.
36
- *
37
- * This function returns the SessionContext object stored in the AsyncLocalStorage
38
- * for the current request. It should only be called within a request context
39
- * that has been established using requestContext.run().
40
- *
41
- * @returns {SessionContext | undefined} The current request's session context, or undefined if not in a request context
42
- *
43
- * @example
44
- * ```typescript
45
- * const context = getSessionContext();
46
- * if (context) {
47
- * console.log(`Processing request for session: ${context.sessionId}`);
48
- * }
49
- * ```
50
- */
51
- export function getSessionContext() {
52
- return requestContext.getStore();
2
+ const als = new AsyncLocalStorage();
3
+ export function runWithRequestContext(context, fn) {
4
+ return als.run(context, fn);
53
5
  }
54
- /**
55
- * Retrieves the current request's working directory (cwd).
56
- *
57
- * This is a convenience function that extracts the cwd property from the
58
- * current request's session context. It's commonly used in file operations
59
- * that need to respect the session's current working directory.
60
- *
61
- * @returns {string | undefined} The current request's working directory, or undefined if not available
62
- *
63
- * @example
64
- * ```typescript
65
- * const cwd = getSessionCwd();
66
- * if (cwd) {
67
- * const fullPath = path.join(cwd, relativePath);
68
- * }
69
- * ```
70
- */
71
- export function getSessionCwd() {
72
- return requestContext.getStore()?.cwd;
6
+ export function getSessionIdFromContext() {
7
+ return als.getStore()?.sessionId;
8
+ }
9
+ export function getTraceIdFromContext() {
10
+ return als.getStore()?.traceId;
11
+ }
12
+ export function isInRequestContext() {
13
+ return als.getStore() !== undefined;
73
14
  }
@@ -218,7 +218,8 @@ describe('Config Schema (v1.1)', () => {
218
218
  jsonPretty: true,
219
219
  mcpCommDebug: false,
220
220
  apiDebug: false,
221
- gatewayDebug: false
221
+ gatewayDebug: false,
222
+ showTraceContext: true
222
223
  }
223
224
  },
224
225
  security: {
@@ -95,7 +95,8 @@ describe('Server Runner', () => {
95
95
  jsonPretty: true,
96
96
  mcpCommDebug: false,
97
97
  apiDebug: false,
98
- gatewayDebug: false
98
+ gatewayDebug: false,
99
+ showTraceContext: true
99
100
  }
100
101
  },
101
102
  security: {
@@ -136,7 +137,8 @@ describe('Server Runner', () => {
136
137
  jsonPretty: true,
137
138
  mcpCommDebug: false,
138
139
  apiDebug: false,
139
- gatewayDebug: false
140
+ gatewayDebug: false,
141
+ showTraceContext: true
140
142
  }
141
143
  },
142
144
  security: {
@@ -186,7 +188,8 @@ describe('Server Runner', () => {
186
188
  jsonPretty: true,
187
189
  mcpCommDebug: false,
188
190
  apiDebug: false,
189
- gatewayDebug: false
191
+ gatewayDebug: false,
192
+ showTraceContext: true
190
193
  }
191
194
  },
192
195
  security: {
@@ -241,7 +244,8 @@ describe('Server Runner', () => {
241
244
  jsonPretty: true,
242
245
  mcpCommDebug: false,
243
246
  apiDebug: false,
244
- gatewayDebug: false
247
+ gatewayDebug: false,
248
+ showTraceContext: true
245
249
  }
246
250
  },
247
251
  security: {
@@ -359,7 +363,8 @@ describe('Server Runner', () => {
359
363
  jsonPretty: true,
360
364
  mcpCommDebug: false,
361
365
  apiDebug: false,
362
- gatewayDebug: false
366
+ gatewayDebug: false,
367
+ showTraceContext: true
363
368
  }
364
369
  },
365
370
  security: {
@@ -414,7 +419,8 @@ describe('Server Runner', () => {
414
419
  jsonPretty: true,
415
420
  mcpCommDebug: false,
416
421
  apiDebug: false,
417
- gatewayDebug: false
422
+ gatewayDebug: false,
423
+ showTraceContext: true
418
424
  }
419
425
  },
420
426
  security: {
@@ -465,7 +471,8 @@ describe('Server Runner', () => {
465
471
  jsonPretty: true,
466
472
  mcpCommDebug: false,
467
473
  apiDebug: false,
468
- gatewayDebug: false
474
+ gatewayDebug: false,
475
+ showTraceContext: true
469
476
  }
470
477
  },
471
478
  security: {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gateway-session-mode.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-session-mode.test.d.ts","sourceRoot":"","sources":["../../../../../tests/unit/services/gateway-session-mode.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,174 @@
1
+ import { describe, test, expect, beforeEach, vi } from 'vitest';
2
+ import { SESSION_MODE_STATEFUL, SESSION_MODE_STATELESS } from '../../../shared/models/constants.js';
3
+ // Mock configManager before importing the module under test
4
+ const mockGetConfig = vi.fn();
5
+ vi.mock('@config/config-manager.js', () => ({
6
+ configManager: {
7
+ getConfig: () => mockGetConfig()
8
+ }
9
+ }));
10
+ // We need to import resolveSessionMode from gateway.ts
11
+ // It's exported from the module, but the module has side-effects (Fastify routes)
12
+ // Use dynamic import after mocks are set up
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ let resolveSessionMode;
15
+ beforeEach(async () => {
16
+ vi.resetModules();
17
+ mockGetConfig.mockReset();
18
+ const mod = await import('../../../src/api/mcp/gateway.js');
19
+ resolveSessionMode = mod.resolveSessionMode;
20
+ });
21
+ function makeRequest(headers = {}) {
22
+ return { headers };
23
+ }
24
+ describe('resolveSessionMode', () => {
25
+ describe('request header override (highest priority)', () => {
26
+ test('x-mcp-session-mode: stateless overrides UA match', () => {
27
+ mockGetConfig.mockReturnValue({
28
+ system: {
29
+ session: {
30
+ sessionModeRules: { stateful: ['claude-code'], stateless: [] },
31
+ defaultSessionMode: SESSION_MODE_STATEFUL
32
+ }
33
+ }
34
+ });
35
+ const request = makeRequest({
36
+ 'x-mcp-session-mode': SESSION_MODE_STATELESS,
37
+ 'user-agent': 'claude-code/2.1.140 (cli)'
38
+ });
39
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATELESS);
40
+ });
41
+ test('x-mcp-session-mode: stateful overrides UA match', () => {
42
+ mockGetConfig.mockReturnValue({
43
+ system: {
44
+ session: {
45
+ sessionModeRules: { stateful: [], stateless: ['cherrystudio'] },
46
+ defaultSessionMode: SESSION_MODE_STATELESS
47
+ }
48
+ }
49
+ });
50
+ const request = makeRequest({
51
+ 'x-mcp-session-mode': SESSION_MODE_STATEFUL,
52
+ 'user-agent': 'CherryStudio/1.9.7'
53
+ });
54
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATEFUL);
55
+ });
56
+ });
57
+ describe('UA keyword matching', () => {
58
+ test('matches stateless pattern (case-insensitive)', () => {
59
+ mockGetConfig.mockReturnValue({
60
+ system: {
61
+ session: {
62
+ sessionModeRules: { stateful: [], stateless: ['cherrystudio'] },
63
+ defaultSessionMode: SESSION_MODE_STATEFUL
64
+ }
65
+ }
66
+ });
67
+ const request = makeRequest({
68
+ 'user-agent': 'Mozilla/5.0 ... CherryStudio/1.9.7 Chrome/146.0.7680.188 Electron/41.2.1'
69
+ });
70
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATELESS);
71
+ });
72
+ test('matches stateless pattern with different casing in UA', () => {
73
+ mockGetConfig.mockReturnValue({
74
+ system: {
75
+ session: {
76
+ sessionModeRules: { stateful: [], stateless: ['cherrystudio'] },
77
+ defaultSessionMode: SESSION_MODE_STATEFUL
78
+ }
79
+ }
80
+ });
81
+ const request = makeRequest({ 'user-agent': 'CHERRYSTUDIO/1.9.7' });
82
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATELESS);
83
+ });
84
+ test('matches stateless pattern with different casing in rule', () => {
85
+ mockGetConfig.mockReturnValue({
86
+ system: {
87
+ session: {
88
+ sessionModeRules: { stateful: [], stateless: ['CherryStudio'] },
89
+ defaultSessionMode: SESSION_MODE_STATEFUL
90
+ }
91
+ }
92
+ });
93
+ const request = makeRequest({ 'user-agent': 'cherrystudio/1.9.7' });
94
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATELESS);
95
+ });
96
+ test('matches stateful pattern', () => {
97
+ mockGetConfig.mockReturnValue({
98
+ system: {
99
+ session: {
100
+ sessionModeRules: { stateful: ['claude-code'], stateless: [] },
101
+ defaultSessionMode: SESSION_MODE_STATELESS
102
+ }
103
+ }
104
+ });
105
+ const request = makeRequest({ 'user-agent': 'claude-code/2.1.140 (cli)' });
106
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATEFUL);
107
+ });
108
+ test('stateless rules checked before stateful (stateless wins on conflict)', () => {
109
+ mockGetConfig.mockReturnValue({
110
+ system: {
111
+ session: {
112
+ sessionModeRules: { stateful: ['claude'], stateless: ['claude-code'] },
113
+ defaultSessionMode: SESSION_MODE_STATEFUL
114
+ }
115
+ }
116
+ });
117
+ // "claude-code" matches both "claude" and "claude-code", but stateless checked first
118
+ const request = makeRequest({ 'user-agent': 'claude-code/2.1.140' });
119
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATELESS);
120
+ });
121
+ });
122
+ describe('default fallback', () => {
123
+ test('no matching UA falls back to defaultSessionMode', () => {
124
+ mockGetConfig.mockReturnValue({
125
+ system: {
126
+ session: {
127
+ sessionModeRules: { stateful: ['claude-code'], stateless: ['cherrystudio'] },
128
+ defaultSessionMode: SESSION_MODE_STATEFUL
129
+ }
130
+ }
131
+ });
132
+ const request = makeRequest({ 'user-agent': 'SomeUnknownClient/1.0' });
133
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATEFUL);
134
+ });
135
+ test('empty UA falls back to defaultSessionMode', () => {
136
+ mockGetConfig.mockReturnValue({
137
+ system: {
138
+ session: {
139
+ sessionModeRules: { stateful: ['claude-code'], stateless: [] },
140
+ defaultSessionMode: SESSION_MODE_STATEFUL
141
+ }
142
+ }
143
+ });
144
+ const request = makeRequest({});
145
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATEFUL);
146
+ });
147
+ test('no gateway config falls back to stateful (hardcoded default)', () => {
148
+ mockGetConfig.mockReturnValue({
149
+ system: {}
150
+ });
151
+ const request = makeRequest({ 'user-agent': 'SomeClient/1.0' });
152
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATEFUL);
153
+ });
154
+ test('no config at all falls back to stateful', () => {
155
+ mockGetConfig.mockReturnValue(null);
156
+ const request = makeRequest({ 'user-agent': 'SomeClient/1.0' });
157
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATEFUL);
158
+ });
159
+ });
160
+ describe('empty rules arrays', () => {
161
+ test('empty stateful and stateless arrays use default', () => {
162
+ mockGetConfig.mockReturnValue({
163
+ system: {
164
+ session: {
165
+ sessionModeRules: { stateful: [], stateless: [] },
166
+ defaultSessionMode: SESSION_MODE_STATELESS
167
+ }
168
+ }
169
+ });
170
+ const request = makeRequest({ 'user-agent': 'SomeClient/1.0' });
171
+ expect(resolveSessionMode(request)).toBe(SESSION_MODE_STATELESS);
172
+ });
173
+ });
174
+ });
@@ -776,10 +776,10 @@ describe('HubToolsService', () => {
776
776
  const result = await hubToolsService.readResource('hub://use-guide');
777
777
  // Assert
778
778
  expect(typeof result).toBe('string');
779
- expect(result).toContain('# MCP Hub Lite Use Guide');
780
- expect(result).toContain('Getting Started');
781
- expect(result).toContain('Progressive Discovery Workflow');
782
- expect(result).toContain('System Tools Reference');
779
+ expect(result).toContain('# MCP Hub Lite');
780
+ expect(result).toContain('快速上手');
781
+ expect(result).toContain('渐进式发现工作流');
782
+ expect(result).toContain('系统工具参考');
783
783
  });
784
784
  it('should throw error for invalid URI format', async () => {
785
785
  // Act & Assert