@samanhappy/mcphub 0.0.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 (39) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +170 -0
  3. package/README.zh.md +173 -0
  4. package/bin/mcphub.js +54 -0
  5. package/dist/config/index.js +41 -0
  6. package/dist/config/index.js.map +1 -0
  7. package/dist/controllers/authController.js +149 -0
  8. package/dist/controllers/authController.js.map +1 -0
  9. package/dist/controllers/groupController.js +312 -0
  10. package/dist/controllers/groupController.js.map +1 -0
  11. package/dist/controllers/marketController.js +140 -0
  12. package/dist/controllers/marketController.js.map +1 -0
  13. package/dist/controllers/serverController.js +283 -0
  14. package/dist/controllers/serverController.js.map +1 -0
  15. package/dist/index.js +15 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/middlewares/auth.js +24 -0
  18. package/dist/middlewares/auth.js.map +1 -0
  19. package/dist/middlewares/index.js +33 -0
  20. package/dist/middlewares/index.js.map +1 -0
  21. package/dist/models/User.js +80 -0
  22. package/dist/models/User.js.map +1 -0
  23. package/dist/routes/index.js +56 -0
  24. package/dist/routes/index.js.map +1 -0
  25. package/dist/server.js +58 -0
  26. package/dist/server.js.map +1 -0
  27. package/dist/services/groupService.js +189 -0
  28. package/dist/services/groupService.js.map +1 -0
  29. package/dist/services/marketService.js +98 -0
  30. package/dist/services/marketService.js.map +1 -0
  31. package/dist/services/mcpService.js +333 -0
  32. package/dist/services/mcpService.js.map +1 -0
  33. package/dist/services/sseService.js +103 -0
  34. package/dist/services/sseService.js.map +1 -0
  35. package/dist/types/index.js +2 -0
  36. package/dist/types/index.js.map +1 -0
  37. package/dist/utils/migration.js +47 -0
  38. package/dist/utils/migration.js.map +1 -0
  39. package/package.json +92 -0
