@loop_ouroboros/mcp-hub-lite 1.2.5 → 1.2.7
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.
- package/CHANGELOG.md +659 -626
- package/dist/client/assets/HomeView-CgEri1kD.js +1 -0
- package/dist/client/assets/{ResourceDetailView-BGBtmsyc.js → ResourceDetailView-B8Qo1_jK.js} +1 -1
- package/dist/client/assets/ResourcesView-B12FzUdo.js +1 -0
- package/dist/client/assets/ServerDashboard-B3O-crvv.js +1 -0
- package/dist/client/assets/ServerDetail-Bz5_9yOY.js +2 -0
- package/dist/client/assets/{ServerDetail-CtnNKJGx.css → ServerDetail-CXg8rI3q.css} +1 -1
- package/dist/client/assets/{ServerListView-C7kcd4GC.js → ServerListView-SlZN8ppC.js} +2 -2
- package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-BHhwEuGe.js → ServerStatusTags.vue_vue_type_script_setup_true_lang-DmGg4uuV.js} +1 -1
- package/dist/client/assets/SettingsView-D8fiOG0O.js +1 -0
- package/dist/client/assets/ToolCallDialog-DYEdhnCk.js +1 -0
- package/dist/client/assets/ToolsView-BreAl-yn.js +1 -0
- package/dist/client/assets/{_baseClone-kbJbcBJT.js → _baseClone-BYxCbA_9.js} +1 -1
- package/dist/client/assets/{el-form-item-iQ0G8e97.js → el-form-item-ySymCPMr.js} +2 -2
- package/dist/client/assets/el-input-C85p6Nqj.js +1 -0
- package/dist/client/assets/el-loading-DIjKEx81.js +1 -0
- package/dist/client/assets/{el-overlay-Cy5xg31y.js → el-overlay-B_CxiSem.js} +1 -1
- package/dist/client/assets/el-radio-group-BjkTCPRf.js +1 -0
- package/dist/client/assets/el-skeleton-item-CupTKK6n.js +1 -0
- package/dist/client/assets/{el-switch-KpjV93lm.js → el-switch-BosIJ9jf.js} +1 -1
- package/dist/client/assets/el-tab-pane-BydxdJoc.js +1 -0
- package/dist/client/assets/{el-table-column-fofd_2n-.js → el-table-column-DV5TZOUW.js} +1 -1
- package/dist/client/assets/index-kC4mf0Vo.js +2 -0
- package/dist/client/assets/{index-DpH6ZSbs.css → index-xJkq2euk.css} +1 -1
- package/dist/client/assets/omit-DxDGRttI.js +1 -0
- package/dist/client/assets/{raf-MWAHt9ca.js → raf-Y9AoxecD.js} +1 -1
- package/dist/client/assets/{vue-vendor-CbgVSHIh.js → vue-vendor-Dwcr0jep.js} +1 -1
- package/dist/client/index.html +3 -3
- package/dist/server/shared/types/websocket.types.d.ts +28 -19
- package/dist/server/shared/types/websocket.types.d.ts.map +1 -1
- package/dist/server/shared/types/websocket.types.js +3 -2
- package/dist/server/src/api/ws/ws-handler.d.ts.map +1 -1
- package/dist/server/src/api/ws/ws-handler.js +4 -3
- package/dist/server/src/app.d.ts.map +1 -1
- package/dist/server/src/app.js +46 -0
- package/dist/server/src/models/event.model.d.ts +0 -13
- package/dist/server/src/models/event.model.d.ts.map +1 -1
- package/dist/server/src/models/server.model.d.ts +0 -29
- package/dist/server/src/models/server.model.d.ts.map +1 -1
- package/dist/server/src/models/types.d.ts +1 -70
- package/dist/server/src/models/types.d.ts.map +1 -1
- package/dist/server/src/models/types.js +1 -67
- package/dist/server/src/server/dev-server.js +24 -6
- package/dist/server/src/services/connection/connection-manager.d.ts +19 -0
- package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
- package/dist/server/src/services/connection/connection-manager.js +145 -5
- package/dist/server/src/services/event-bus.service.d.ts +0 -4
- package/dist/server/src/services/event-bus.service.d.ts.map +1 -1
- package/dist/server/src/services/event-bus.service.js +1 -6
- package/dist/server/src/services/gateway/request-handlers/initialize-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/initialize-handler.js +1 -0
- package/dist/server/src/services/hub-tools/instance-selector.d.ts +8 -1
- package/dist/server/src/services/hub-tools/instance-selector.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/instance-selector.js +24 -10
- package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.js +4 -19
- package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools.service.js +22 -5
- package/dist/server/src/services/mcp-oauth/index.d.ts +5 -0
- package/dist/server/src/services/mcp-oauth/index.d.ts.map +1 -0
- package/dist/server/src/services/mcp-oauth/index.js +3 -0
- package/dist/server/src/services/mcp-oauth/oauth-callback-server.d.ts +19 -0
- package/dist/server/src/services/mcp-oauth/oauth-callback-server.d.ts.map +1 -0
- package/dist/server/src/services/mcp-oauth/oauth-callback-server.js +100 -0
- package/dist/server/src/services/mcp-oauth/oauth-provider.d.ts +42 -0
- package/dist/server/src/services/mcp-oauth/oauth-provider.d.ts.map +1 -0
- package/dist/server/src/services/mcp-oauth/oauth-provider.js +121 -0
- package/dist/server/src/services/mcp-oauth/oauth-token-storage.d.ts +23 -0
- package/dist/server/src/services/mcp-oauth/oauth-token-storage.d.ts.map +1 -0
- package/dist/server/src/services/mcp-oauth/oauth-token-storage.js +92 -0
- package/dist/server/src/services/mcp-oauth/oauth-types.d.ts +21 -0
- package/dist/server/src/services/mcp-oauth/oauth-types.d.ts.map +1 -0
- package/dist/server/src/services/mcp-oauth/oauth-types.js +4 -0
- package/dist/server/src/utils/network-security.d.ts +33 -0
- package/dist/server/src/utils/network-security.d.ts.map +1 -0
- package/dist/server/src/utils/network-security.js +83 -0
- package/dist/server/src/utils/transports/streamable-http-transport.d.ts +12 -1
- package/dist/server/src/utils/transports/streamable-http-transport.d.ts.map +1 -1
- package/dist/server/src/utils/transports/streamable-http-transport.js +20 -1
- package/dist/server/src/utils/transports/transport-factory.d.ts +2 -0
- package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
- package/dist/server/src/utils/transports/transport-factory.js +17 -2
- package/dist/server/src/utils/transports/transport.interface.d.ts +2 -0
- package/dist/server/src/utils/transports/transport.interface.d.ts.map +1 -1
- package/dist/server/tests/unit/services/hub-tools/instance-selector.test.js +21 -16
- package/dist/server/tests/unit/services/hub-tools.service.test.js +36 -35
- package/dist/server/tests/unit/utils/network-security.test.d.ts +2 -0
- package/dist/server/tests/unit/utils/network-security.test.d.ts.map +1 -0
- package/dist/server/tests/unit/utils/network-security.test.js +123 -0
- package/dist/server/vite.config.js +1 -1
- package/package.json +111 -111
- package/dist/client/assets/HomeView-BnO4yIT9.js +0 -1
- package/dist/client/assets/ResourcesView-B5Xg0ynh.js +0 -1
- package/dist/client/assets/ServerDashboard-DYAVrrUk.js +0 -1
- package/dist/client/assets/ServerDetail-q94ZFfjL.js +0 -2
- package/dist/client/assets/SettingsView-BM6P5yrT.js +0 -1
- package/dist/client/assets/ToolCallDialog-BoAGxlB5.js +0 -1
- package/dist/client/assets/ToolsView-lqFhr7Bk.js +0 -1
- package/dist/client/assets/el-input-DkJq57wP.js +0 -1
- package/dist/client/assets/el-loading-C3v6a9xV.js +0 -1
- package/dist/client/assets/el-radio-group-C9QUL5mm.js +0 -1
- package/dist/client/assets/el-skeleton-item-Bbmpc0Xz.js +0 -1
- package/dist/client/assets/el-tab-pane-YsYuBcem.js +0 -1
- package/dist/client/assets/index-5tzIwwtS.js +0 -1
- package/dist/client/assets/index-MqHvQjDP.js +0 -2
- package/dist/client/assets/omit-CB4hTeTH.js +0 -1
- package/dist/client/assets/typescript-Bp3YSIOJ.js +0 -1
|
@@ -17,8 +17,13 @@ describe('InstanceSelector', () => {
|
|
|
17
17
|
headers: {},
|
|
18
18
|
tags: {}
|
|
19
19
|
};
|
|
20
|
+
// Mock statusChecker that simulates connected instances
|
|
21
|
+
const mockConnectedStatus = () => ({ connected: true });
|
|
22
|
+
const mockDisconnectedStatus = () => ({ connected: false });
|
|
20
23
|
describe('random strategy', () => {
|
|
21
|
-
it('should select random instance from
|
|
24
|
+
it('should select random instance from connected instances', () => {
|
|
25
|
+
// Local mock that simulates idx=2 being disconnected
|
|
26
|
+
const localMockConnectedStatus = (_name, idx) => idx === 2 ? { connected: false } : { connected: true };
|
|
22
27
|
const config = {
|
|
23
28
|
template: {
|
|
24
29
|
...baseTemplate,
|
|
@@ -31,11 +36,11 @@ describe('InstanceSelector', () => {
|
|
|
31
36
|
],
|
|
32
37
|
tagDefinitions: []
|
|
33
38
|
};
|
|
34
|
-
const selected = InstanceSelector.selectInstance('test-server', config);
|
|
39
|
+
const selected = InstanceSelector.selectInstance('test-server', config, undefined, localMockConnectedStatus);
|
|
35
40
|
expect(selected).toBeDefined();
|
|
36
41
|
expect(['1', '2']).toContain(selected.id);
|
|
37
42
|
});
|
|
38
|
-
it('should return undefined when no
|
|
43
|
+
it('should return undefined when no connected instances', () => {
|
|
39
44
|
const config = {
|
|
40
45
|
template: {
|
|
41
46
|
...baseTemplate,
|
|
@@ -47,12 +52,12 @@ describe('InstanceSelector', () => {
|
|
|
47
52
|
],
|
|
48
53
|
tagDefinitions: []
|
|
49
54
|
};
|
|
50
|
-
const selected = InstanceSelector.selectInstance('test-server', config);
|
|
55
|
+
const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockDisconnectedStatus);
|
|
51
56
|
expect(selected).toBeUndefined();
|
|
52
57
|
});
|
|
53
58
|
});
|
|
54
59
|
describe('round-robin strategy', () => {
|
|
55
|
-
it('should cycle through
|
|
60
|
+
it('should cycle through connected instances', () => {
|
|
56
61
|
const config = {
|
|
57
62
|
template: {
|
|
58
63
|
...baseTemplate,
|
|
@@ -65,10 +70,10 @@ describe('InstanceSelector', () => {
|
|
|
65
70
|
],
|
|
66
71
|
tagDefinitions: []
|
|
67
72
|
};
|
|
68
|
-
const selected1 = InstanceSelector.selectInstance('test-server-rr', config);
|
|
69
|
-
const selected2 = InstanceSelector.selectInstance('test-server-rr', config);
|
|
70
|
-
const selected3 = InstanceSelector.selectInstance('test-server-rr', config);
|
|
71
|
-
const selected4 = InstanceSelector.selectInstance('test-server-rr', config);
|
|
73
|
+
const selected1 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
|
|
74
|
+
const selected2 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
|
|
75
|
+
const selected3 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
|
|
76
|
+
const selected4 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
|
|
72
77
|
expect(selected1.id).toBe('1');
|
|
73
78
|
expect(selected2.id).toBe('2');
|
|
74
79
|
expect(selected3.id).toBe('3');
|
|
@@ -91,7 +96,7 @@ describe('InstanceSelector', () => {
|
|
|
91
96
|
};
|
|
92
97
|
const selected = InstanceSelector.selectInstance('test-server', config, {
|
|
93
98
|
tags: { env: 'prod', region: 'us' }
|
|
94
|
-
});
|
|
99
|
+
}, mockConnectedStatus);
|
|
95
100
|
expect(selected).toBeDefined();
|
|
96
101
|
expect(selected.id).toBe('2');
|
|
97
102
|
});
|
|
@@ -110,7 +115,7 @@ describe('InstanceSelector', () => {
|
|
|
110
115
|
expect(() => {
|
|
111
116
|
InstanceSelector.selectInstance('test-server', config, {
|
|
112
117
|
tags: { env: 'staging' }
|
|
113
|
-
});
|
|
118
|
+
}, mockConnectedStatus);
|
|
114
119
|
}).toThrow('No instance found matching tags');
|
|
115
120
|
});
|
|
116
121
|
it('should throw error when multiple instances match tags', () => {
|
|
@@ -128,7 +133,7 @@ describe('InstanceSelector', () => {
|
|
|
128
133
|
expect(() => {
|
|
129
134
|
InstanceSelector.selectInstance('test-server', config, {
|
|
130
135
|
tags: { env: 'prod' }
|
|
131
|
-
});
|
|
136
|
+
}, mockConnectedStatus);
|
|
132
137
|
}).toThrow('Multiple instances match tags');
|
|
133
138
|
});
|
|
134
139
|
it('should return the single instance when no tags provided and only one instance exists', () => {
|
|
@@ -140,7 +145,7 @@ describe('InstanceSelector', () => {
|
|
|
140
145
|
instances: [{ ...baseInstance, id: '1', index: 0 }],
|
|
141
146
|
tagDefinitions: []
|
|
142
147
|
};
|
|
143
|
-
const selected = InstanceSelector.selectInstance('test-server', config);
|
|
148
|
+
const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
|
|
144
149
|
expect(selected).toBeDefined();
|
|
145
150
|
expect(selected.id).toBe('1');
|
|
146
151
|
});
|
|
@@ -157,7 +162,7 @@ describe('InstanceSelector', () => {
|
|
|
157
162
|
tagDefinitions: []
|
|
158
163
|
};
|
|
159
164
|
expect(() => {
|
|
160
|
-
InstanceSelector.selectInstance('test-server', config);
|
|
165
|
+
InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
|
|
161
166
|
}).toThrow('No tags provided for tag-match-unique strategy with 2 instances. Available: [0:{}, 1:{}]. Pass matching tags to select.');
|
|
162
167
|
});
|
|
163
168
|
});
|
|
@@ -171,7 +176,7 @@ describe('InstanceSelector', () => {
|
|
|
171
176
|
instances: [{ ...baseInstance, id: '1', index: 0 }],
|
|
172
177
|
tagDefinitions: []
|
|
173
178
|
};
|
|
174
|
-
const selected = InstanceSelector.selectInstance('test-server', config);
|
|
179
|
+
const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
|
|
175
180
|
expect(selected).toBeDefined();
|
|
176
181
|
expect(selected.id).toBe('1');
|
|
177
182
|
});
|
|
@@ -187,7 +192,7 @@ describe('InstanceSelector', () => {
|
|
|
187
192
|
tagDefinitions: []
|
|
188
193
|
// No instanceSelectionStrategy field
|
|
189
194
|
};
|
|
190
|
-
const selected = InstanceSelector.selectInstance('test-server', config);
|
|
195
|
+
const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
|
|
191
196
|
expect(selected).toBeDefined();
|
|
192
197
|
expect(['1', '2']).toContain(selected.id);
|
|
193
198
|
});
|
|
@@ -36,6 +36,7 @@ describe('HubToolsService', () => {
|
|
|
36
36
|
instances: [
|
|
37
37
|
{
|
|
38
38
|
id: 'test-server-1-instance',
|
|
39
|
+
index: 0,
|
|
39
40
|
enabled: true,
|
|
40
41
|
args: [],
|
|
41
42
|
env: {},
|
|
@@ -62,6 +63,7 @@ describe('HubToolsService', () => {
|
|
|
62
63
|
instances: [
|
|
63
64
|
{
|
|
64
65
|
id: 'test-server-2-instance',
|
|
66
|
+
index: 0,
|
|
65
67
|
enabled: true,
|
|
66
68
|
args: [],
|
|
67
69
|
env: {},
|
|
@@ -82,13 +84,11 @@ describe('HubToolsService', () => {
|
|
|
82
84
|
const server = mockServers.find((s) => s.name === name);
|
|
83
85
|
return server?.config;
|
|
84
86
|
});
|
|
85
|
-
vi.mocked(mcpConnectionManager.
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
resourcesCount: 0
|
|
91
|
-
};
|
|
87
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation(() => {
|
|
88
|
+
return [0];
|
|
89
|
+
});
|
|
90
|
+
vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
|
|
91
|
+
return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
|
|
92
92
|
});
|
|
93
93
|
// Act
|
|
94
94
|
const servers = await hubToolsService.listServers();
|
|
@@ -118,6 +118,7 @@ describe('HubToolsService', () => {
|
|
|
118
118
|
instances: [
|
|
119
119
|
{
|
|
120
120
|
id: 'server1-instance',
|
|
121
|
+
index: 0,
|
|
121
122
|
enabled: true,
|
|
122
123
|
args: [],
|
|
123
124
|
env: {},
|
|
@@ -138,13 +139,11 @@ describe('HubToolsService', () => {
|
|
|
138
139
|
const server = mockServers.find((s) => s.name === name);
|
|
139
140
|
return server?.config;
|
|
140
141
|
});
|
|
141
|
-
vi.mocked(mcpConnectionManager.
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
resourcesCount: 0
|
|
147
|
-
};
|
|
142
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation(() => {
|
|
143
|
+
return [0];
|
|
144
|
+
});
|
|
145
|
+
vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
|
|
146
|
+
return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
|
|
148
147
|
});
|
|
149
148
|
// Act
|
|
150
149
|
const servers = await hubToolsService.listServers();
|
|
@@ -173,6 +172,7 @@ describe('HubToolsService', () => {
|
|
|
173
172
|
instances: [
|
|
174
173
|
{
|
|
175
174
|
id: 'filesystem-instance',
|
|
175
|
+
index: 0,
|
|
176
176
|
enabled: true,
|
|
177
177
|
args: [],
|
|
178
178
|
env: {},
|
|
@@ -200,6 +200,7 @@ describe('HubToolsService', () => {
|
|
|
200
200
|
instances: [
|
|
201
201
|
{
|
|
202
202
|
id: 'time-instance',
|
|
203
|
+
index: 0,
|
|
203
204
|
enabled: true,
|
|
204
205
|
args: [],
|
|
205
206
|
env: {},
|
|
@@ -220,13 +221,11 @@ describe('HubToolsService', () => {
|
|
|
220
221
|
const server = mockServers.find((s) => s.name === name);
|
|
221
222
|
return server?.config;
|
|
222
223
|
});
|
|
223
|
-
vi.mocked(mcpConnectionManager.
|
|
224
|
-
return
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
resourcesCount: 0
|
|
229
|
-
};
|
|
224
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation(() => {
|
|
225
|
+
return [0];
|
|
226
|
+
});
|
|
227
|
+
vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
|
|
228
|
+
return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
|
|
230
229
|
});
|
|
231
230
|
// Act
|
|
232
231
|
const servers = await hubToolsService.listServers();
|
|
@@ -256,6 +255,7 @@ describe('HubToolsService', () => {
|
|
|
256
255
|
instances: [
|
|
257
256
|
{
|
|
258
257
|
id: 'connected-server-instance',
|
|
258
|
+
index: 0,
|
|
259
259
|
enabled: true,
|
|
260
260
|
args: [],
|
|
261
261
|
env: {},
|
|
@@ -303,21 +303,14 @@ describe('HubToolsService', () => {
|
|
|
303
303
|
const server = mockServers.find((s) => s.name === name);
|
|
304
304
|
return server?.config;
|
|
305
305
|
});
|
|
306
|
-
vi.mocked(mcpConnectionManager.
|
|
306
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation((name) => {
|
|
307
307
|
if (name === 'Connected Server') {
|
|
308
|
-
return
|
|
309
|
-
connected: true,
|
|
310
|
-
lastCheck: Date.now(),
|
|
311
|
-
toolsCount: 0,
|
|
312
|
-
resourcesCount: 0
|
|
313
|
-
};
|
|
308
|
+
return [0];
|
|
314
309
|
}
|
|
315
|
-
return
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
resourcesCount: 0
|
|
320
|
-
};
|
|
310
|
+
return [];
|
|
311
|
+
});
|
|
312
|
+
vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
|
|
313
|
+
return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
|
|
321
314
|
});
|
|
322
315
|
// Act
|
|
323
316
|
const servers = await hubToolsService.listServers();
|
|
@@ -380,6 +373,7 @@ describe('HubToolsService', () => {
|
|
|
380
373
|
tagDefinitions: []
|
|
381
374
|
});
|
|
382
375
|
vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
|
|
376
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
|
|
383
377
|
// Act
|
|
384
378
|
const result = await hubToolsService.listToolsInServer({ serverName });
|
|
385
379
|
// Assert
|
|
@@ -392,7 +386,7 @@ describe('HubToolsService', () => {
|
|
|
392
386
|
it('should throw error if server not found', async () => {
|
|
393
387
|
// Arrange
|
|
394
388
|
const serverName = 'Non-existent Server';
|
|
395
|
-
vi.mocked(mcpConnectionManager.
|
|
389
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([]);
|
|
396
390
|
// Act & Assert
|
|
397
391
|
await expect(hubToolsService.listToolsInServer({ serverName })).rejects.toThrow(`Server not found: ${serverName}`);
|
|
398
392
|
});
|
|
@@ -440,6 +434,7 @@ describe('HubToolsService', () => {
|
|
|
440
434
|
tagDefinitions: []
|
|
441
435
|
});
|
|
442
436
|
vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
|
|
437
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
|
|
443
438
|
// Act
|
|
444
439
|
const tool = await hubToolsService.getTool({ serverName, toolName });
|
|
445
440
|
// Assert
|
|
@@ -480,6 +475,8 @@ describe('HubToolsService', () => {
|
|
|
480
475
|
instances: [mockInstance],
|
|
481
476
|
tagDefinitions: []
|
|
482
477
|
});
|
|
478
|
+
vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
|
|
479
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
|
|
483
480
|
vi.mocked(mcpConnectionManager.getTools).mockReturnValue(mockTools);
|
|
484
481
|
// Act
|
|
485
482
|
const tool = await hubToolsService.getTool({ serverName, toolName });
|
|
@@ -681,6 +678,7 @@ describe('HubToolsService', () => {
|
|
|
681
678
|
vi.mocked(mcpConnectionManager.getResources).mockReturnValue([
|
|
682
679
|
{ uri: 'test://resource', name: 'Test Resource' }
|
|
683
680
|
]);
|
|
681
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
|
|
684
682
|
// Act
|
|
685
683
|
const resources = await hubToolsService.listResources();
|
|
686
684
|
// Assert - the resource list should include use-guide and at least the server resource
|
|
@@ -747,6 +745,7 @@ describe('HubToolsService', () => {
|
|
|
747
745
|
instances: [
|
|
748
746
|
{
|
|
749
747
|
id: 'test-instance',
|
|
748
|
+
index: 0,
|
|
750
749
|
enabled: true,
|
|
751
750
|
args: [],
|
|
752
751
|
env: {},
|
|
@@ -792,6 +791,7 @@ describe('HubToolsService', () => {
|
|
|
792
791
|
const serverName = 'Test Server';
|
|
793
792
|
const mockInstance = {
|
|
794
793
|
id: '1',
|
|
794
|
+
index: 0,
|
|
795
795
|
enabled: true,
|
|
796
796
|
args: [],
|
|
797
797
|
env: {},
|
|
@@ -817,6 +817,7 @@ describe('HubToolsService', () => {
|
|
|
817
817
|
const serverName = 'Test Server';
|
|
818
818
|
const mockInstance = {
|
|
819
819
|
id: '1',
|
|
820
|
+
index: 0,
|
|
820
821
|
enabled: true,
|
|
821
822
|
args: [],
|
|
822
823
|
env: {},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-security.test.d.ts","sourceRoot":"","sources":["../../../../../tests/unit/utils/network-security.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ip4ToInt, normalizeIp, parseCidr, isIpAllowed } from '../../../src/utils/network-security.js';
|
|
3
|
+
describe('network-security', () => {
|
|
4
|
+
describe('ip4ToInt', () => {
|
|
5
|
+
it('should convert standard IPv4 to 32-bit integer', () => {
|
|
6
|
+
expect(ip4ToInt('192.168.1.1')).toBe(0xc0a80101);
|
|
7
|
+
expect(ip4ToInt('0.0.0.0')).toBe(0);
|
|
8
|
+
expect(ip4ToInt('255.255.255.255')).toBe(0xffffffff >>> 0);
|
|
9
|
+
expect(ip4ToInt('127.0.0.1')).toBe(0x7f000001);
|
|
10
|
+
expect(ip4ToInt('10.0.0.0')).toBe(0x0a000000);
|
|
11
|
+
});
|
|
12
|
+
it('should return NaN for invalid format', () => {
|
|
13
|
+
expect(ip4ToInt('')).toBeNaN();
|
|
14
|
+
expect(ip4ToInt('abc')).toBeNaN();
|
|
15
|
+
expect(ip4ToInt('1.2.3.4.5')).toBeNaN();
|
|
16
|
+
expect(ip4ToInt('1.2.3')).toBeNaN();
|
|
17
|
+
});
|
|
18
|
+
it('should return NaN for out-of-range octets', () => {
|
|
19
|
+
expect(ip4ToInt('256.1.1.1')).toBeNaN();
|
|
20
|
+
expect(ip4ToInt('1.1.1.-1')).toBeNaN();
|
|
21
|
+
expect(ip4ToInt('1.1.1.999')).toBeNaN();
|
|
22
|
+
});
|
|
23
|
+
it('should return NaN for non-numeric octets', () => {
|
|
24
|
+
expect(ip4ToInt('1.2.3.abc')).toBeNaN();
|
|
25
|
+
expect(ip4ToInt('1.2.3.1.1')).toBeNaN();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('normalizeIp', () => {
|
|
29
|
+
it('should return IPv4 unchanged', () => {
|
|
30
|
+
expect(normalizeIp('192.168.1.1')).toBe('192.168.1.1');
|
|
31
|
+
expect(normalizeIp('127.0.0.1')).toBe('127.0.0.1');
|
|
32
|
+
});
|
|
33
|
+
it('should strip ::ffff: prefix from IPv4-mapped IPv6', () => {
|
|
34
|
+
expect(normalizeIp('::ffff:192.168.1.1')).toBe('192.168.1.1');
|
|
35
|
+
expect(normalizeIp('::ffff:127.0.0.1')).toBe('127.0.0.1');
|
|
36
|
+
expect(normalizeIp('::ffff:10.0.0.1')).toBe('10.0.0.1');
|
|
37
|
+
});
|
|
38
|
+
it('should map ::1 to 127.0.0.1', () => {
|
|
39
|
+
expect(normalizeIp('::1')).toBe('127.0.0.1');
|
|
40
|
+
});
|
|
41
|
+
it('should return bare IPv6 unchanged', () => {
|
|
42
|
+
expect(normalizeIp('fe80::1')).toBe('fe80::1');
|
|
43
|
+
expect(normalizeIp('::1:2:3:4')).toBe('::1:2:3:4');
|
|
44
|
+
});
|
|
45
|
+
it('should handle empty/falsy values', () => {
|
|
46
|
+
expect(normalizeIp('')).toBe('');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('parseCidr', () => {
|
|
50
|
+
it('should parse standard CIDR notation', () => {
|
|
51
|
+
const result = parseCidr('192.168.1.0/24');
|
|
52
|
+
expect(result).not.toBeNull();
|
|
53
|
+
expect(result.network).toBe(0xc0a80100);
|
|
54
|
+
expect(result.mask).toBe(0xffffff00);
|
|
55
|
+
});
|
|
56
|
+
it('should handle /32 prefix (single host)', () => {
|
|
57
|
+
const result = parseCidr('10.0.0.1/32');
|
|
58
|
+
expect(result).not.toBeNull();
|
|
59
|
+
expect(result.network).toBe(0x0a000001);
|
|
60
|
+
expect(result.mask).toBe(0xffffffff);
|
|
61
|
+
});
|
|
62
|
+
it('should handle /0 prefix (match all)', () => {
|
|
63
|
+
const result = parseCidr('0.0.0.0/0');
|
|
64
|
+
expect(result).not.toBeNull();
|
|
65
|
+
expect(result.mask).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
it('should assume /32 when no prefix given', () => {
|
|
68
|
+
const result = parseCidr('192.168.1.1');
|
|
69
|
+
expect(result).not.toBeNull();
|
|
70
|
+
expect(result.network).toBe(0xc0a80101);
|
|
71
|
+
expect(result.mask).toBe(0xffffffff);
|
|
72
|
+
});
|
|
73
|
+
it('should return null for invalid prefix', () => {
|
|
74
|
+
expect(parseCidr('192.168.1.0/33')).toBeNull();
|
|
75
|
+
expect(parseCidr('192.168.1.0/-1')).toBeNull();
|
|
76
|
+
expect(parseCidr('192.168.1.0/abc')).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
it('should return null for invalid IP in CIDR', () => {
|
|
79
|
+
expect(parseCidr('256.1.1.0/24')).toBeNull();
|
|
80
|
+
expect(parseCidr('abc/24')).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
it('should return null for empty or null input', () => {
|
|
83
|
+
expect(parseCidr('')).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('isIpAllowed', () => {
|
|
87
|
+
const defaults = ['127.0.0.1', '192.168.0.0/16', '10.0.0.0/8'];
|
|
88
|
+
it('should allow IP matching a CIDR', () => {
|
|
89
|
+
expect(isIpAllowed('192.168.1.1', defaults)).toBe(true);
|
|
90
|
+
expect(isIpAllowed('10.0.0.1', defaults)).toBe(true);
|
|
91
|
+
expect(isIpAllowed('10.255.255.255', defaults)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
it('should reject IP not matching any CIDR', () => {
|
|
94
|
+
expect(isIpAllowed('1.2.3.4', defaults)).toBe(false);
|
|
95
|
+
expect(isIpAllowed('8.8.8.8', defaults)).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it('should allow all when list is empty', () => {
|
|
98
|
+
expect(isIpAllowed('1.2.3.4', [])).toBe(true);
|
|
99
|
+
expect(isIpAllowed('8.8.8.8', [])).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
it('should reject invalid IPs (safety default)', () => {
|
|
102
|
+
expect(isIpAllowed('', defaults)).toBe(false);
|
|
103
|
+
expect(isIpAllowed('invalid', defaults)).toBe(false);
|
|
104
|
+
expect(isIpAllowed('256.1.1.1', defaults)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
it('should match IPv4-mapped IPv6 addresses', () => {
|
|
107
|
+
expect(isIpAllowed('::ffff:192.168.1.1', defaults)).toBe(true);
|
|
108
|
+
expect(isIpAllowed('::ffff:10.0.0.1', defaults)).toBe(true);
|
|
109
|
+
expect(isIpAllowed('::ffff:1.2.3.4', defaults)).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
it('should match ::1 as localhost', () => {
|
|
112
|
+
expect(isIpAllowed('::1', defaults)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
it('should skip invalid CIDR entries silently', () => {
|
|
115
|
+
expect(isIpAllowed('192.168.1.1', ['invalid/24', '192.168.0.0/16'])).toBe(true);
|
|
116
|
+
expect(isIpAllowed('1.2.3.4', ['invalid/24'])).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
it('should handle /32 exact match', () => {
|
|
119
|
+
expect(isIpAllowed('192.168.1.1', ['192.168.1.1/32'])).toBe(true);
|
|
120
|
+
expect(isIpAllowed('192.168.1.2', ['192.168.1.1/32'])).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -118,7 +118,7 @@ export default defineConfig({
|
|
|
118
118
|
server: {
|
|
119
119
|
host: '127.0.0.1', // Explicitly use IPv4 address
|
|
120
120
|
port: 5173, // Use common port number to avoid permission issues
|
|
121
|
-
strictPort:
|
|
121
|
+
strictPort: false, // Auto-increment port if occupied
|
|
122
122
|
proxy: {
|
|
123
123
|
'/api': {
|
|
124
124
|
target: `http://${backendHost}:${backendPort}`,
|