@samanhappy/mcphub 0.0.5 → 0.0.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.
Files changed (99) hide show
  1. package/.env.example +2 -0
  2. package/.eslintrc.json +25 -0
  3. package/.github/workflows/build.yml +51 -0
  4. package/.github/workflows/release.yml +19 -0
  5. package/.prettierrc +7 -0
  6. package/Dockerfile +51 -0
  7. package/assets/amap-edit.png +0 -0
  8. package/assets/amap-result.png +0 -0
  9. package/assets/cherry-mcp.png +0 -0
  10. package/assets/cursor-mcp.png +0 -0
  11. package/assets/cursor-query.png +0 -0
  12. package/assets/cursor-tools.png +0 -0
  13. package/assets/dashboard.png +0 -0
  14. package/assets/dashboard.zh.png +0 -0
  15. package/assets/group.png +0 -0
  16. package/assets/group.zh.png +0 -0
  17. package/assets/market.zh.png +0 -0
  18. package/assets/wegroup.jpg +0 -0
  19. package/assets/wegroup.png +0 -0
  20. package/assets/wexin.png +0 -0
  21. package/dist/index.js +1 -0
  22. package/dist/index.js.map +1 -1
  23. package/doc/intro.md +73 -0
  24. package/doc/intro2.md +232 -0
  25. package/entrypoint.sh +10 -0
  26. package/frontend/favicon.ico +0 -0
  27. package/frontend/index.html +13 -0
  28. package/frontend/postcss.config.js +6 -0
  29. package/frontend/src/App.tsx +44 -0
  30. package/frontend/src/components/AddGroupForm.tsx +132 -0
  31. package/frontend/src/components/AddServerForm.tsx +90 -0
  32. package/frontend/src/components/ChangePasswordForm.tsx +158 -0
  33. package/frontend/src/components/EditGroupForm.tsx +149 -0
  34. package/frontend/src/components/EditServerForm.tsx +76 -0
  35. package/frontend/src/components/GroupCard.tsx +143 -0
  36. package/frontend/src/components/MarketServerCard.tsx +153 -0
  37. package/frontend/src/components/MarketServerDetail.tsx +297 -0
  38. package/frontend/src/components/ProtectedRoute.tsx +27 -0
  39. package/frontend/src/components/ServerCard.tsx +230 -0
  40. package/frontend/src/components/ServerForm.tsx +276 -0
  41. package/frontend/src/components/icons/LucideIcons.tsx +14 -0
  42. package/frontend/src/components/layout/Content.tsx +17 -0
  43. package/frontend/src/components/layout/Header.tsx +61 -0
  44. package/frontend/src/components/layout/Sidebar.tsx +98 -0
  45. package/frontend/src/components/ui/Badge.tsx +33 -0
  46. package/frontend/src/components/ui/Button.tsx +0 -0
  47. package/frontend/src/components/ui/DeleteDialog.tsx +48 -0
  48. package/frontend/src/components/ui/Pagination.tsx +128 -0
  49. package/frontend/src/components/ui/Toast.tsx +96 -0
  50. package/frontend/src/components/ui/ToggleGroup.tsx +134 -0
  51. package/frontend/src/components/ui/ToolCard.tsx +38 -0
  52. package/frontend/src/contexts/AuthContext.tsx +159 -0
  53. package/frontend/src/contexts/ToastContext.tsx +60 -0
  54. package/frontend/src/hooks/useGroupData.ts +232 -0
  55. package/frontend/src/hooks/useMarketData.ts +410 -0
  56. package/frontend/src/hooks/useServerData.ts +306 -0
  57. package/frontend/src/hooks/useSettingsData.ts +131 -0
  58. package/frontend/src/i18n.ts +42 -0
  59. package/frontend/src/index.css +20 -0
  60. package/frontend/src/layouts/MainLayout.tsx +33 -0
  61. package/frontend/src/locales/en.json +214 -0
  62. package/frontend/src/locales/zh.json +214 -0
  63. package/frontend/src/main.tsx +12 -0
  64. package/frontend/src/pages/Dashboard.tsx +206 -0
  65. package/frontend/src/pages/GroupsPage.tsx +116 -0
  66. package/frontend/src/pages/LoginPage.tsx +104 -0
  67. package/frontend/src/pages/MarketPage.tsx +356 -0
  68. package/frontend/src/pages/ServersPage.tsx +144 -0
  69. package/frontend/src/pages/SettingsPage.tsx +149 -0
  70. package/frontend/src/services/authService.ts +141 -0
  71. package/frontend/src/types/index.ts +160 -0
  72. package/frontend/src/utils/cn.ts +10 -0
  73. package/frontend/tsconfig.json +31 -0
  74. package/frontend/tsconfig.node.json +10 -0
  75. package/frontend/vite.config.ts +26 -0
  76. package/googled76ca578b6543fbc.html +1 -0
  77. package/jest.config.js +10 -0
  78. package/mcp_settings.json +45 -0
  79. package/package.json +4 -16
  80. package/servers.json +74722 -0
  81. package/src/config/index.ts +46 -0
  82. package/src/controllers/authController.ts +179 -0
  83. package/src/controllers/groupController.ts +341 -0
  84. package/src/controllers/marketController.ts +154 -0
  85. package/src/controllers/serverController.ts +303 -0
  86. package/src/index.ts +18 -0
  87. package/src/middlewares/auth.ts +28 -0
  88. package/src/middlewares/index.ts +43 -0
  89. package/src/models/User.ts +103 -0
  90. package/src/routes/index.ts +96 -0
  91. package/src/server.ts +72 -0
  92. package/src/services/groupService.ts +232 -0
  93. package/src/services/marketService.ts +116 -0
  94. package/src/services/mcpService.ts +385 -0
  95. package/src/services/sseService.ts +119 -0
  96. package/src/types/index.ts +129 -0
  97. package/src/utils/migration.ts +52 -0
  98. package/tsconfig.json +17 -0
  99. package/bin/cli.js +0 -43