@@ -0,0 +1,103 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
5
+ import { getMcpServer } from './mcpService.js';
6
+ import { loadSettings } from '../config/index.js';
7
+ const transports = {};
8
+ export const getGroup = (sessionId) => {
9
+ return transports[sessionId]?.group || '';
10
+ };
11
+ export const handleSseConnection = async (req, res) => {
12
+ const settings = loadSettings();
13
+ const routingConfig = settings.systemConfig?.routing || {
14
+ enableGlobalRoute: true,
15
+ enableGroupNameRoute: true,
16
+ };
17
+ const group = req.params.group;
18
+ // Check if this is a global route (no group) and if it's allowed
19
+ if (!group && !routingConfig.enableGlobalRoute) {
20
+ res.status(403).send('Global routes are disabled. Please specify a group ID.');
21
+ return;
22
+ }
23
+ const transport = new SSEServerTransport('/messages', res);
24
+ transports[transport.sessionId] = { transport, group: group };
25
+ res.on('close', () => {
26
+ delete transports[transport.sessionId];
27
+ console.log(`SSE connection closed: ${transport.sessionId}`);
28
+ });
29
+ console.log(`New SSE connection established: ${transport.sessionId} with group: ${group || 'global'}`);
30
+ await getMcpServer().connect(transport);
31
+ };
32
+ export const handleSseMessage = async (req, res) => {
33
+ const sessionId = req.query.sessionId;
34
+ const { transport, group } = transports[sessionId];
35
+ req.params.group = group;
36
+ req.query.group = group;
37
+ console.log(`Received message for sessionId: ${sessionId} in group: ${group}`);
38
+ if (transport) {
39
+ await transport.handlePostMessage(req, res);
40
+ }
41
+ else {
42
+ console.error(`No transport found for sessionId: ${sessionId}`);
43
+ res.status(400).send('No transport found for sessionId');
44
+ }
45
+ };
46
+ export const handleMcpPostRequest = async (req, res) => {
47
+ console.log('Handling MCP post request');
48
+ const sessionId = req.headers['mcp-session-id'];
49
+ const group = req.params.group;
50
+ const settings = loadSettings();
51
+ const routingConfig = settings.systemConfig?.routing || {
52
+ enableGlobalRoute: true,
53
+ enableGroupNameRoute: true,
54
+ };
55
+ if (!group && !routingConfig.enableGlobalRoute) {
56
+ res.status(403).send('Global routes are disabled. Please specify a group ID.');
57
+ return;
58
+ }
59
+ let transport;
60
+ if (sessionId && transports[sessionId]) {
61
+ transport = transports[sessionId].transport;
62
+ }
63
+ else if (!sessionId && isInitializeRequest(req.body)) {
64
+ transport = new StreamableHTTPServerTransport({
65
+ sessionIdGenerator: () => randomUUID(),
66
+ onsessioninitialized: (sessionId) => {
67
+ transports[sessionId] = { transport, group };
68
+ },
69
+ });
70
+ transport.onclose = () => {
71
+ if (transport.sessionId) {
72
+ delete transports[transport.sessionId];
73
+ }
74
+ };
75
+ await getMcpServer().connect(transport);
76
+ }
77
+ else {
78
+ res.status(400).json({
79
+ jsonrpc: '2.0',
80
+ error: {
81
+ code: -32000,
82
+ message: 'Bad Request: No valid session ID provided',
83
+ },
84
+ id: null,
85
+ });
86
+ return;
87
+ }
88
+ await transport.handleRequest(req, res, req.body);
89
+ };
90
+ export const handleMcpOtherRequest = async (req, res) => {
91
+ console.log('Handling MCP other request');
92
+ const sessionId = req.headers['mcp-session-id'];
93
+ if (!sessionId || !transports[sessionId]) {
94
+ res.status(400).send('Invalid or missing session ID');
95
+ return;
96
+ }
97
+ const { transport } = transports[sessionId];
98
+ await transport.handleRequest(req, res);
99
+ };
100
+ export const getConnectionCount = () => {
101
+ return Object.keys(transports).length;
102
+ };
103
+ //# sourceMappingURL=sseService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sseService.js","sourceRoot":"","sources":["../../src/services/sseService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
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
+ /**
6
+ * Migrates user data from the old users.json file to mcp_settings.json
7
+ * This is a one-time migration to support the refactoring from separate
8
+ * users.json to integrated user data in mcp_settings.json
9
+ */
10
+ export const migrateUserData = () => {
11
+ const oldUsersFilePath = path.join(process.cwd(), 'data', 'users.json');
12
+ // Check if the old users file exists
13
+ if (fs.existsSync(oldUsersFilePath)) {
14
+ try {
15
+ // Read users from the old file
16
+ const usersData = fs.readFileSync(oldUsersFilePath, 'utf8');
17
+ const users = JSON.parse(usersData);
18
+ if (users && Array.isArray(users) && users.length > 0) {
19
+ console.log(`Migrating ${users.length} users from users.json to mcp_settings.json`);
20
+ // Load current settings
21
+ const settings = loadSettings();
22
+ // Merge users, giving priority to existing settings users
23
+ const existingUsernames = new Set((settings.users || []).map(u => u.username));
24
+ const newUsers = users.filter(u => !existingUsernames.has(u.username));
25
+ settings.users = [...(settings.users || []), ...newUsers];
26
+ // Save updated settings
27
+ if (saveSettings(settings)) {
28
+ console.log('User data migration completed successfully');
29
+ // Rename the old file as backup
30
+ const backupPath = `${oldUsersFilePath}.bak.${Date.now()}`;
31
+ fs.renameSync(oldUsersFilePath, backupPath);
32
+ console.log(`Renamed old users file to ${backupPath}`);
33
+ }
34
+ }
35
+ else {
36
+ console.log('No users found in users.json, skipping migration');
37
+ }
38
+ }
39
+ catch (error) {
40
+ console.error('Error during user data migration:', error);
41
+ }
42
+ }
43
+ else {
44
+ console.log('users.json not found, no migration needed');
45
+ }
46
+ };
47
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.js","sourceRoot":"","sources":["../../src/utils/migration.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGhE;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAExE,qCAAqC;IACrC,IAAI,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,+BAA+B;YAC/B,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;YAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAY,CAAC;YAE/C,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtD,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,CAAC,MAAM,6CAA6C,CAAC,CAAC;gBAEpF,wBAAwB;gBACxB,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;gBAEhC,0DAA0D;gBAC1D,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC/E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAEvE,QAAQ,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC;gBAE1D,wBAAwB;gBACxB,IAAI,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC3B,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;oBAE1D,gCAAgC;oBAChC,MAAM,UAAU,GAAG,GAAG,gBAAgB,QAAQ,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;oBAC3D,EAAE,CAAC,UAAU,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;oBAC5C,OAAO,CAAC,GAAG,CAAC,6BAA6B,UAAU,EAAE,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@samanhappy/mcphub",
3
+ "version": "0.0.1",
4
+ "description": "A hub server for mcp servers",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "mcphub": "./bin/mcphub.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "README.md"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/username/mcphub.git"
18
+ },
19
+ "homepage": "https://github.com/username/mcphub#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/username/mcphub/issues"
22
+ },
23
+ "author": "Your Name <your.email@example.com>",
24
+ "license": "ISC",
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.10.2",
27
+ "@radix-ui/react-accordion": "^1.2.3",
28
+ "@radix-ui/react-slot": "^1.1.2",
29
+ "@shadcn/ui": "^0.0.4",
30
+ "@tailwindcss/vite": "^4.1.3",
31
+ "@types/react": "^19.0.12",
32
+ "@types/react-dom": "^19.0.4",
33
+ "@types/uuid": "^10.0.0",
34
+ "autoprefixer": "^10.4.21",
35
+ "bcryptjs": "^3.0.2",
36
+ "class-variance-authority": "^0.7.1",
37
+ "clsx": "^2.1.1",
38
+ "dotenv": "^16.3.1",
39
+ "express": "^4.18.2",
40
+ "express-validator": "^7.2.1",
41
+ "i18next": "^24.2.3",
42
+ "i18next-browser-languagedetector": "^8.0.4",
43
+ "jsonwebtoken": "^9.0.2",
44
+ "lucide-react": "^0.486.0",
45
+ "next": "^15.2.4",
46
+ "postcss": "^8.5.3",
47
+ "react": "^19.1.0",
48
+ "react-dom": "^19.1.0",
49
+ "react-i18next": "^15.4.1",
50
+ "react-router-dom": "^7.5.0",
51
+ "tailwind-merge": "^3.1.0",
52
+ "tailwind-scrollbar-hide": "^2.0.0",
53
+ "tailwindcss": "^4.0.17",
54
+ "uuid": "^11.1.0",
55
+ "zod": "^3.24.2"
56
+ },
57
+ "devDependencies": {
58
+ "@tailwindcss/postcss": "^4.1.3",
59
+ "@types/bcryptjs": "^3.0.0",
60
+ "@types/express": "^4.17.21",
61
+ "@types/jest": "^29.5.5",
62
+ "@types/jsonwebtoken": "^9.0.9",
63
+ "@types/node": "^20.8.2",
64
+ "@typescript-eslint/eslint-plugin": "^6.7.4",
65
+ "@typescript-eslint/parser": "^6.7.4",
66
+ "@vitejs/plugin-react": "^4.2.1",
67
+ "concurrently": "^8.2.2",
68
+ "eslint": "^8.50.0",
69
+ "jest": "^29.7.0",
70
+ "prettier": "^3.0.3",
71
+ "ts-jest": "^29.1.1",
72
+ "ts-node-dev": "^2.0.0",
73
+ "tsx": "^4.7.0",
74
+ "typescript": "^5.2.2",
75
+ "vite": "^5.4.18"
76
+ },
77
+ "scripts": {
78
+ "build": "tsc",
79
+ "start": "node dist/index.js",
80
+ "backend:dev": "tsx watch src/index.ts",
81
+ "lint": "eslint . --ext .ts",
82
+ "format": "prettier --write \"src/**/*.ts\"",
83
+ "test": "jest",
84
+ "frontend:dev": "cd frontend && vite",
85
+ "frontend:build": "cd frontend && vite build",
86
+ "frontend:preview": "cd frontend && vite preview",
87
+ "dev": "concurrently \"pnpm backend:dev\" \"pnpm frontend:dev\"",
88
+ "prepublishOnly": "npm run build && npm run frontend:build",
89
+ "version": "npm run format && git add -A src",
90
+ "postversion": "git push && git push --tags"
91
+ }
92
+ }