@loop_ouroboros/mcp-hub-lite 1.2.9 → 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 (203) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +405 -331
  3. package/dist/client/assets/{HomeView-CGezWc0j.js → HomeView-DplI3V-h.js} +1 -1
  4. package/dist/client/assets/{ResourceDetailView-CDmWGdAK.css → ResourceDetailView-BkTSg91z.css} +1 -1
  5. package/dist/client/assets/ResourceDetailView-CeHPn99Y.js +1 -0
  6. package/dist/client/assets/ResourcesView-C1ObRhYS.js +1 -0
  7. package/dist/client/assets/ResourcesView-zgV8Nq7w.css +1 -0
  8. package/dist/client/assets/{ServerDashboard-g5p4VC_-.js → ServerDashboard-D7wG4Gvt.js} +1 -1
  9. package/dist/client/assets/{ServerDetail-DCQH8HIb.css → ServerDetail-CPNAFBPM.css} +1 -1
  10. package/dist/client/assets/ServerDetail-G23phOcJ.js +2 -0
  11. package/dist/client/assets/{ServerListView-DZsy2gaQ.js → ServerListView-BFiZLtPO.js} +1 -1
  12. package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-DmGg4uuV.js → ServerStatusTags.vue_vue_type_script_setup_true_lang-Deb_SbFw.js} +1 -1
  13. package/dist/client/assets/SettingsView-QBFLZ6fP.js +1 -0
  14. package/dist/client/assets/ToolCallDialog-BQ9UJZ_-.css +1 -0
  15. package/dist/client/assets/ToolCallDialog-DYS-ADCL.js +1 -0
  16. package/dist/client/assets/ToolsView-DYwgtm7W.js +1 -0
  17. package/dist/client/assets/ToolsView-cO61nMNr.css +1 -0
  18. package/dist/client/assets/_baseClone-DQno9YO3.js +1 -0
  19. package/dist/client/assets/{el-form-item-CTsVV8sm.js → el-form-item-DF0zzQdH.js} +2 -2
  20. package/dist/client/assets/el-input-C_p2Qw42.js +1 -0
  21. package/dist/client/assets/el-loading-BaenpNzU.js +1 -0
  22. package/dist/client/assets/el-overlay-MbIUXSQ7.js +1 -0
  23. package/dist/client/assets/el-radio-group-COnCjCcz.js +1 -0
  24. package/dist/client/assets/el-skeleton-item-qj0eQP4s.js +1 -0
  25. package/dist/client/assets/el-switch-BZbXqB3_.js +1 -0
  26. package/dist/client/assets/el-tab-pane-w7RltRLd.js +1 -0
  27. package/dist/client/assets/el-table-column-OD8zhFcD.js +1 -0
  28. package/dist/client/assets/index-DwhULJXZ.js +2 -0
  29. package/dist/client/assets/{index-BNmwPGMT.css → index-UtsV0Cvh.css} +1 -1
  30. package/dist/client/assets/{omit-Btci9mp3.js → omit-BAJQlviJ.js} +1 -1
  31. package/dist/client/assets/raf-B1Ry7ruA.js +1 -0
  32. package/dist/client/assets/{vue-vendor-Dwcr0jep.js → vue-vendor-ClSvefnQ.js} +1 -1
  33. package/dist/client/index.html +3 -3
  34. package/dist/server/shared/models/constants.d.ts +8 -0
  35. package/dist/server/shared/models/constants.d.ts.map +1 -0
  36. package/dist/server/shared/models/constants.js +6 -0
  37. package/dist/server/shared/models/index.d.ts +1 -0
  38. package/dist/server/shared/models/index.d.ts.map +1 -1
  39. package/dist/server/shared/models/index.js +1 -0
  40. package/dist/server/shared/models/server.model.d.ts +14 -0
  41. package/dist/server/shared/models/server.model.d.ts.map +1 -1
  42. package/dist/server/shared/models/server.model.js +27 -4
  43. package/dist/server/shared/types/index.d.ts +0 -1
  44. package/dist/server/shared/types/index.d.ts.map +1 -1
  45. package/dist/server/shared/types/index.js +0 -1
  46. package/dist/server/src/api/mcp/debug-response-wrapper.js +1 -1
  47. package/dist/server/src/api/mcp/gateway.d.ts +10 -6
  48. package/dist/server/src/api/mcp/gateway.d.ts.map +1 -1
  49. package/dist/server/src/api/mcp/gateway.js +235 -87
  50. package/dist/server/src/api/web/hub-tools.d.ts.map +1 -1
  51. package/dist/server/src/api/web/hub-tools.js +11 -0
  52. package/dist/server/src/api/web/mcp-status.js +2 -2
  53. package/dist/server/src/api/web/search.d.ts +2 -16
  54. package/dist/server/src/api/web/search.d.ts.map +1 -1
  55. package/dist/server/src/api/web/search.js +22 -30
  56. package/dist/server/src/api/web/servers.js +1 -1
  57. package/dist/server/src/api/web/sessions.d.ts +1 -27
  58. package/dist/server/src/api/web/sessions.d.ts.map +1 -1
  59. package/dist/server/src/api/web/sessions.js +8 -97
  60. package/dist/server/src/api/ws/events.js +1 -1
  61. package/dist/server/src/api/ws/ws-handler.js +1 -1
  62. package/dist/server/src/app.d.ts.map +1 -1
  63. package/dist/server/src/app.js +6 -1
  64. package/dist/server/src/cli/commands/status.js +39 -1
  65. package/dist/server/src/cli/commands/tool-use.d.ts +10 -3
  66. package/dist/server/src/cli/commands/tool-use.d.ts.map +1 -1
  67. package/dist/server/src/cli/commands/tool-use.js +69 -30
  68. package/dist/server/src/cli/commands/use-guide.d.ts +0 -8
  69. package/dist/server/src/cli/commands/use-guide.d.ts.map +1 -1
  70. package/dist/server/src/cli/commands/use-guide.js +28 -170
  71. package/dist/server/src/cli/server.d.ts +10 -0
  72. package/dist/server/src/cli/server.d.ts.map +1 -1
  73. package/dist/server/src/cli/server.js +31 -1
  74. package/dist/server/src/config/config-change-logger.js +1 -1
  75. package/dist/server/src/config/config-loader.js +1 -1
  76. package/dist/server/src/config/config-manager.js +1 -1
  77. package/dist/server/src/config/config-migrator.d.ts +4 -48
  78. package/dist/server/src/config/config-migrator.d.ts.map +1 -1
  79. package/dist/server/src/config/config-migrator.js +2 -103
  80. package/dist/server/src/config/config-saver.js +1 -1
  81. package/dist/server/src/config/server-config-manager.js +1 -1
  82. package/dist/server/src/models/system-tools.constants.d.ts +2 -1
  83. package/dist/server/src/models/system-tools.constants.d.ts.map +1 -1
  84. package/dist/server/src/models/system-tools.constants.js +2 -1
  85. package/dist/server/src/pid/manager.js +1 -1
  86. package/dist/server/src/server/dev-server.js +4 -2
  87. package/dist/server/src/server/runner.d.ts.map +1 -1
  88. package/dist/server/src/server/runner.js +4 -2
  89. package/dist/server/src/server/startup.js +2 -2
  90. package/dist/server/src/services/connection/connection-manager.d.ts +2 -0
  91. package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
  92. package/dist/server/src/services/connection/connection-manager.js +27 -25
  93. package/dist/server/src/services/connection/tool-cache.d.ts.map +1 -1
  94. package/dist/server/src/services/connection/tool-cache.js +10 -8
  95. package/dist/server/src/services/event-bus.service.d.ts +3 -1
  96. package/dist/server/src/services/event-bus.service.d.ts.map +1 -1
  97. package/dist/server/src/services/event-bus.service.js +1 -0
  98. package/dist/server/src/services/gateway/gateway.service.d.ts +14 -0
  99. package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
  100. package/dist/server/src/services/gateway/gateway.service.js +101 -7
  101. package/dist/server/src/services/gateway/global-transport.d.ts +20 -10
  102. package/dist/server/src/services/gateway/global-transport.d.ts.map +1 -1
  103. package/dist/server/src/services/gateway/global-transport.js +50 -34
  104. package/dist/server/src/services/gateway/request-handlers/call-tool-handler.d.ts +1 -2
  105. package/dist/server/src/services/gateway/request-handlers/call-tool-handler.d.ts.map +1 -1
  106. package/dist/server/src/services/gateway/request-handlers/call-tool-handler.js +24 -13
  107. package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
  108. package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +22 -6
  109. package/dist/server/src/services/gateway/request-handlers/resources-handler.d.ts.map +1 -1
  110. package/dist/server/src/services/gateway/request-handlers/resources-handler.js +12 -4
  111. package/dist/server/src/services/gateway/session-manager.d.ts +101 -0
  112. package/dist/server/src/services/gateway/session-manager.d.ts.map +1 -0
  113. package/dist/server/src/services/gateway/session-manager.js +256 -0
  114. package/dist/server/src/services/gateway/tool-list-generator.d.ts +14 -19
  115. package/dist/server/src/services/gateway/tool-list-generator.d.ts.map +1 -1
  116. package/dist/server/src/services/gateway/tool-list-generator.js +221 -80
  117. package/dist/server/src/services/hub-manager.service.d.ts.map +1 -1
  118. package/dist/server/src/services/hub-manager.service.js +15 -2
  119. package/dist/server/src/services/hub-tools/instance-selector.js +1 -1
  120. package/dist/server/src/services/hub-tools/resource-generator.d.ts +1 -22
  121. package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
  122. package/dist/server/src/services/hub-tools/resource-generator.js +24 -22
  123. package/dist/server/src/services/hub-tools/server-selector.js +1 -1
  124. package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
  125. package/dist/server/src/services/hub-tools.service.js +18 -13
  126. package/dist/server/src/services/log-storage.service.js +1 -1
  127. package/dist/server/src/services/system-tool-handler.js +1 -1
  128. package/dist/server/src/utils/error-handler.js +1 -1
  129. package/dist/server/src/utils/index.d.ts +1 -1
  130. package/dist/server/src/utils/index.d.ts.map +1 -1
  131. package/dist/server/src/utils/index.js +1 -1
  132. package/dist/server/src/utils/json-utils.d.ts +9 -0
  133. package/dist/server/src/utils/json-utils.d.ts.map +1 -1
  134. package/dist/server/src/utils/json-utils.js +23 -4
  135. package/dist/server/src/utils/log-rotator.d.ts +0 -15
  136. package/dist/server/src/utils/log-rotator.d.ts.map +1 -1
  137. package/dist/server/src/utils/log-rotator.js +0 -18
  138. package/dist/server/src/utils/logger/index.d.ts +1 -1
  139. package/dist/server/src/utils/logger/index.d.ts.map +1 -1
  140. package/dist/server/src/utils/logger/index.js +1 -1
  141. package/dist/server/src/utils/logger/log-context.d.ts +1 -0
  142. package/dist/server/src/utils/logger/log-context.d.ts.map +1 -1
  143. package/dist/server/src/utils/logger/log-formatter.d.ts.map +1 -1
  144. package/dist/server/src/utils/logger/log-formatter.js +25 -11
  145. package/dist/server/src/utils/logger/log-output.d.ts +17 -1
  146. package/dist/server/src/utils/logger/log-output.d.ts.map +1 -1
  147. package/dist/server/src/utils/logger/log-output.js +46 -40
  148. package/dist/server/src/utils/logger/logger.d.ts.map +1 -1
  149. package/dist/server/src/utils/logger/logger.js +18 -2
  150. package/dist/server/src/utils/port-checker.js +1 -1
  151. package/dist/server/src/utils/request-context.d.ts +8 -70
  152. package/dist/server/src/utils/request-context.d.ts.map +1 -1
  153. package/dist/server/src/utils/request-context.js +11 -70
  154. package/dist/server/src/utils/transports/stdio-transport.js +1 -1
  155. package/dist/server/src/utils/transports/streamable-http-transport.js +1 -1
  156. package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
  157. package/dist/server/src/utils/transports/transport-factory.js +26 -3
  158. package/dist/server/tests/contract/mcp-protocol/initialize.test.js +1 -1
  159. package/dist/server/tests/contract/mcp-protocol/tools-call.test.js +1 -1
  160. package/dist/server/tests/contract/mcp-protocol/tools-list.test.js +1 -1
  161. package/dist/server/tests/integration/gateway/fault-tolerance.test.js +1 -1
  162. package/dist/server/tests/integration/gateway/mcp-connection.test.js +1 -1
  163. package/dist/server/tests/types/logger-test-helpers.d.ts +1 -1
  164. package/dist/server/tests/types/logger-test-helpers.d.ts.map +1 -1
  165. package/dist/server/tests/unit/config/config-migrator.test.js +45 -105
  166. package/dist/server/tests/unit/config/config-saver.test.js +1 -1
  167. package/dist/server/tests/unit/config/config.schema.test.js +2 -1
  168. package/dist/server/tests/unit/server/runner.test.js +19 -13
  169. package/dist/server/tests/unit/services/gateway-logging.test.js +1 -1
  170. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts +2 -0
  171. package/dist/server/tests/unit/services/gateway-session-mode.test.d.ts.map +1 -0
  172. package/dist/server/tests/unit/services/gateway-session-mode.test.js +174 -0
  173. package/dist/server/tests/unit/services/hub-manager-service.test.js +4 -5
  174. package/dist/server/tests/unit/services/hub-tools.service.test.js +82 -6
  175. package/dist/server/tests/unit/utils/config.test.js +14 -7
  176. package/dist/server/tests/unit/utils/log-output.test.d.ts +2 -0
  177. package/dist/server/tests/unit/utils/log-output.test.d.ts.map +1 -0
  178. package/dist/server/tests/unit/utils/log-output.test.js +198 -0
  179. package/dist/server/tests/unit/utils/log-rotator.test.js +1 -15
  180. package/dist/server/tests/unit/utils/logger.test.js +1 -1
  181. package/dist/server/vitest.config.d.ts.map +1 -1
  182. package/dist/server/vitest.config.js +0 -2
  183. package/package.json +1 -3
  184. package/dist/client/assets/ResourceDetailView-Bi5UsbFq.js +0 -1
  185. package/dist/client/assets/ResourcesView-B9anSm85.js +0 -1
  186. package/dist/client/assets/ResourcesView-Cc8RHtia.css +0 -1
  187. package/dist/client/assets/ServerDetail-DMoFqWCp.js +0 -2
  188. package/dist/client/assets/SettingsView-DQSWb9xM.js +0 -1
  189. package/dist/client/assets/ToolCallDialog-BEyRp_J3.js +0 -1
  190. package/dist/client/assets/ToolCallDialog-BhdPX-Kf.css +0 -1
  191. package/dist/client/assets/ToolsView-BU7PKJwt.js +0 -1
  192. package/dist/client/assets/ToolsView-BkrQLjH9.css +0 -1
  193. package/dist/client/assets/_baseClone-DsVtZfPm.js +0 -1
  194. package/dist/client/assets/el-input-Bh1VGJTU.js +0 -1
  195. package/dist/client/assets/el-loading-huOeK9cW.js +0 -1
  196. package/dist/client/assets/el-overlay-CR_KVhLU.js +0 -1
  197. package/dist/client/assets/el-radio-group-BSbtAW4k.js +0 -1
  198. package/dist/client/assets/el-skeleton-item-BSxOLPFM.js +0 -1
  199. package/dist/client/assets/el-switch-BaQUQWTL.js +0 -1
  200. package/dist/client/assets/el-tab-pane-9JxLgdS7.js +0 -1
  201. package/dist/client/assets/el-table-column-Du1l9Ww3.js +0 -1
  202. package/dist/client/assets/index-CsZoFRv1.js +0 -2
  203. package/dist/client/assets/raf-tUu4BwZS.js +0 -1