@@ -0,0 +1,46 @@
1
+ import dotenv from 'dotenv';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { McpSettings } from '../types/index.js';
5
+
6
+ dotenv.config();
7
+
8
+ const defaultConfig = {
9
+ port: process.env.PORT || 3000,
10
+ initTimeout: process.env.INIT_TIMEOUT || 300000,
11
+ timeout: process.env.REQUEST_TIMEOUT || 60000,
12
+ mcpHubName: 'mcphub',
13
+ mcpHubVersion: '0.0.1',
14
+ };
15
+
16
+ export const getSettingsPath = (): string => {
17
+ return path.resolve(process.cwd(), 'mcp_settings.json');
18
+ };
19
+
20
+ export const loadSettings = (): McpSettings => {
21
+ const settingsPath = getSettingsPath();
22
+ try {
23
+ const settingsData = fs.readFileSync(settingsPath, 'utf8');
24
+ return JSON.parse(settingsData);
25
+ } catch (error) {
26
+ console.error(`Failed to load settings from ${settingsPath}:`, error);
27
+ return { mcpServers: {}, users: [] };
28
+ }
29
+ };
30
+
31
+ export const saveSettings = (settings: McpSettings): boolean => {
32
+ const settingsPath = getSettingsPath();
33
+ try {
34
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
35
+ return true;
36
+ } catch (error) {
37
+ console.error(`Failed to save settings to ${settingsPath}:`, error);
38
+ return false;
39
+ }
40
+ };
41
+
42
+ export const expandEnvVars = (value: string): string => {
43
+ return value.replace(/\$\{([^}]+)\}/g, (_, key) => process.env[key] || '');
44
+ };
45
+
46
+ export default defaultConfig;
@@ -0,0 +1,179 @@
1
+ import { Request, Response } from 'express';
2
+ import jwt from 'jsonwebtoken';
3
+ import { validationResult } from 'express-validator';
4
+ import { findUserByUsername, verifyPassword, createUser, updateUserPassword } from '../models/User.js';
5
+
6
+ // Default secret key - in production, use an environment variable
7
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-this';
8
+ const TOKEN_EXPIRY = '24h';
9
+
10
+ // Login user
11
+ export const login = async (req: Request, res: Response): Promise<void> => {
12
+ // Validate request
13
+ const errors = validationResult(req);
14
+ if (!errors.isEmpty()) {
15
+ res.status(400).json({ success: false, errors: errors.array() });
16
+ return;
17
+ }
18
+
19
+ const { username, password } = req.body;
20
+
21
+ try {
22
+ // Find user by username
23
+ const user = findUserByUsername(username);
24
+
25
+ if (!user) {
26
+ res.status(401).json({ success: false, message: 'Invalid credentials' });
27
+ return;
28
+ }
29
+
30
+ // Verify password
31
+ const isPasswordValid = await verifyPassword(password, user.password);
32
+
33
+ if (!isPasswordValid) {
34
+ res.status(401).json({ success: false, message: 'Invalid credentials' });
35
+ return;
36
+ }
37
+
38
+ // Generate JWT token
39
+ const payload = {
40
+ user: {
41
+ username: user.username,
42
+ isAdmin: user.isAdmin || false
43
+ }
44
+ };
45
+
46
+ jwt.sign(
47
+ payload,
48
+ JWT_SECRET,
49
+ { expiresIn: TOKEN_EXPIRY },
50
+ (err, token) => {
51
+ if (err) throw err;
52
+ res.json({
53
+ success: true,
54
+ token,
55
+ user: {
56
+ username: user.username,
57
+ isAdmin: user.isAdmin
58
+ }
59
+ });
60
+ }
61
+ );
62
+ } catch (error) {
63
+ console.error('Login error:', error);
64
+ res.status(500).json({ success: false, message: 'Server error' });
65
+ }
66
+ };
67
+
68
+ // Register new user
69
+ export const register = async (req: Request, res: Response): Promise<void> => {
70
+ // Validate request
71
+ const errors = validationResult(req);
72
+ if (!errors.isEmpty()) {
73
+ res.status(400).json({ success: false, errors: errors.array() });
74
+ return;
75
+ }
76
+
77
+ const { username, password, isAdmin } = req.body;
78
+
79
+ try {
80
+ // Create new user
81
+ const newUser = await createUser({ username, password, isAdmin });
82
+
83
+ if (!newUser) {
84
+ res.status(400).json({ success: false, message: 'User already exists' });
85
+ return;
86
+ }
87
+
88
+ // Generate JWT token
89
+ const payload = {
90
+ user: {
91
+ username: newUser.username,
92
+ isAdmin: newUser.isAdmin || false
93
+ }
94
+ };
95
+
96
+ jwt.sign(
97
+ payload,
98
+ JWT_SECRET,
99
+ { expiresIn: TOKEN_EXPIRY },
100
+ (err, token) => {
101
+ if (err) throw err;
102
+ res.json({
103
+ success: true,
104
+ token,
105
+ user: {
106
+ username: newUser.username,
107
+ isAdmin: newUser.isAdmin
108
+ }
109
+ });
110
+ }
111
+ );
112
+ } catch (error) {
113
+ console.error('Registration error:', error);
114
+ res.status(500).json({ success: false, message: 'Server error' });
115
+ }
116
+ };
117
+
118
+ // Get current user
119
+ export const getCurrentUser = (req: Request, res: Response): void => {
120
+ try {
121
+ // User is already attached to request by auth middleware
122
+ const user = (req as any).user;
123
+
124
+ res.json({
125
+ success: true,
126
+ user: {
127
+ username: user.username,
128
+ isAdmin: user.isAdmin
129
+ }
130
+ });
131
+ } catch (error) {
132
+ console.error('Get current user error:', error);
133
+ res.status(500).json({ success: false, message: 'Server error' });
134
+ }
135
+ };
136
+
137
+ // Change password
138
+ export const changePassword = async (req: Request, res: Response): Promise<void> => {
139
+ // Validate request
140
+ const errors = validationResult(req);
141
+ if (!errors.isEmpty()) {
142
+ res.status(400).json({ success: false, errors: errors.array() });
143
+ return;
144
+ }
145
+
146
+ const { currentPassword, newPassword } = req.body;
147
+ const username = (req as any).user.username;
148
+
149
+ try {
150
+ // Find user by username
151
+ const user = findUserByUsername(username);
152
+
153
+ if (!user) {
154
+ res.status(404).json({ success: false, message: 'User not found' });
155
+ return;
156
+ }
157
+
158
+ // Verify current password
159
+ const isPasswordValid = await verifyPassword(currentPassword, user.password);
160
+
161
+ if (!isPasswordValid) {
162
+ res.status(401).json({ success: false, message: 'Current password is incorrect' });
163
+ return;
164
+ }
165
+
166
+ // Update the password
167
+ const updated = await updateUserPassword(username, newPassword);
168
+
169
+ if (!updated) {
170
+ res.status(500).json({ success: false, message: 'Failed to update password' });
171
+ return;
172
+ }
173
+
174
+ res.json({ success: true, message: 'Password updated successfully' });
175
+ } catch (error) {
176
+ console.error('Change password error:', error);
177
+ res.status(500).json({ success: false, message: 'Server error' });
178
+ }
179
+ };
@@ -0,0 +1,341 @@
1
+ import { Request, Response } from 'express';
2
+ import { ApiResponse } from '../types/index.js';
3
+ import {
4
+ getAllGroups,
5
+ getGroupByIdOrName,
6
+ createGroup,
7
+ updateGroup,
8
+ updateGroupServers,
9
+ deleteGroup,
10
+ addServerToGroup,
11
+ removeServerFromGroup,
12
+ getServersInGroup
13
+ } from '../services/groupService.js';
14
+
15
+ // Get all groups
16
+ export const getGroups = (_: Request, res: Response): void => {
17
+ try {
18
+ const groups = getAllGroups();
19
+ const response: ApiResponse = {
20
+ success: true,
21
+ data: groups,
22
+ };
23
+ res.json(response);
24
+ } catch (error) {
25
+ res.status(500).json({
26
+ success: false,
27
+ message: 'Failed to get groups information',
28
+ });
29
+ }
30
+ };
31
+
32
+ // Get a specific group by ID
33
+ export const getGroup = (req: Request, res: Response): void => {
34
+ try {
35
+ const { id } = req.params;
36
+ if (!id) {
37
+ res.status(400).json({
38
+ success: false,
39
+ message: 'Group ID is required',
40
+ });
41
+ return;
42
+ }
43
+
44
+ const group = getGroupByIdOrName(id);
45
+ if (!group) {
46
+ res.status(404).json({
47
+ success: false,
48
+ message: 'Group not found',
49
+ });
50
+ return;
51
+ }
52
+
53
+ const response: ApiResponse = {
54
+ success: true,
55
+ data: group,
56
+ };
57
+ res.json(response);
58
+ } catch (error) {
59
+ res.status(500).json({
60
+ success: false,
61
+ message: 'Failed to get group information',
62
+ });
63
+ }
64
+ };
65
+
66
+ // Create a new group
67
+ export const createNewGroup = (req: Request, res: Response): void => {
68
+ try {
69
+ const { name, description, servers } = req.body;
70
+ if (!name) {
71
+ res.status(400).json({
72
+ success: false,
73
+ message: 'Group name is required',
74
+ });
75
+ return;
76
+ }
77
+
78
+ const serverList = Array.isArray(servers) ? servers : [];
79
+ const newGroup = createGroup(name, description, serverList);
80
+ if (!newGroup) {
81
+ res.status(400).json({
82
+ success: false,
83
+ message: 'Failed to create group or group name already exists',
84
+ });
85
+ return;
86
+ }
87
+
88
+ const response: ApiResponse = {
89
+ success: true,
90
+ data: newGroup,
91
+ message: 'Group created successfully',
92
+ };
93
+ res.status(201).json(response);
94
+ } catch (error) {
95
+ res.status(500).json({
96
+ success: false,
97
+ message: 'Internal server error',
98
+ });
99
+ }
100
+ };
101
+
102
+ // Update an existing group
103
+ export const updateExistingGroup = (req: Request, res: Response): void => {
104
+ try {
105
+ const { id } = req.params;
106
+ const { name, description, servers } = req.body;
107
+ if (!id) {
108
+ res.status(400).json({
109
+ success: false,
110
+ message: 'Group ID is required',
111
+ });
112
+ return;
113
+ }
114
+
115
+ // Allow updating servers along with other fields
116
+ const updateData: any = {};
117
+ if (name !== undefined) updateData.name = name;
118
+ if (description !== undefined) updateData.description = description;
119
+ if (servers !== undefined) updateData.servers = servers;
120
+
121
+ if (Object.keys(updateData).length === 0) {
122
+ res.status(400).json({
123
+ success: false,
124
+ message: 'At least one field (name, description, or servers) is required to update',
125
+ });
126
+ return;
127
+ }
128
+
129
+ const updatedGroup = updateGroup(id, updateData);
130
+ if (!updatedGroup) {
131
+ res.status(404).json({
132
+ success: false,
133
+ message: 'Group not found or name already exists',
134
+ });
135
+ return;
136
+ }
137
+
138
+ const response: ApiResponse = {
139
+ success: true,
140
+ data: updatedGroup,
141
+ message: 'Group updated successfully',
142
+ };
143
+ res.json(response);
144
+ } catch (error) {
145
+ res.status(500).json({
146
+ success: false,
147
+ message: 'Internal server error',
148
+ });
149
+ }
150
+ };
151
+
152
+ // Update servers in a group (batch update)
153
+ export const updateGroupServersBatch = (req: Request, res: Response): void => {
154
+ try {
155
+ const { id } = req.params;
156
+ const { servers } = req.body;
157
+
158
+ if (!id) {
159
+ res.status(400).json({
160
+ success: false,
161
+ message: 'Group ID is required',
162
+ });
163
+ return;
164
+ }
165
+
166
+ if (!Array.isArray(servers)) {
167
+ res.status(400).json({
168
+ success: false,
169
+ message: 'Servers must be an array of server names',
170
+ });
171
+ return;
172
+ }
173
+
174
+ const updatedGroup = updateGroupServers(id, servers);
175
+ if (!updatedGroup) {
176
+ res.status(404).json({
177
+ success: false,
178
+ message: 'Group not found',
179
+ });
180
+ return;
181
+ }
182
+
183
+ const response: ApiResponse = {
184
+ success: true,
185
+ data: updatedGroup,
186
+ message: 'Group servers updated successfully',
187
+ };
188
+ res.json(response);
189
+ } catch (error) {
190
+ res.status(500).json({
191
+ success: false,
192
+ message: 'Internal server error',
193
+ });
194
+ }
195
+ };
196
+
197
+ // Delete a group
198
+ export const deleteExistingGroup = (req: Request, res: Response): void => {
199
+ try {
200
+ const { id } = req.params;
201
+ if (!id) {
202
+ res.status(400).json({
203
+ success: false,
204
+ message: 'Group ID is required',
205
+ });
206
+ return;
207
+ }
208
+
209
+ const success = deleteGroup(id);
210
+ if (!success) {
211
+ res.status(404).json({
212
+ success: false,
213
+ message: 'Group not found or failed to delete',
214
+ });
215
+ return;
216
+ }
217
+
218
+ res.json({
219
+ success: true,
220
+ message: 'Group deleted successfully',
221
+ });
222
+ } catch (error) {
223
+ res.status(500).json({
224
+ success: false,
225
+ message: 'Internal server error',
226
+ });
227
+ }
228
+ };
229
+
230
+ // Add server to a group
231
+ export const addServerToExistingGroup = (req: Request, res: Response): void => {
232
+ try {
233
+ const { id } = req.params;
234
+ const { serverName } = req.body;
235
+ if (!id) {
236
+ res.status(400).json({
237
+ success: false,
238
+ message: 'Group ID is required',
239
+ });
240
+ return;
241
+ }
242
+
243
+ if (!serverName) {
244
+ res.status(400).json({
245
+ success: false,
246
+ message: 'Server name is required',
247
+ });
248
+ return;
249
+ }
250
+
251
+ const updatedGroup = addServerToGroup(id, serverName);
252
+ if (!updatedGroup) {
253
+ res.status(404).json({
254
+ success: false,
255
+ message: 'Group or server not found',
256
+ });
257
+ return;
258
+ }
259
+
260
+ const response: ApiResponse = {
261
+ success: true,
262
+ data: updatedGroup,
263
+ message: 'Server added to group successfully',
264
+ };
265
+ res.json(response);
266
+ } catch (error) {
267
+ res.status(500).json({
268
+ success: false,
269
+ message: 'Internal server error',
270
+ });
271
+ }
272
+ };
273
+
274
+ // Remove server from a group
275
+ export const removeServerFromExistingGroup = (req: Request, res: Response): void => {
276
+ try {
277
+ const { id, serverName } = req.params;
278
+ if (!id || !serverName) {
279
+ res.status(400).json({
280
+ success: false,
281
+ message: 'Group ID and server name are required',
282
+ });
283
+ return;
284
+ }
285
+
286
+ const updatedGroup = removeServerFromGroup(id, serverName);
287
+ if (!updatedGroup) {
288
+ res.status(404).json({
289
+ success: false,
290
+ message: 'Group not found',
291
+ });
292
+ return;
293
+ }
294
+
295
+ const response: ApiResponse = {
296
+ success: true,
297
+ data: updatedGroup,
298
+ message: 'Server removed from group successfully',
299
+ };
300
+ res.json(response);
301
+ } catch (error) {
302
+ res.status(500).json({
303
+ success: false,
304
+ message: 'Internal server error',
305
+ });
306
+ }
307
+ };
308
+
309
+ // Get servers in a group
310
+ export const getGroupServers = (req: Request, res: Response): void => {
311
+ try {
312
+ const { id } = req.params;
313
+ if (!id) {
314
+ res.status(400).json({
315
+ success: false,
316
+ message: 'Group ID is required',
317
+ });
318
+ return;
319
+ }
320
+
321
+ const group = getGroupByIdOrName(id);
322
+ if (!group) {
323
+ res.status(404).json({
324
+ success: false,
325
+ message: 'Group not found',
326
+ });
327
+ return;
328
+ }
329
+
330
+ const response: ApiResponse = {
331
+ success: true,
332
+ data: group.servers,
333
+ };
334
+ res.json(response);
335
+ } catch (error) {
336
+ res.status(500).json({
337
+ success: false,
338
+ message: 'Failed to get group servers',
339
+ });
340
+ }
341
+ };
@@ -0,0 +1,154 @@
1
+ import { Request, Response } from 'express';
2
+ import { ApiResponse } from '../types/index.js';
3
+ import {
4
+ getMarketServers,
5
+ getMarketServerByName,
6
+ getMarketCategories,
7
+ getMarketTags,
8
+ searchMarketServers,
9
+ filterMarketServersByCategory,
10
+ filterMarketServersByTag
11
+ } from '../services/marketService.js';
12
+
13
+ // Get all market servers
14
+ export const getAllMarketServers = (_: Request, res: Response): void => {
15
+ try {
16
+ const marketServers = Object.values(getMarketServers());
17
+ const response: ApiResponse = {
18
+ success: true,
19
+ data: marketServers,
20
+ };
21
+ res.json(response);
22
+ } catch (error) {
23
+ res.status(500).json({
24
+ success: false,
25
+ message: 'Failed to get market servers information',
26
+ });
27
+ }
28
+ };
29
+
30
+ // Get a specific market server by name
31
+ export const getMarketServer = (req: Request, res: Response): void => {
32
+ try {
33
+ const { name } = req.params;
34
+ if (!name) {
35
+ res.status(400).json({
36
+ success: false,
37
+ message: 'Server name is required',
38
+ });
39
+ return;
40
+ }
41
+
42
+ const server = getMarketServerByName(name);
43
+ if (!server) {
44
+ res.status(404).json({
45
+ success: false,
46
+ message: 'Market server not found',
47
+ });
48
+ return;
49
+ }
50
+
51
+ const response: ApiResponse = {
52
+ success: true,
53
+ data: server,
54
+ };
55
+ res.json(response);
56
+ } catch (error) {
57
+ res.status(500).json({
58
+ success: false,
59
+ message: 'Failed to get market server information',
60
+ });
61
+ }
62
+ };
63
+
64
+ // Get all market categories
65
+ export const getAllMarketCategories = (_: Request, res: Response): void => {
66
+ try {
67
+ const categories = getMarketCategories();
68
+ const response: ApiResponse = {
69
+ success: true,
70
+ data: categories,
71
+ };
72
+ res.json(response);
73
+ } catch (error) {
74
+ res.status(500).json({
75
+ success: false,
76
+ message: 'Failed to get market categories',
77
+ });
78
+ }
79
+ };
80
+
81
+ // Get all market tags
82
+ export const getAllMarketTags = (_: Request, res: Response): void => {
83
+ try {
84
+ const tags = getMarketTags();
85
+ const response: ApiResponse = {
86
+ success: true,
87
+ data: tags,
88
+ };
89
+ res.json(response);
90
+ } catch (error) {
91
+ res.status(500).json({
92
+ success: false,
93
+ message: 'Failed to get market tags',
94
+ });
95
+ }
96
+ };
97
+
98
+ // Search market servers
99
+ export const searchMarketServersByQuery = (req: Request, res: Response): void => {
100
+ try {
101
+ const { query } = req.query;
102
+ const searchQuery = typeof query === 'string' ? query : '';
103
+
104
+ const servers = searchMarketServers(searchQuery);
105
+ const response: ApiResponse = {
106
+ success: true,
107
+ data: servers,
108
+ };
109
+ res.json(response);
110
+ } catch (error) {
111
+ res.status(500).json({
112
+ success: false,
113
+ message: 'Failed to search market servers',
114
+ });
115
+ }
116
+ };
117
+
118
+ // Filter market servers by category
119
+ export const getMarketServersByCategory = (req: Request, res: Response): void => {
120
+ try {
121
+ const { category } = req.params;
122
+
123
+ const servers = filterMarketServersByCategory(category);
124
+ const response: ApiResponse = {
125
+ success: true,
126
+ data: servers,
127
+ };
128
+ res.json(response);
129
+ } catch (error) {
130
+ res.status(500).json({
131
+ success: false,
132
+ message: 'Failed to filter market servers by category',
133
+ });
134
+ }
135
+ };
136
+
137
+ // Filter market servers by tag
138
+ export const getMarketServersByTag = (req: Request, res: Response): void => {
139
+ try {
140
+ const { tag } = req.params;
141
+
142
+ const servers = filterMarketServersByTag(tag);
143
+ const response: ApiResponse = {
144
+ success: true,
145
+ data: servers,
146
+ };
147
+ res.json(response);
148
+ } catch (error) {
149
+ res.status(500).json({
150
+ success: false,
151
+ message: 'Failed to filter market servers by tag',
152
+ });
153
+ }
154
+ };