@samanhappy/mcphub 0.0.7 → 0.0.9
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/package.json +6 -3
- package/.env.example +0 -2
- package/.eslintrc.json +0 -25
- package/.github/workflows/build.yml +0 -51
- package/.github/workflows/release.yml +0 -19
- package/.prettierrc +0 -7
- package/Dockerfile +0 -51
- package/assets/amap-edit.png +0 -0
- package/assets/amap-result.png +0 -0
- package/assets/cherry-mcp.png +0 -0
- package/assets/cursor-mcp.png +0 -0
- package/assets/cursor-query.png +0 -0
- package/assets/cursor-tools.png +0 -0
- package/assets/dashboard.png +0 -0
- package/assets/dashboard.zh.png +0 -0
- package/assets/group.png +0 -0
- package/assets/group.zh.png +0 -0
- package/assets/market.zh.png +0 -0
- package/assets/wegroup.jpg +0 -0
- package/assets/wegroup.png +0 -0
- package/assets/wexin.png +0 -0
- package/doc/intro.md +0 -73
- package/doc/intro2.md +0 -232
- package/entrypoint.sh +0 -10
- package/frontend/favicon.ico +0 -0
- package/frontend/index.html +0 -13
- package/frontend/postcss.config.js +0 -6
- package/frontend/src/App.tsx +0 -44
- package/frontend/src/components/AddGroupForm.tsx +0 -132
- package/frontend/src/components/AddServerForm.tsx +0 -90
- package/frontend/src/components/ChangePasswordForm.tsx +0 -158
- package/frontend/src/components/EditGroupForm.tsx +0 -149
- package/frontend/src/components/EditServerForm.tsx +0 -76
- package/frontend/src/components/GroupCard.tsx +0 -143
- package/frontend/src/components/MarketServerCard.tsx +0 -153
- package/frontend/src/components/MarketServerDetail.tsx +0 -297
- package/frontend/src/components/ProtectedRoute.tsx +0 -27
- package/frontend/src/components/ServerCard.tsx +0 -230
- package/frontend/src/components/ServerForm.tsx +0 -276
- package/frontend/src/components/icons/LucideIcons.tsx +0 -14
- package/frontend/src/components/layout/Content.tsx +0 -17
- package/frontend/src/components/layout/Header.tsx +0 -61
- package/frontend/src/components/layout/Sidebar.tsx +0 -98
- package/frontend/src/components/ui/Badge.tsx +0 -33
- package/frontend/src/components/ui/Button.tsx +0 -0
- package/frontend/src/components/ui/DeleteDialog.tsx +0 -48
- package/frontend/src/components/ui/Pagination.tsx +0 -128
- package/frontend/src/components/ui/Toast.tsx +0 -96
- package/frontend/src/components/ui/ToggleGroup.tsx +0 -134
- package/frontend/src/components/ui/ToolCard.tsx +0 -38
- package/frontend/src/contexts/AuthContext.tsx +0 -159
- package/frontend/src/contexts/ToastContext.tsx +0 -60
- package/frontend/src/hooks/useGroupData.ts +0 -232
- package/frontend/src/hooks/useMarketData.ts +0 -410
- package/frontend/src/hooks/useServerData.ts +0 -306
- package/frontend/src/hooks/useSettingsData.ts +0 -131
- package/frontend/src/i18n.ts +0 -42
- package/frontend/src/index.css +0 -20
- package/frontend/src/layouts/MainLayout.tsx +0 -33
- package/frontend/src/locales/en.json +0 -214
- package/frontend/src/locales/zh.json +0 -214
- package/frontend/src/main.tsx +0 -12
- package/frontend/src/pages/Dashboard.tsx +0 -206
- package/frontend/src/pages/GroupsPage.tsx +0 -116
- package/frontend/src/pages/LoginPage.tsx +0 -104
- package/frontend/src/pages/MarketPage.tsx +0 -356
- package/frontend/src/pages/ServersPage.tsx +0 -144
- package/frontend/src/pages/SettingsPage.tsx +0 -149
- package/frontend/src/services/authService.ts +0 -141
- package/frontend/src/types/index.ts +0 -160
- package/frontend/src/utils/cn.ts +0 -10
- package/frontend/tsconfig.json +0 -31
- package/frontend/tsconfig.node.json +0 -10
- package/frontend/vite.config.ts +0 -26
- package/googled76ca578b6543fbc.html +0 -1
- package/jest.config.js +0 -10
- package/mcp_settings.json +0 -45
- package/servers.json +0 -74722
- package/src/config/index.ts +0 -46
- package/src/controllers/authController.ts +0 -179
- package/src/controllers/groupController.ts +0 -341
- package/src/controllers/marketController.ts +0 -154
- package/src/controllers/serverController.ts +0 -303
- package/src/index.ts +0 -18
- package/src/middlewares/auth.ts +0 -28
- package/src/middlewares/index.ts +0 -43
- package/src/models/User.ts +0 -103
- package/src/routes/index.ts +0 -96
- package/src/server.ts +0 -72
- package/src/services/groupService.ts +0 -232
- package/src/services/marketService.ts +0 -116
- package/src/services/mcpService.ts +0 -385
- package/src/services/sseService.ts +0 -119
- package/src/types/index.ts +0 -129
- package/src/utils/migration.ts +0 -52
- package/tsconfig.json +0 -17
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
4
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
5
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
6
|
-
import { ServerInfo, ServerConfig } from '../types/index.js';
|
|
7
|
-
import { loadSettings, saveSettings, expandEnvVars } from '../config/index.js';
|
|
8
|
-
import config from '../config/index.js';
|
|
9
|
-
import { getGroup } from './sseService.js';
|
|
10
|
-
import { getServersInGroup } from './groupService.js';
|
|
11
|
-
|
|
12
|
-
let currentServer: Server;
|
|
13
|
-
|
|
14
|
-
export const initMcpServer = async (name: string, version: string): Promise<void> => {
|
|
15
|
-
currentServer = createMcpServer(name, version);
|
|
16
|
-
await registerAllTools(currentServer, true, true);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const setMcpServer = (server: Server): void => {
|
|
20
|
-
currentServer = server;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const getMcpServer = (): Server => {
|
|
24
|
-
return currentServer;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const notifyToolChanged = async () => {
|
|
28
|
-
await registerAllTools(currentServer, true, false);
|
|
29
|
-
currentServer
|
|
30
|
-
.sendToolListChanged()
|
|
31
|
-
.catch((error) => {
|
|
32
|
-
console.warn('Failed to send tool list changed notification:', error.message);
|
|
33
|
-
})
|
|
34
|
-
.then(() => {
|
|
35
|
-
console.log('Tool list changed notification sent successfully');
|
|
36
|
-
});
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// Store all server information
|
|
40
|
-
let serverInfos: ServerInfo[] = [];
|
|
41
|
-
|
|
42
|
-
// Initialize MCP server clients
|
|
43
|
-
export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] => {
|
|
44
|
-
const settings = loadSettings();
|
|
45
|
-
const existingServerInfos = serverInfos;
|
|
46
|
-
serverInfos = [];
|
|
47
|
-
|
|
48
|
-
for (const [name, conf] of Object.entries(settings.mcpServers)) {
|
|
49
|
-
// Skip disabled servers
|
|
50
|
-
if (conf.enabled === false) {
|
|
51
|
-
console.log(`Skipping disabled server: ${name}`);
|
|
52
|
-
serverInfos.push({
|
|
53
|
-
name,
|
|
54
|
-
status: 'disconnected',
|
|
55
|
-
error: null,
|
|
56
|
-
tools: [],
|
|
57
|
-
createTime: Date.now(),
|
|
58
|
-
enabled: false,
|
|
59
|
-
});
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Check if server is already connected
|
|
64
|
-
const existingServer = existingServerInfos.find(
|
|
65
|
-
(s) => s.name === name && s.status === 'connected',
|
|
66
|
-
);
|
|
67
|
-
if (existingServer) {
|
|
68
|
-
serverInfos.push({
|
|
69
|
-
...existingServer,
|
|
70
|
-
enabled: conf.enabled === undefined ? true : conf.enabled,
|
|
71
|
-
});
|
|
72
|
-
console.log(`Server '${name}' is already connected.`);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let transport;
|
|
77
|
-
if (conf.url) {
|
|
78
|
-
transport = new SSEClientTransport(new URL(conf.url));
|
|
79
|
-
} else if (conf.command && conf.args) {
|
|
80
|
-
const env: Record<string, string> = conf.env || {};
|
|
81
|
-
env['PATH'] = expandEnvVars(process.env.PATH as string) || '';
|
|
82
|
-
transport = new StdioClientTransport({
|
|
83
|
-
command: conf.command,
|
|
84
|
-
args: conf.args,
|
|
85
|
-
env: env,
|
|
86
|
-
});
|
|
87
|
-
} else {
|
|
88
|
-
console.warn(`Skipping server '${name}': missing required configuration`);
|
|
89
|
-
serverInfos.push({
|
|
90
|
-
name,
|
|
91
|
-
status: 'disconnected',
|
|
92
|
-
error: 'Missing required configuration',
|
|
93
|
-
tools: [],
|
|
94
|
-
createTime: Date.now(),
|
|
95
|
-
});
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const client = new Client(
|
|
100
|
-
{
|
|
101
|
-
name: `mcp-client-${name}`,
|
|
102
|
-
version: '1.0.0',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
capabilities: {
|
|
106
|
-
prompts: {},
|
|
107
|
-
resources: {},
|
|
108
|
-
tools: {},
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
const timeout = isInit ? Number(config.initTimeout) : Number(config.timeout);
|
|
113
|
-
client
|
|
114
|
-
.connect(transport, { timeout: timeout })
|
|
115
|
-
.then(() => {
|
|
116
|
-
console.log(`Successfully connected client for server: ${name}`);
|
|
117
|
-
|
|
118
|
-
client
|
|
119
|
-
.listTools({}, { timeout: timeout })
|
|
120
|
-
.then((tools) => {
|
|
121
|
-
console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`);
|
|
122
|
-
const serverInfo = getServerByName(name);
|
|
123
|
-
if (!serverInfo) {
|
|
124
|
-
console.warn(`Server info not found for server: ${name}`);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
serverInfo.tools = tools.tools.map((tool) => ({
|
|
129
|
-
name: tool.name,
|
|
130
|
-
description: tool.description || '',
|
|
131
|
-
inputSchema: tool.inputSchema || {},
|
|
132
|
-
}));
|
|
133
|
-
serverInfo.status = 'connected';
|
|
134
|
-
serverInfo.error = null;
|
|
135
|
-
})
|
|
136
|
-
.catch((error) => {
|
|
137
|
-
console.error(
|
|
138
|
-
`Failed to list tools for server ${name} by error: ${error} with stack: ${error.stack}`,
|
|
139
|
-
);
|
|
140
|
-
const serverInfo = getServerByName(name);
|
|
141
|
-
if (serverInfo) {
|
|
142
|
-
serverInfo.status = 'disconnected';
|
|
143
|
-
serverInfo.error = `Failed to list tools: ${error.stack} `;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
})
|
|
147
|
-
.catch((error) => {
|
|
148
|
-
console.error(
|
|
149
|
-
`Failed to connect client for server ${name} by error: ${error} with stack: ${error.stack}`,
|
|
150
|
-
);
|
|
151
|
-
const serverInfo = getServerByName(name);
|
|
152
|
-
if (serverInfo) {
|
|
153
|
-
serverInfo.status = 'disconnected';
|
|
154
|
-
serverInfo.error = `Failed to connect: ${error.stack} `;
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
serverInfos.push({
|
|
158
|
-
name,
|
|
159
|
-
status: 'connecting',
|
|
160
|
-
error: null,
|
|
161
|
-
tools: [],
|
|
162
|
-
client,
|
|
163
|
-
transport,
|
|
164
|
-
createTime: Date.now(),
|
|
165
|
-
});
|
|
166
|
-
console.log(`Initialized client for server: ${name}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return serverInfos;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
// Register all MCP tools
|
|
173
|
-
export const registerAllTools = async (
|
|
174
|
-
server: Server,
|
|
175
|
-
forceInit: boolean,
|
|
176
|
-
isInit: boolean,
|
|
177
|
-
): Promise<void> => {
|
|
178
|
-
initializeClientsFromSettings(isInit);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Get all server information
|
|
182
|
-
export const getServersInfo = (): Omit<ServerInfo, 'client' | 'transport'>[] => {
|
|
183
|
-
const settings = loadSettings();
|
|
184
|
-
const infos = serverInfos.map(({ name, status, tools, createTime, error }) => {
|
|
185
|
-
const serverConfig = settings.mcpServers[name];
|
|
186
|
-
const enabled = serverConfig ? serverConfig.enabled !== false : true;
|
|
187
|
-
return {
|
|
188
|
-
name,
|
|
189
|
-
status,
|
|
190
|
-
error,
|
|
191
|
-
tools,
|
|
192
|
-
createTime,
|
|
193
|
-
enabled,
|
|
194
|
-
};
|
|
195
|
-
});
|
|
196
|
-
infos.sort((a, b) => {
|
|
197
|
-
if (a.enabled === b.enabled) return 0;
|
|
198
|
-
return a.enabled ? -1 : 1;
|
|
199
|
-
});
|
|
200
|
-
return infos;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
// Get server by name
|
|
204
|
-
const getServerByName = (name: string): ServerInfo | undefined => {
|
|
205
|
-
return serverInfos.find((serverInfo) => serverInfo.name === name);
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// Get server by tool name
|
|
209
|
-
const getServerByTool = (toolName: string): ServerInfo | undefined => {
|
|
210
|
-
return serverInfos.find((serverInfo) => serverInfo.tools.some((tool) => tool.name === toolName));
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// Add new server
|
|
214
|
-
export const addServer = async (
|
|
215
|
-
name: string,
|
|
216
|
-
config: ServerConfig,
|
|
217
|
-
): Promise<{ success: boolean; message?: string }> => {
|
|
218
|
-
try {
|
|
219
|
-
const settings = loadSettings();
|
|
220
|
-
if (settings.mcpServers[name]) {
|
|
221
|
-
return { success: false, message: 'Server name already exists' };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
settings.mcpServers[name] = config;
|
|
225
|
-
if (!saveSettings(settings)) {
|
|
226
|
-
return { success: false, message: 'Failed to save settings' };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
registerAllTools(currentServer, false, false);
|
|
230
|
-
return { success: true, message: 'Server added successfully' };
|
|
231
|
-
} catch (error) {
|
|
232
|
-
console.error(`Failed to add server: ${name}`, error);
|
|
233
|
-
return { success: false, message: 'Failed to add server' };
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// Remove server
|
|
238
|
-
export const removeServer = (name: string): { success: boolean; message?: string } => {
|
|
239
|
-
try {
|
|
240
|
-
const settings = loadSettings();
|
|
241
|
-
if (!settings.mcpServers[name]) {
|
|
242
|
-
return { success: false, message: 'Server not found' };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
delete settings.mcpServers[name];
|
|
246
|
-
|
|
247
|
-
if (!saveSettings(settings)) {
|
|
248
|
-
return { success: false, message: 'Failed to save settings' };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
serverInfos = serverInfos.filter((serverInfo) => serverInfo.name !== name);
|
|
252
|
-
return { success: true, message: 'Server removed successfully' };
|
|
253
|
-
} catch (error) {
|
|
254
|
-
console.error(`Failed to remove server: ${name}`, error);
|
|
255
|
-
return { success: false, message: `Failed to remove server: ${error}` };
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
// Update existing server
|
|
260
|
-
export const updateMcpServer = async (
|
|
261
|
-
name: string,
|
|
262
|
-
config: ServerConfig,
|
|
263
|
-
): Promise<{ success: boolean; message?: string }> => {
|
|
264
|
-
try {
|
|
265
|
-
const settings = loadSettings();
|
|
266
|
-
if (!settings.mcpServers[name]) {
|
|
267
|
-
return { success: false, message: 'Server not found' };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
settings.mcpServers[name] = config;
|
|
271
|
-
if (!saveSettings(settings)) {
|
|
272
|
-
return { success: false, message: 'Failed to save settings' };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
closeServer(name);
|
|
276
|
-
|
|
277
|
-
serverInfos = serverInfos.filter((serverInfo) => serverInfo.name !== name);
|
|
278
|
-
return { success: true, message: 'Server updated successfully' };
|
|
279
|
-
} catch (error) {
|
|
280
|
-
console.error(`Failed to update server: ${name}`, error);
|
|
281
|
-
return { success: false, message: 'Failed to update server' };
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
// Close server client and transport
|
|
286
|
-
function closeServer(name: string) {
|
|
287
|
-
const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name);
|
|
288
|
-
if (serverInfo && serverInfo.client && serverInfo.transport) {
|
|
289
|
-
serverInfo.client.close();
|
|
290
|
-
serverInfo.transport.close();
|
|
291
|
-
console.log(`Closed client and transport for server: ${serverInfo.name}`);
|
|
292
|
-
// TODO kill process
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Toggle server enabled status
|
|
297
|
-
export const toggleServerStatus = async (
|
|
298
|
-
name: string,
|
|
299
|
-
enabled: boolean,
|
|
300
|
-
): Promise<{ success: boolean; message?: string }> => {
|
|
301
|
-
try {
|
|
302
|
-
const settings = loadSettings();
|
|
303
|
-
if (!settings.mcpServers[name]) {
|
|
304
|
-
return { success: false, message: 'Server not found' };
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Update the enabled status in settings
|
|
308
|
-
settings.mcpServers[name].enabled = enabled;
|
|
309
|
-
|
|
310
|
-
if (!saveSettings(settings)) {
|
|
311
|
-
return { success: false, message: 'Failed to save settings' };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// If disabling, disconnect the server and remove from active servers
|
|
315
|
-
if (!enabled) {
|
|
316
|
-
closeServer(name);
|
|
317
|
-
|
|
318
|
-
// Update the server info to show as disconnected and disabled
|
|
319
|
-
const index = serverInfos.findIndex((s) => s.name === name);
|
|
320
|
-
if (index !== -1) {
|
|
321
|
-
serverInfos[index] = {
|
|
322
|
-
...serverInfos[index],
|
|
323
|
-
status: 'disconnected',
|
|
324
|
-
enabled: false,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return { success: true, message: `Server ${enabled ? 'enabled' : 'disabled'} successfully` };
|
|
330
|
-
} catch (error) {
|
|
331
|
-
console.error(`Failed to toggle server status: ${name}`, error);
|
|
332
|
-
return { success: false, message: 'Failed to toggle server status' };
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
// Create McpServer instance
|
|
337
|
-
export const createMcpServer = (name: string, version: string): Server => {
|
|
338
|
-
const server = new Server({ name, version }, { capabilities: { tools: {} } });
|
|
339
|
-
server.setRequestHandler(ListToolsRequestSchema, async (_, extra) => {
|
|
340
|
-
const sessionId = extra.sessionId || '';
|
|
341
|
-
const group = getGroup(sessionId);
|
|
342
|
-
console.log(`Handling ListToolsRequest for group: ${group}`);
|
|
343
|
-
const allServerInfos = serverInfos.filter((serverInfo) => {
|
|
344
|
-
if (serverInfo.enabled === false) return false;
|
|
345
|
-
if (!group) return true;
|
|
346
|
-
const serversInGroup = getServersInGroup(group);
|
|
347
|
-
return serversInGroup.includes(serverInfo.name);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const allTools = [];
|
|
351
|
-
for (const serverInfo of allServerInfos) {
|
|
352
|
-
if (serverInfo.tools && serverInfo.tools.length > 0) {
|
|
353
|
-
allTools.push(...serverInfo.tools);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return {
|
|
358
|
-
tools: allTools,
|
|
359
|
-
};
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
server.setRequestHandler(CallToolRequestSchema, async (request, _) => {
|
|
363
|
-
console.log(`Handling CallToolRequest for tool: ${request.params.name}`);
|
|
364
|
-
try {
|
|
365
|
-
if (!request.params.arguments) {
|
|
366
|
-
throw new Error('Arguments are required');
|
|
367
|
-
}
|
|
368
|
-
const serverInfo = getServerByTool(request.params.name);
|
|
369
|
-
if (!serverInfo) {
|
|
370
|
-
throw new Error(`Server not found: ${request.params.name}`);
|
|
371
|
-
}
|
|
372
|
-
const client = serverInfo.client;
|
|
373
|
-
if (!client) {
|
|
374
|
-
throw new Error(`Client not found for server: ${request.params.name}`);
|
|
375
|
-
}
|
|
376
|
-
const result = await client.callTool(request.params);
|
|
377
|
-
console.log(`Tool call result: ${JSON.stringify(result)}`);
|
|
378
|
-
return result;
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error(`Error handling CallToolRequest: ${error}`);
|
|
381
|
-
return { error: `Failed to call tool: ${error}` };
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
return server;
|
|
385
|
-
};
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
4
|
-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
5
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
6
|
-
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import { getMcpServer } from './mcpService.js';
|
|
8
|
-
import { loadSettings } from '../config/index.js';
|
|
9
|
-
|
|
10
|
-
const transports: { [sessionId: string]: { transport: Transport; group: string } } = {};
|
|
11
|
-
|
|
12
|
-
export const getGroup = (sessionId: string): string => {
|
|
13
|
-
return transports[sessionId]?.group || '';
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export const handleSseConnection = async (req: Request, res: Response): Promise<void> => {
|
|
17
|
-
const settings = loadSettings();
|
|
18
|
-
const routingConfig = settings.systemConfig?.routing || {
|
|
19
|
-
enableGlobalRoute: true,
|
|
20
|
-
enableGroupNameRoute: true,
|
|
21
|
-
};
|
|
22
|
-
const group = req.params.group;
|
|
23
|
-
|
|
24
|
-
// Check if this is a global route (no group) and if it's allowed
|
|
25
|
-
if (!group && !routingConfig.enableGlobalRoute) {
|
|
26
|
-
res.status(403).send('Global routes are disabled. Please specify a group ID.');
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const transport = new SSEServerTransport('/messages', res);
|
|
31
|
-
transports[transport.sessionId] = { transport, group: group };
|
|
32
|
-
|
|
33
|
-
res.on('close', () => {
|
|
34
|
-
delete transports[transport.sessionId];
|
|
35
|
-
console.log(`SSE connection closed: ${transport.sessionId}`);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
console.log(
|
|
39
|
-
`New SSE connection established: ${transport.sessionId} with group: ${group || 'global'}`,
|
|
40
|
-
);
|
|
41
|
-
await getMcpServer().connect(transport);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const handleSseMessage = async (req: Request, res: Response): Promise<void> => {
|
|
45
|
-
const sessionId = req.query.sessionId as string;
|
|
46
|
-
const { transport, group } = transports[sessionId];
|
|
47
|
-
req.params.group = group;
|
|
48
|
-
req.query.group = group;
|
|
49
|
-
console.log(`Received message for sessionId: ${sessionId} in group: ${group}`);
|
|
50
|
-
if (transport) {
|
|
51
|
-
await (transport as SSEServerTransport).handlePostMessage(req, res);
|
|
52
|
-
} else {
|
|
53
|
-
console.error(`No transport found for sessionId: ${sessionId}`);
|
|
54
|
-
res.status(400).send('No transport found for sessionId');
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const handleMcpPostRequest = async (req: Request, res: Response): Promise<void> => {
|
|
59
|
-
console.log('Handling MCP post request');
|
|
60
|
-
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
61
|
-
const group = req.params.group;
|
|
62
|
-
const settings = loadSettings();
|
|
63
|
-
const routingConfig = settings.systemConfig?.routing || {
|
|
64
|
-
enableGlobalRoute: true,
|
|
65
|
-
enableGroupNameRoute: true,
|
|
66
|
-
};
|
|
67
|
-
if (!group && !routingConfig.enableGlobalRoute) {
|
|
68
|
-
res.status(403).send('Global routes are disabled. Please specify a group ID.');
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let transport: StreamableHTTPServerTransport;
|
|
73
|
-
if (sessionId && transports[sessionId]) {
|
|
74
|
-
transport = transports[sessionId].transport as StreamableHTTPServerTransport;
|
|
75
|
-
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
76
|
-
transport = new StreamableHTTPServerTransport({
|
|
77
|
-
sessionIdGenerator: () => randomUUID(),
|
|
78
|
-
onsessioninitialized: (sessionId) => {
|
|
79
|
-
transports[sessionId] = { transport, group };
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
transport.onclose = () => {
|
|
84
|
-
if (transport.sessionId) {
|
|
85
|
-
delete transports[transport.sessionId];
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
await getMcpServer().connect(transport);
|
|
90
|
-
} else {
|
|
91
|
-
res.status(400).json({
|
|
92
|
-
jsonrpc: '2.0',
|
|
93
|
-
error: {
|
|
94
|
-
code: -32000,
|
|
95
|
-
message: 'Bad Request: No valid session ID provided',
|
|
96
|
-
},
|
|
97
|
-
id: null,
|
|
98
|
-
});
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
await transport.handleRequest(req, res, req.body);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export const handleMcpOtherRequest = async (req: Request, res: Response) => {
|
|
106
|
-
console.log('Handling MCP other request');
|
|
107
|
-
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
108
|
-
if (!sessionId || !transports[sessionId]) {
|
|
109
|
-
res.status(400).send('Invalid or missing session ID');
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const { transport } = transports[sessionId];
|
|
114
|
-
await (transport as StreamableHTTPServerTransport).handleRequest(req, res);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export const getConnectionCount = (): number => {
|
|
118
|
-
return Object.keys(transports).length;
|
|
119
|
-
};
|
package/src/types/index.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
3
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
4
|
-
|
|
5
|
-
// User interface
|
|
6
|
-
export interface IUser {
|
|
7
|
-
username: string;
|
|
8
|
-
password: string;
|
|
9
|
-
isAdmin?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Group interface for server grouping
|
|
13
|
-
export interface IGroup {
|
|
14
|
-
id: string; // Unique UUID for the group
|
|
15
|
-
name: string; // Display name of the group
|
|
16
|
-
description?: string; // Optional description of the group
|
|
17
|
-
servers: string[]; // Array of server names that belong to this group
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Market server types
|
|
21
|
-
export interface MarketServerRepository {
|
|
22
|
-
type: string;
|
|
23
|
-
url: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface MarketServerAuthor {
|
|
27
|
-
name: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface MarketServerInstallation {
|
|
31
|
-
type: string;
|
|
32
|
-
command: string;
|
|
33
|
-
args: string[];
|
|
34
|
-
env?: Record<string, string>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface MarketServerArgument {
|
|
38
|
-
description: string;
|
|
39
|
-
required: boolean;
|
|
40
|
-
example: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface MarketServerExample {
|
|
44
|
-
title: string;
|
|
45
|
-
description: string;
|
|
46
|
-
prompt: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface MarketServerTool {
|
|
50
|
-
name: string;
|
|
51
|
-
description: string;
|
|
52
|
-
inputSchema: Record<string, any>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface MarketServer {
|
|
56
|
-
name: string;
|
|
57
|
-
display_name: string;
|
|
58
|
-
description: string;
|
|
59
|
-
repository: MarketServerRepository;
|
|
60
|
-
homepage: string;
|
|
61
|
-
author: MarketServerAuthor;
|
|
62
|
-
license: string;
|
|
63
|
-
categories: string[];
|
|
64
|
-
tags: string[];
|
|
65
|
-
examples: MarketServerExample[];
|
|
66
|
-
installations: {
|
|
67
|
-
[key: string]: MarketServerInstallation;
|
|
68
|
-
};
|
|
69
|
-
arguments: Record<string, MarketServerArgument>;
|
|
70
|
-
tools: MarketServerTool[];
|
|
71
|
-
is_official?: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Represents the settings for MCP servers
|
|
75
|
-
export interface McpSettings {
|
|
76
|
-
users?: IUser[]; // Array of user credentials and permissions
|
|
77
|
-
mcpServers: {
|
|
78
|
-
[key: string]: ServerConfig; // Key-value pairs of server names and their configurations
|
|
79
|
-
};
|
|
80
|
-
groups?: IGroup[]; // Array of server groups
|
|
81
|
-
systemConfig?: {
|
|
82
|
-
routing?: {
|
|
83
|
-
enableGlobalRoute?: boolean; // Controls whether the /sse endpoint without group is enabled
|
|
84
|
-
enableGroupNameRoute?: boolean; // Controls whether group routing by name is allowed
|
|
85
|
-
};
|
|
86
|
-
// Add other system configuration sections here in the future
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Configuration details for an individual server
|
|
91
|
-
export interface ServerConfig {
|
|
92
|
-
url?: string; // URL for SSE-based servers
|
|
93
|
-
command?: string; // Command to execute for stdio-based servers
|
|
94
|
-
args?: string[]; // Arguments for the command
|
|
95
|
-
env?: Record<string, string>; // Environment variables
|
|
96
|
-
enabled?: boolean; // Flag to enable/disable the server
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Information about a server's status and tools
|
|
100
|
-
export interface ServerInfo {
|
|
101
|
-
name: string; // Unique name of the server
|
|
102
|
-
status: 'connected' | 'connecting' | 'disconnected'; // Current connection status
|
|
103
|
-
error: string | null; // Error message if any
|
|
104
|
-
tools: ToolInfo[]; // List of tools available on the server
|
|
105
|
-
client?: Client; // Client instance for communication
|
|
106
|
-
transport?: SSEClientTransport | StdioClientTransport; // Transport mechanism used
|
|
107
|
-
createTime: number; // Timestamp of when the server was created
|
|
108
|
-
enabled?: boolean; // Flag to indicate if the server is enabled
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Details about a tool available on the server
|
|
112
|
-
export interface ToolInfo {
|
|
113
|
-
name: string; // Name of the tool
|
|
114
|
-
description: string; // Brief description of the tool
|
|
115
|
-
inputSchema: Record<string, unknown>; // Input schema for the tool
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Standardized API response structure
|
|
119
|
-
export interface ApiResponse<T = unknown> {
|
|
120
|
-
success: boolean; // Indicates if the operation was successful
|
|
121
|
-
message?: string; // Optional message providing additional details
|
|
122
|
-
data?: T; // Optional data payload
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Request payload for adding a new server
|
|
126
|
-
export interface AddServerRequest {
|
|
127
|
-
name: string; // Name of the server to add
|
|
128
|
-
config: ServerConfig; // Configuration details for the server
|
|
129
|
-
}
|
package/src/utils/migration.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// filepath: /Users/sunmeng/code/github/mcphub/src/utils/migration.ts
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { loadSettings, saveSettings } from '../config/index.js';
|
|
5
|
-
import { IUser } from '../types/index.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Migrates user data from the old users.json file to mcp_settings.json
|
|
9
|
-
* This is a one-time migration to support the refactoring from separate
|
|
10
|
-
* users.json to integrated user data in mcp_settings.json
|
|
11
|
-
*/
|
|
12
|
-
export const migrateUserData = (): void => {
|
|
13
|
-
const oldUsersFilePath = path.join(process.cwd(), 'data', 'users.json');
|
|
14
|
-
|
|
15
|
-
// Check if the old users file exists
|
|
16
|
-
if (fs.existsSync(oldUsersFilePath)) {
|
|
17
|
-
try {
|
|
18
|
-
// Read users from the old file
|
|
19
|
-
const usersData = fs.readFileSync(oldUsersFilePath, 'utf8');
|
|
20
|
-
const users = JSON.parse(usersData) as IUser[];
|
|
21
|
-
|
|
22
|
-
if (users && Array.isArray(users) && users.length > 0) {
|
|
23
|
-
console.log(`Migrating ${users.length} users from users.json to mcp_settings.json`);
|
|
24
|
-
|
|
25
|
-
// Load current settings
|
|
26
|
-
const settings = loadSettings();
|
|
27
|
-
|
|
28
|
-
// Merge users, giving priority to existing settings users
|
|
29
|
-
const existingUsernames = new Set((settings.users || []).map(u => u.username));
|
|
30
|
-
const newUsers = users.filter(u => !existingUsernames.has(u.username));
|
|
31
|
-
|
|
32
|
-
settings.users = [...(settings.users || []), ...newUsers];
|
|
33
|
-
|
|
34
|
-
// Save updated settings
|
|
35
|
-
if (saveSettings(settings)) {
|
|
36
|
-
console.log('User data migration completed successfully');
|
|
37
|
-
|
|
38
|
-
// Rename the old file as backup
|
|
39
|
-
const backupPath = `${oldUsersFilePath}.bak.${Date.now()}`;
|
|
40
|
-
fs.renameSync(oldUsersFilePath, backupPath);
|
|
41
|
-
console.log(`Renamed old users file to ${backupPath}`);
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
console.log('No users found in users.json, skipping migration');
|
|
45
|
-
}
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.error('Error during user data migration:', error);
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
console.log('users.json not found, no migration needed');
|
|
51
|
-
}
|
|
52
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"sourceMap": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*"],
|
|
16
|
-
"exclude": ["node_modules", "**/*.test.ts", "dist"]
|
|
17
|
-
}
|