@@ -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
+ });
@@ -1,11 +1,10 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
2
  import { HubManagerService } from '../../../src/services/hub-manager.service.js';
3
- import { mcpConnectionManager } from '../../../src/services/mcp-connection-manager.js';
3
+ import { mcpConnectionManager } from '../../../src/services/connection/index.js';
4
4
  import { configManager } from '../../../src/config/config-manager.js';
5
5
  // Mock resolveInstanceConfig to return a valid resolved config
6
6
  vi.mock('@config/config-migrator.js', () => ({
7
- resolveInstanceConfig: vi.fn(),
8
- getEnabledInstances: vi.fn()
7
+ resolveInstanceConfig: vi.fn()
9
8
  }));
10
9
  // Mock dependencies
11
10
  vi.mock('@config/config-manager.js', () => ({
@@ -23,13 +22,13 @@ vi.mock('@config/config-manager.js', () => ({
23
22
  removeServerInstance: vi.fn()
24
23
  }
25
24
  }));
26
- vi.mock('@services/mcp-connection-manager.js', () => ({
25
+ vi.mock('@services/connection/index.js', () => ({
27
26
  mcpConnectionManager: {
28
27
  connect: vi.fn(),
29
28
  disconnect: vi.fn(() => Promise.resolve())
30
29
  }
31
30
  }));
32
- vi.mock('@utils/logger.js', () => ({
31
+ vi.mock('@utils/logger/index.js', () => ({
33
32
  logger: {
34
33
  info: vi.fn(),
35
34
  error: vi.fn(),
@@ -1,10 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HubToolsService } from '../../../src/services/hub-tools.service.js';
3
3
  import { hubManager } from '../../../src/services/hub-manager.service.js';
4
- import { mcpConnectionManager } from '../../../src/services/mcp-connection-manager.js';
4
+ import { mcpConnectionManager } from '../../../src/services/connection/index.js';
5
5
  // Mock dependencies
6
6
  vi.mock('@services/hub-manager.service.js');
7
- vi.mock('@services/mcp-connection-manager.js');
7
+ vi.mock('@services/connection/index.js');
8
8
  describe('HubToolsService', () => {
9
9
  let hubToolsService;
10
10
  beforeEach(() => {
@@ -320,6 +320,82 @@ describe('HubToolsService', () => {
320
320
  });
321
321
  expect(servers).not.toHaveProperty('Disconnected Server');
322
322
  });
323
+ it('should include tag-match-unique servers with multiple connected instances', async () => {
324
+ // Arrange: 4-instance server with tag-match-unique strategy, all connected
325
+ const mockServers = [
326
+ {
327
+ name: 'multi-instance-server',
328
+ config: {
329
+ template: {
330
+ type: 'stdio',
331
+ command: 'test-command',
332
+ args: [],
333
+ env: {},
334
+ headers: {},
335
+ aggregatedTools: [],
336
+ timeout: 30000,
337
+ description: 'Multi-instance test server',
338
+ tags: {}
339
+ },
340
+ instances: [
341
+ {
342
+ id: 'inst-0',
343
+ index: 0,
344
+ enabled: false,
345
+ args: [],
346
+ env: {},
347
+ headers: {},
348
+ tags: { Env: 'dev' }
349
+ },
350
+ {
351
+ id: 'inst-1',
352
+ index: 1,
353
+ enabled: false,
354
+ args: [],
355
+ env: {},
356
+ headers: {},
357
+ tags: { Env: 'test' }
358
+ },
359
+ {
360
+ id: 'inst-2',
361
+ index: 2,
362
+ enabled: false,
363
+ args: [],
364
+ env: {},
365
+ headers: {},
366
+ tags: { Env: 'prod' }
367
+ },
368
+ {
369
+ id: 'inst-3',
370
+ index: 3,
371
+ enabled: false,
372
+ args: [],
373
+ env: {},
374
+ headers: {},
375
+ tags: { Env: 'staging' }
376
+ }
377
+ ],
378
+ tagDefinitions: [],
379
+ instanceSelectionStrategy: 'tag-match-unique'
380
+ }
381
+ }
382
+ ];
383
+ vi.mocked(hubManager.getAllServers).mockReturnValue(mockServers);
384
+ vi.mocked(hubManager.getServerInstancesByName).mockReturnValue(mockServers[0].config.instances);
385
+ vi.mocked(hubManager.getServerByName).mockReturnValue(mockServers[0].config);
386
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0, 1, 2, 3]);
387
+ vi.mocked(mcpConnectionManager.getStatus).mockReturnValue({
388
+ connected: true,
389
+ lastCheck: Date.now(),
390
+ toolsCount: 1,
391
+ resourcesCount: 0
392
+ });
393
+ // Act
394
+ const servers = await hubToolsService.listServers();
395
+ // Assert: tag-match-unique server with multiple connected instances must be included
396
+ expect(servers).toHaveProperty('multi-instance-server');
397
+ expect(servers['multi-instance-server']).toBe('Multi-instance test server');
398
+ });
323
399
  });
324
400
  describe('listToolsInServer', () => {
325
401
  it('should return tool summaries from a specific server (without inputSchema)', async () => {
@@ -700,10 +776,10 @@ describe('HubToolsService', () => {
700
776
  const result = await hubToolsService.readResource('hub://use-guide');
701
777
  // Assert
702
778
  expect(typeof result).toBe('string');
703
- expect(result).toContain('# MCP Hub Lite Use Guide');
704
- expect(result).toContain('Getting Started');
705
- expect(result).toContain('Progressive Discovery Workflow');
706
- 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('系统工具参考');
707
783
  });
708
784
  it('should throw error for invalid URI format', async () => {
709
785
  // Act & Assert
@@ -81,7 +81,8 @@ describe('ConfigManager', () => {
81
81
  jsonPretty: true,
82
82
  mcpCommDebug: false,
83
83
  apiDebug: false,
84
- gatewayDebug: false
84
+ gatewayDebug: false,
85
+ showTraceContext: true
85
86
  }
86
87
  },
87
88
  security: {
@@ -151,7 +152,8 @@ describe('ConfigManager', () => {
151
152
  jsonPretty: true,
152
153
  mcpCommDebug: false,
153
154
  apiDebug: false,
154
- gatewayDebug: false
155
+ gatewayDebug: false,
156
+ showTraceContext: true
155
157
  }
156
158
  }
157
159
  });
@@ -196,7 +198,8 @@ describe('ConfigManager', () => {
196
198
  jsonPretty: true,
197
199
  mcpCommDebug: false,
198
200
  apiDebug: false,
199
- gatewayDebug: false
201
+ gatewayDebug: false,
202
+ showTraceContext: true
200
203
  }
201
204
  }
202
205
  });
@@ -239,7 +242,8 @@ describe('ConfigManager', () => {
239
242
  jsonPretty: true,
240
243
  mcpCommDebug: false,
241
244
  apiDebug: false,
242
- gatewayDebug: false
245
+ gatewayDebug: false,
246
+ showTraceContext: true
243
247
  }
244
248
  }
245
249
  };
@@ -409,7 +413,8 @@ describe('ConfigManager', () => {
409
413
  jsonPretty: true,
410
414
  mcpCommDebug: false,
411
415
  apiDebug: false,
412
- gatewayDebug: false
416
+ gatewayDebug: false,
417
+ showTraceContext: true
413
418
  }
414
419
  },
415
420
  security: {
@@ -463,7 +468,8 @@ describe('ConfigManager', () => {
463
468
  jsonPretty: true,
464
469
  mcpCommDebug: false,
465
470
  apiDebug: false,
466
- gatewayDebug: false
471
+ gatewayDebug: false,
472
+ showTraceContext: true
467
473
  }
468
474
  }
469
475
  });
@@ -481,7 +487,8 @@ describe('ConfigManager', () => {
481
487
  jsonPretty: true,
482
488
  mcpCommDebug: false,
483
489
  apiDebug: false,
484
- gatewayDebug: false
490
+ gatewayDebug: false,
491
+ showTraceContext: true
485
492
  }
486
493
  },
487
494
  security: {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=log-output.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-output.test.d.ts","sourceRoot":"","sources":["../../../../../tests/unit/utils/log-output.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,198 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { hasDataUriImage, simplifyDataUriImages, simplifyImageContent, isToolsListResponse, formatMcpMessageForLogging } from '../../../src/utils/logger/log-output.js';
3
+ import { setJsonPrettyConfigGetter, setDevModeEnabled } from '../../../src/utils/json-utils.js';
4
+ describe('hasDataUriImage', () => {
5
+ it('should detect data:image/png;base64 in JSON', () => {
6
+ const json = JSON.stringify({
7
+ icons: [{ src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==' }]
8
+ });
9
+ expect(hasDataUriImage(json)).toBe(true);
10
+ });
11
+ it('should detect data:image/jpeg;base64', () => {
12
+ const json = JSON.stringify({
13
+ icon: 'data:image/jpeg;base64,/9j/4AAQSkZJRg=='
14
+ });
15
+ expect(hasDataUriImage(json)).toBe(true);
16
+ });
17
+ it('should detect data:image/svg+xml;base64', () => {
18
+ const json = JSON.stringify({
19
+ icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0i'
20
+ });
21
+ expect(hasDataUriImage(json)).toBe(true);
22
+ });
23
+ it('should return false for JSON without data:image', () => {
24
+ const json = JSON.stringify({ name: 'test', value: 123 });
25
+ expect(hasDataUriImage(json)).toBe(false);
26
+ });
27
+ it('should return false for non-base64 data URIs', () => {
28
+ const json = JSON.stringify({ icon: 'https://example.com/icon.png' });
29
+ expect(hasDataUriImage(json)).toBe(false);
30
+ });
31
+ });
32
+ describe('simplifyDataUriImages', () => {
33
+ it('should replace base64 payload with [Truncated]', () => {
34
+ const input = JSON.stringify({
35
+ icons: [{ src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==' }]
36
+ });
37
+ const result = simplifyDataUriImages(input);
38
+ expect(result).toContain('data:image/png;base64,[Truncated]');
39
+ expect(result).not.toContain('iVBORw0KGgoAAAANSUhEUg==');
40
+ });
41
+ it('should handle multiple data URIs in one JSON', () => {
42
+ const input = JSON.stringify({
43
+ icons: [{ src: 'data:image/png;base64,AAAA' }, { src: 'data:image/png;base64,BBBB' }]
44
+ });
45
+ const result = simplifyDataUriImages(input);
46
+ expect(result).toContain('data:image/png;base64,[Truncated]');
47
+ expect(result).not.toContain('AAAA');
48
+ expect(result).not.toContain('BBBB');
49
+ });
50
+ it('should leave non-data-URI strings unchanged', () => {
51
+ const input = JSON.stringify({ name: 'test', url: 'https://example.com/img.png' });
52
+ const result = simplifyDataUriImages(input);
53
+ expect(result).toBe(input);
54
+ });
55
+ it('should produce valid JSON after simplification', () => {
56
+ const input = JSON.stringify({
57
+ icons: [{ src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==' }]
58
+ });
59
+ const result = simplifyDataUriImages(input);
60
+ expect(() => JSON.parse(result)).not.toThrow();
61
+ });
62
+ });
63
+ describe('simplifyImageContent', () => {
64
+ it('should replace MCP image content data with [Truncated]', () => {
65
+ const message = {
66
+ jsonrpc: '2.0',
67
+ id: 1,
68
+ result: {
69
+ content: [{ type: 'image', data: 'base64payload', mimeType: 'image/png' }]
70
+ }
71
+ };
72
+ const json = JSON.stringify(message);
73
+ const result = simplifyImageContent(json);
74
+ expect(result).toContain('"[Truncated]"');
75
+ expect(result).not.toContain('base64payload');
76
+ });
77
+ });
78
+ describe('isToolsListResponse', () => {
79
+ it('should return true for tools/list response', () => {
80
+ const json = JSON.stringify({
81
+ jsonrpc: '2.0',
82
+ id: 1,
83
+ result: { tools: [{ name: 'tool1' }] }
84
+ });
85
+ expect(isToolsListResponse(json)).toBe(true);
86
+ });
87
+ it('should return true for resources/list response', () => {
88
+ const json = JSON.stringify({
89
+ jsonrpc: '2.0',
90
+ id: 1,
91
+ result: { resources: [{ uri: 'res1' }] }
92
+ });
93
+ expect(isToolsListResponse(json)).toBe(true);
94
+ });
95
+ it('should return false for initialize response with capabilities (not result.tools)', () => {
96
+ const json = JSON.stringify({
97
+ jsonrpc: '2.0',
98
+ id: 0,
99
+ result: {
100
+ capabilities: { tools: {}, resources: {} },
101
+ serverInfo: { name: 'test', version: '1.0' }
102
+ }
103
+ });
104
+ expect(isToolsListResponse(json)).toBe(false);
105
+ });
106
+ });
107
+ describe('formatMcpMessageForLogging', () => {
108
+ beforeEach(() => {
109
+ setJsonPrettyConfigGetter(null);
110
+ setDevModeEnabled(false);
111
+ });
112
+ it('should truncate data:image URIs in initialize response with icons', () => {
113
+ const message = {
114
+ jsonrpc: '2.0',
115
+ id: 0,
116
+ result: {
117
+ capabilities: {
118
+ tools: {},
119
+ resources: {}
120
+ },
121
+ protocolVersion: '2025-11-25',
122
+ serverInfo: {
123
+ name: 'github-mcp-server',
124
+ title: 'GitHub MCP Server',
125
+ version: 'github-mcp-server/remote-abc123',
126
+ icons: [
127
+ {
128
+ src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAADK0lEQVR',
129
+ mimeType: 'image/png',
130
+ theme: 'light'
131
+ },
132
+ {
133
+ src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAB8ElEQVR',
134
+ mimeType: 'image/png',
135
+ theme: 'dark'
136
+ }
137
+ ]
138
+ }
139
+ }
140
+ };
141
+ const result = formatMcpMessageForLogging(message);
142
+ // Must contain [Truncated] placeholder
143
+ expect(result).toContain('[Truncated]');
144
+ // Must NOT contain the raw base64 payload
145
+ expect(result).not.toContain('iVBORw0KGgoAAAANSUhEUg');
146
+ // Must still contain server info
147
+ expect(result).toContain('github-mcp-server');
148
+ });
149
+ it('should handle initialize response without icons normally', () => {
150
+ const message = {
151
+ jsonrpc: '2.0',
152
+ id: 0,
153
+ result: {
154
+ capabilities: { tools: {} },
155
+ serverInfo: { name: 'test-server', version: '1.0' }
156
+ }
157
+ };
158
+ const result = formatMcpMessageForLogging(message);
159
+ // Should be valid JSON (since isToolsListResponse returns null for empty capabilities,
160
+ // but now hasDataUriImage should be checked first and return false, then
161
+ // isToolsListResponse returns true and returns null, falling to stringifyForLogging)
162
+ expect(result).toContain('test-server');
163
+ });
164
+ it('should simplify tools/list response to count', () => {
165
+ const message = {
166
+ jsonrpc: '2.0',
167
+ id: 1,
168
+ result: { tools: [{ name: 't1' }, { name: 't2' }, { name: 't3' }] }
169
+ };
170
+ const result = formatMcpMessageForLogging(message);
171
+ expect(result).toBe('Returned 3 tools');
172
+ });
173
+ it('should simplify resources/list response to count', () => {
174
+ const message = {
175
+ jsonrpc: '2.0',
176
+ id: 1,
177
+ result: { resources: [{ uri: 'r1' }, { uri: 'r2' }] }
178
+ };
179
+ const result = formatMcpMessageForLogging(message);
180
+ expect(result).toBe('Returned 2 resources');
181
+ });
182
+ it('should not simplify capabilities response (output full JSON)', () => {
183
+ const message = {
184
+ jsonrpc: '2.0',
185
+ id: 1,
186
+ result: {
187
+ capabilities: {
188
+ tools: { list: {}, call: {} },
189
+ resources: { list: {} }
190
+ }
191
+ }
192
+ };
193
+ const result = formatMcpMessageForLogging(message);
194
+ // Capabilities are no longer simplified; expect full JSON output
195
+ expect(result).toContain('capabilities');
196
+ expect(result).toContain('list');
197
+ });
198
+ });
@@ -79,21 +79,7 @@ describe('LogRotator', () => {
79
79
  const latest = logRotator.getLatestLogFilePath();
80
80
  expect(latest).toBeNull();
81
81
  });
82
- it('should maintain backward compatibility with getCurrentLogFilePath', () => {
83
- logRotator = new LogRotator(tempLogDir, 'mcp-hub', undefined, () => originalConfig);
84
- // When no files exist, getCurrentLogFilePath will create a new path (but not the file)
85
- const path1 = logRotator.getCurrentLogFilePath();
86
- expect(path.basename(path1)).toMatch(/^mcp-hub\.\d{8}_\d{9}\.log$/);
87
- // Actually create the file on disk
88
- fs.writeFileSync(path1, 'test content 1');
89
- // Create an older file
90
- const oldFile = path.join(tempLogDir, 'mcp-hub.20260301_100000000.log');
91
- fs.writeFileSync(oldFile, 'test content 2');
92
- // When files exist, getCurrentLogFilePath should return the latest one
93
- const path2 = logRotator.getCurrentLogFilePath();
94
- expect(path2).toBe(path1); // Should return the newer one we just created
95
- });
96
- it('should get current log file path with custom base name', () => {
82
+ it('should create new log file path with custom base name', () => {
97
83
  logRotator = new LogRotator(tempLogDir, 'custom-log', undefined, () => originalConfig);
98
84
  const currentPath = logRotator.createNewLogFilePath();
99
85
  const basename = path.basename(currentPath);
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
- import { Logger } from '../../../src/utils/logger.js';
5
+ import { Logger } from '../../../src/utils/logger/index.js';
6
6
  import { setJsonPrettyConfigGetter, setDevModeEnabled } from '../../../src/utils/json-utils.js';
7
7
  import { createColoredLogMessage, createLogMessage, getCallerInfo, formatCallerInfo } from '../../../src/utils/logger/log-formatter.js';
8
8
  describe('Logger', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../../vitest.config.ts"],"names":[],"mappings":";AAUA,wBA8CG"}
1
+ {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../../vitest.config.ts"],"names":[],"mappings":";AAUA,wBA4CG"}
@@ -44,8 +44,6 @@ export default defineConfig({
44
44
  'tests/unit/**/*.test.ts',
45
45
  'tests/integration/**/*.test.ts',
46
46
  'tests/contract/**/*.test.ts',
47
- 'tests/evaluation/**/*.test.ts',
48
- 'src/**/*.test.ts',
49
47
  'tests/temp/**/*.test.ts'
50
48
  ],
51
49
  exclude: ['tests/unit/frontend/**/*']
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loop_ouroboros/mcp-hub-lite",
3
- "version": "1.2.9",
3
+ "version": "1.3.1",
4
4
  "description": "A lightweight MCP management platform designed for independent developers",
5
5
  "license": "MIT",
6
6
  "author": "loop_ouroboros",
@@ -68,7 +68,6 @@
68
68
  "element-plus": "^2.13.0",
69
69
  "eventsource": "^4.1.0",
70
70
  "fastify": "^5.6.2",
71
- "open": "^11.0.0",
72
71
  "pinia": "^3.0.4",
73
72
  "undici": "^8.1.0",
74
73
  "vue": "^3.5.26",
@@ -96,7 +95,6 @@
96
95
  "npm-run-all": "^4.1.5",
97
96
  "postcss": "^8.5.6",
98
97
  "prettier": "^3.7.4",
99
- "rimraf": "^6.1.2",
100
98
  "tailwindcss": "^3.4.19",
101
99
  "tsc-alias": "^1.8.16",
102
100
  "tsx": "^4.21.0",