@mcp-shark/mcp-shark 1.5.4 → 1.5.6

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 (188) hide show
  1. package/README.md +32 -96
  2. package/bin/mcp-shark.js +1 -1
  3. package/core/configs/codex.js +68 -0
  4. package/core/configs/environment.js +51 -0
  5. package/{lib/common → core}/configs/index.js +16 -1
  6. package/core/constants/Defaults.js +15 -0
  7. package/core/constants/HttpStatus.js +14 -0
  8. package/core/constants/Server.js +20 -0
  9. package/core/constants/StatusCodes.js +25 -0
  10. package/core/constants/index.js +7 -0
  11. package/core/container/DependencyContainer.js +179 -0
  12. package/core/db/init.js +33 -0
  13. package/core/index.js +10 -0
  14. package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
  15. package/core/libraries/LoggerLibrary.js +91 -0
  16. package/core/libraries/SerializationLibrary.js +32 -0
  17. package/core/libraries/bootstrap-logger.js +19 -0
  18. package/core/libraries/errors/ApplicationError.js +97 -0
  19. package/core/libraries/index.js +17 -0
  20. package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
  21. package/core/mcp-server/index.js +192 -0
  22. package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
  23. package/core/mcp-server/server/external/config.js +75 -0
  24. package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
  25. package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
  26. package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
  27. package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
  28. package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
  29. package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
  30. package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
  31. package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
  32. package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
  33. package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
  34. package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
  35. package/core/mcp-server/server/internal/run.js +53 -0
  36. package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
  37. package/core/models/ConversationFilters.js +31 -0
  38. package/core/models/ExportFormat.js +8 -0
  39. package/core/models/RequestFilters.js +43 -0
  40. package/core/models/SessionFilters.js +23 -0
  41. package/core/models/index.js +8 -0
  42. package/core/repositories/AuditRepository.js +233 -0
  43. package/core/repositories/ConversationRepository.js +182 -0
  44. package/core/repositories/PacketRepository.js +237 -0
  45. package/core/repositories/SchemaRepository.js +107 -0
  46. package/core/repositories/SessionRepository.js +59 -0
  47. package/core/repositories/StatisticsRepository.js +54 -0
  48. package/core/repositories/index.js +10 -0
  49. package/core/services/AuditService.js +144 -0
  50. package/core/services/BackupService.js +222 -0
  51. package/core/services/ConfigDetectionService.js +89 -0
  52. package/core/services/ConfigFileService.js +210 -0
  53. package/core/services/ConfigPatchingService.js +137 -0
  54. package/core/services/ConfigService.js +250 -0
  55. package/core/services/ConfigTransformService.js +178 -0
  56. package/core/services/ConversationService.js +19 -0
  57. package/core/services/ExportService.js +117 -0
  58. package/core/services/LogService.js +64 -0
  59. package/core/services/McpClientService.js +235 -0
  60. package/core/services/McpDiscoveryService.js +107 -0
  61. package/core/services/RequestService.js +56 -0
  62. package/core/services/ScanCacheService.js +242 -0
  63. package/core/services/ScanService.js +167 -0
  64. package/core/services/ServerManagementService.js +206 -0
  65. package/core/services/SessionService.js +34 -0
  66. package/core/services/SettingsService.js +163 -0
  67. package/core/services/StatisticsService.js +64 -0
  68. package/core/services/TokenService.js +94 -0
  69. package/core/services/index.js +25 -0
  70. package/core/services/parsers/ConfigParserFactory.js +113 -0
  71. package/core/services/parsers/JsonConfigParser.js +66 -0
  72. package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
  73. package/core/services/parsers/TomlConfigParser.js +87 -0
  74. package/core/services/parsers/index.js +4 -0
  75. package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
  76. package/core/utils/validation.js +77 -0
  77. package/package.json +14 -11
  78. package/ui/dist/assets/index-CArYxKxS.js +35 -0
  79. package/ui/dist/index.html +1 -1
  80. package/ui/server/controllers/BackupController.js +129 -0
  81. package/ui/server/controllers/ConfigController.js +92 -0
  82. package/ui/server/controllers/ConversationController.js +41 -0
  83. package/ui/server/controllers/LogController.js +44 -0
  84. package/ui/server/controllers/McpClientController.js +60 -0
  85. package/ui/server/controllers/McpDiscoveryController.js +44 -0
  86. package/ui/server/controllers/RequestController.js +129 -0
  87. package/ui/server/controllers/ScanController.js +122 -0
  88. package/ui/server/controllers/ServerManagementController.js +154 -0
  89. package/ui/server/controllers/SessionController.js +57 -0
  90. package/ui/server/controllers/SettingsController.js +24 -0
  91. package/ui/server/controllers/StatisticsController.js +54 -0
  92. package/ui/server/controllers/TokenController.js +58 -0
  93. package/ui/server/controllers/index.js +17 -0
  94. package/ui/server/routes/backups/index.js +15 -9
  95. package/ui/server/routes/composite/index.js +63 -32
  96. package/ui/server/routes/composite/servers.js +20 -15
  97. package/ui/server/routes/config.js +13 -172
  98. package/ui/server/routes/conversations.js +9 -19
  99. package/ui/server/routes/help.js +4 -3
  100. package/ui/server/routes/logs.js +14 -26
  101. package/ui/server/routes/playground.js +11 -174
  102. package/ui/server/routes/requests.js +12 -232
  103. package/ui/server/routes/sessions.js +10 -21
  104. package/ui/server/routes/settings.js +10 -192
  105. package/ui/server/routes/smartscan.js +26 -15
  106. package/ui/server/routes/statistics.js +8 -79
  107. package/ui/server/setup.js +163 -0
  108. package/ui/server/swagger/paths/backups.js +151 -0
  109. package/ui/server/swagger/paths/components.js +76 -0
  110. package/ui/server/swagger/paths/config.js +117 -0
  111. package/ui/server/swagger/paths/conversations.js +29 -0
  112. package/ui/server/swagger/paths/help.js +82 -0
  113. package/ui/server/swagger/paths/logs.js +87 -0
  114. package/ui/server/swagger/paths/playground.js +49 -0
  115. package/ui/server/swagger/paths/requests.js +178 -0
  116. package/ui/server/swagger/paths/serverManagement.js +205 -0
  117. package/ui/server/swagger/paths/sessions.js +61 -0
  118. package/ui/server/swagger/paths/settings.js +31 -0
  119. package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
  120. package/ui/server/swagger/paths/smartScan/index.js +13 -0
  121. package/ui/server/swagger/paths/smartScan/scans.js +151 -0
  122. package/ui/server/swagger/paths/smartScan/token.js +71 -0
  123. package/ui/server/swagger/paths/statistics.js +40 -0
  124. package/ui/server/swagger/paths.js +38 -0
  125. package/ui/server/swagger/swagger.js +37 -0
  126. package/ui/server/utils/cleanup.js +99 -0
  127. package/ui/server/utils/config.js +18 -96
  128. package/ui/server/utils/errorHandler.js +43 -0
  129. package/ui/server/utils/logger.js +2 -2
  130. package/ui/server/utils/paths.js +27 -30
  131. package/ui/server/utils/port.js +21 -21
  132. package/ui/server/utils/process.js +18 -10
  133. package/ui/server/utils/processState.js +17 -0
  134. package/ui/server/utils/signals.js +34 -0
  135. package/ui/server/websocket/broadcast.js +33 -0
  136. package/ui/server/websocket/handler.js +52 -0
  137. package/ui/server.js +51 -230
  138. package/ui/src/App.jsx +2 -0
  139. package/ui/src/CompositeSetup.jsx +23 -9
  140. package/ui/src/PacketFilters.jsx +17 -3
  141. package/ui/src/components/AlertModal.jsx +116 -0
  142. package/ui/src/components/App/ApiDocsButton.jsx +57 -0
  143. package/ui/src/components/App/useAppState.js +43 -1
  144. package/ui/src/components/BackupList.jsx +27 -3
  145. package/ui/src/utils/requestPairing.js +35 -36
  146. package/ui/src/utils/requestUtils.js +1 -0
  147. package/lib/common/db/init.js +0 -132
  148. package/lib/common/db/logger.js +0 -349
  149. package/lib/common/db/query.js +0 -403
  150. package/lib/common/logger.js +0 -90
  151. package/mcp-server/index.js +0 -138
  152. package/mcp-server/lib/server/external/config.js +0 -57
  153. package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
  154. package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
  155. package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
  156. package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
  157. package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
  158. package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
  159. package/mcp-server/lib/server/internal/run.js +0 -37
  160. package/mcp-server/mcp-shark.js +0 -22
  161. package/ui/dist/assets/index-CFHeMNwd.js +0 -35
  162. package/ui/server/routes/backups/deleteBackup.js +0 -54
  163. package/ui/server/routes/backups/listBackups.js +0 -75
  164. package/ui/server/routes/backups/restoreBackup.js +0 -83
  165. package/ui/server/routes/backups/viewBackup.js +0 -47
  166. package/ui/server/routes/composite/setup.js +0 -129
  167. package/ui/server/routes/composite/status.js +0 -7
  168. package/ui/server/routes/composite/stop.js +0 -39
  169. package/ui/server/routes/composite/utils.js +0 -45
  170. package/ui/server/routes/smartscan/discover.js +0 -118
  171. package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
  172. package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
  173. package/ui/server/routes/smartscan/scans/createScan.js +0 -43
  174. package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
  175. package/ui/server/routes/smartscan/scans/getScan.js +0 -42
  176. package/ui/server/routes/smartscan/scans/listScans.js +0 -25
  177. package/ui/server/routes/smartscan/scans.js +0 -13
  178. package/ui/server/routes/smartscan/token.js +0 -57
  179. package/ui/server/utils/config-update.js +0 -240
  180. package/ui/server/utils/scan-cache/all-results.js +0 -197
  181. package/ui/server/utils/scan-cache/file-operations.js +0 -107
  182. package/ui/server/utils/scan-cache/hash.js +0 -47
  183. package/ui/server/utils/scan-cache/server-operations.js +0 -85
  184. package/ui/server/utils/scan-cache.js +0 -12
  185. package/ui/server/utils/smartscan-token.js +0 -43
  186. /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
  187. /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
  188. /package/{mcp-server/lib → core/mcp-server}/server/internal/session.js +0 -0
@@ -1,179 +1,20 @@
1
- import * as fs from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import * as path from 'node:path';
4
- import { extractServices } from '../utils/config.js';
5
- import { createBackupRoutes } from './backups/index.js';
1
+ import { ConfigController } from '#ui/server/controllers/index.js';
6
2
 
7
- function readFileContent(filePath) {
8
- const resolvedFilePath = filePath.startsWith('~')
9
- ? path.join(homedir(), filePath.slice(1))
10
- : filePath;
3
+ /**
4
+ * Create config routes
5
+ * Routes delegate to ConfigController which calls ConfigService
6
+ */
7
+ export function createConfigRoutes(container) {
8
+ const configService = container.getService('config');
9
+ const logger = container.getLibrary('logger');
10
+ const configController = new ConfigController(configService, logger);
11
11
 
12
- if (!fs.existsSync(resolvedFilePath)) {
13
- return null;
14
- }
15
- return fs.readFileSync(resolvedFilePath, 'utf-8');
16
- }
17
-
18
- function parseJsonSafely(content) {
19
- try {
20
- return { config: JSON.parse(content), error: null };
21
- } catch (e) {
22
- return { config: null, error: e };
23
- }
24
- }
25
-
26
- function tryParseJson(content) {
27
- try {
28
- return JSON.parse(content);
29
- } catch (_e) {
30
- return null;
31
- }
32
- }
33
-
34
- export function createConfigRoutes() {
35
12
  const router = {};
36
13
 
37
- router.extractServices = (req, res) => {
38
- try {
39
- const { filePath, fileContent } = req.body;
40
-
41
- if (!filePath && !fileContent) {
42
- return res.status(400).json({ error: 'Either filePath or fileContent is required' });
43
- }
44
-
45
- const content = fileContent ? fileContent : readFileContent(filePath);
46
-
47
- if (!content) {
48
- const resolvedFilePath = filePath.startsWith('~')
49
- ? path.join(homedir(), filePath.slice(1))
50
- : filePath;
51
- return res.status(404).json({ error: 'File not found', path: resolvedFilePath });
52
- }
53
-
54
- const parseResult = parseJsonSafely(content);
55
-
56
- if (!parseResult.config) {
57
- return res.status(400).json({
58
- error: 'Invalid JSON file',
59
- details: parseResult.error ? parseResult.error.message : 'Failed to parse JSON',
60
- });
61
- }
62
-
63
- const config = parseResult.config;
64
-
65
- const services = extractServices(config);
66
- res.json({ success: true, services });
67
- } catch (error) {
68
- res.status(500).json({ error: 'Failed to extract services', details: error.message });
69
- }
70
- };
71
-
72
- router.readConfig = (req, res) => {
73
- try {
74
- const { filePath } = req.query;
75
-
76
- if (!filePath) {
77
- return res.status(400).json({ error: 'filePath is required' });
78
- }
79
-
80
- const resolvedPath = filePath.startsWith('~')
81
- ? path.join(homedir(), filePath.slice(1))
82
- : filePath;
83
-
84
- if (!fs.existsSync(resolvedPath)) {
85
- return res.status(404).json({ error: 'File not found', path: resolvedPath });
86
- }
87
-
88
- const content = fs.readFileSync(resolvedPath, 'utf-8');
89
- const parsed = tryParseJson(content);
90
-
91
- res.json({
92
- success: true,
93
- filePath: resolvedPath,
94
- displayPath: resolvedPath.replace(homedir(), '~'),
95
- content: content,
96
- parsed: parsed,
97
- exists: true,
98
- });
99
- } catch (error) {
100
- res.status(500).json({ error: 'Failed to read file', details: error.message });
101
- }
102
- };
103
-
104
- router.detectConfig = (_req, res) => {
105
- const detected = [];
106
- const platform = process.platform;
107
- const homeDir = homedir();
108
-
109
- const cursorPaths = [
110
- path.join(homeDir, '.cursor', 'mcp.json'),
111
- ...(platform === 'win32'
112
- ? [path.join(process.env.USERPROFILE || '', '.cursor', 'mcp.json')]
113
- : []),
114
- ];
115
-
116
- const windsurfPaths = [
117
- path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
118
- ...(platform === 'win32'
119
- ? [path.join(process.env.USERPROFILE || '', '.codeium', 'windsurf', 'mcp_config.json')]
120
- : []),
121
- ];
122
-
123
- for (const cursorPath of cursorPaths) {
124
- if (fs.existsSync(cursorPath)) {
125
- detected.push({
126
- editor: 'Cursor',
127
- path: cursorPath,
128
- displayPath: cursorPath.replace(homeDir, '~'),
129
- exists: true,
130
- });
131
- break;
132
- }
133
- }
134
-
135
- for (const windsurfPath of windsurfPaths) {
136
- if (fs.existsSync(windsurfPath)) {
137
- detected.push({
138
- editor: 'Windsurf',
139
- path: windsurfPath,
140
- displayPath: windsurfPath.replace(homeDir, '~'),
141
- exists: true,
142
- });
143
- break;
144
- }
145
- }
146
-
147
- const defaultPaths = [
148
- {
149
- editor: 'Cursor',
150
- path: path.join(homeDir, '.cursor', 'mcp.json'),
151
- displayPath: '~/.cursor/mcp.json',
152
- exists: fs.existsSync(path.join(homeDir, '.cursor', 'mcp.json')),
153
- },
154
- {
155
- editor: 'Windsurf',
156
- path: path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
157
- displayPath: '~/.codeium/windsurf/mcp_config.json',
158
- exists: fs.existsSync(path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json')),
159
- },
160
- ];
161
-
162
- const result = detected.length > 0 ? detected : defaultPaths;
163
-
164
- res.json({
165
- detected: result,
166
- platform,
167
- homeDir,
168
- });
169
- };
170
-
171
- // Delegate backup routes to separate module
172
- const backupRoutes = createBackupRoutes();
173
- router.listBackups = backupRoutes.listBackups;
174
- router.restoreBackup = backupRoutes.restoreBackup;
175
- router.viewBackup = backupRoutes.viewBackup;
176
- router.deleteBackup = backupRoutes.deleteBackup;
14
+ // Delegate to controller
15
+ router.extractServices = configController.extractServices;
16
+ router.readConfig = configController.readConfig;
17
+ router.detectConfig = configController.detectConfig;
177
18
 
178
19
  return router;
179
20
  }
@@ -1,25 +1,15 @@
1
- import { queryConversations } from '#common/db/query';
2
- import { serializeBigInt } from '../utils/serialization.js';
1
+ import { ConversationController } from '../controllers/ConversationController.js';
2
+
3
+ export function createConversationsRoutes(container) {
4
+ const conversationService = container.getService('conversation');
5
+ const serializationLib = container.getLibrary('serialization');
6
+ const logger = container.getLibrary('logger');
7
+
8
+ const controller = new ConversationController(conversationService, serializationLib, logger);
3
9
 
4
- export function createConversationsRoutes(db) {
5
10
  const router = {};
6
11
 
7
- router.getConversations = (req, res) => {
8
- const limit = Number.parseInt(req.query.limit) || 1000;
9
- const offset = Number.parseInt(req.query.offset) || 0;
10
- const filters = {
11
- sessionId: req.query.sessionId || null,
12
- method: req.query.method || null,
13
- status: req.query.status || null,
14
- jsonrpcId: req.query.jsonrpcId || null,
15
- startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
16
- endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
17
- limit,
18
- offset,
19
- };
20
- const conversations = queryConversations(db, filters);
21
- res.json(serializeBigInt(conversations));
22
- };
12
+ router.getConversations = (req, res) => controller.getConversations(req, res);
23
13
 
24
14
  return router;
25
15
  }
@@ -1,4 +1,5 @@
1
- import { readHelpState, writeHelpState } from '#common/configs';
1
+ import { readHelpState, writeHelpState } from '#core/configs/index.js';
2
+ import { StatusCodes } from '#core/constants/index.js';
2
3
 
3
4
  export function createHelpRoutes() {
4
5
  const router = {};
@@ -21,7 +22,7 @@ export function createHelpRoutes() {
21
22
  if (success) {
22
23
  res.json({ success: true });
23
24
  } else {
24
- res.status(500).json({ error: 'Failed to save help state' });
25
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: 'Failed to save help state' });
25
26
  }
26
27
  };
27
28
 
@@ -35,7 +36,7 @@ export function createHelpRoutes() {
35
36
  if (success) {
36
37
  res.json({ success: true });
37
38
  } else {
38
- res.status(500).json({ error: 'Failed to reset help state' });
39
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: 'Failed to reset help state' });
39
40
  }
40
41
  };
41
42
 
@@ -1,32 +1,20 @@
1
- export function createLogsRoutes(mcpSharkLogs, _broadcastLogUpdate) {
2
- const router = {};
3
-
4
- router.getLogs = (req, res) => {
5
- const limit = Number.parseInt(req.query.limit) || 1000;
6
- const offset = Number.parseInt(req.query.offset) || 0;
7
- const logs = [...mcpSharkLogs].reverse().slice(offset, offset + limit);
8
- res.json(logs);
9
- };
1
+ import { LogController } from '#ui/server/controllers/index.js';
10
2
 
11
- router.clearLogs = (_req, res) => {
12
- mcpSharkLogs.length = 0;
13
- res.json({ success: true, message: 'Logs cleared' });
14
- };
3
+ /**
4
+ * Create logs routes
5
+ * Routes delegate to LogController which calls LogService
6
+ */
7
+ export function createLogsRoutes(container, mcpSharkLogs) {
8
+ const logService = container.getService('log');
9
+ const logger = container.getLibrary('logger');
10
+ logService.initialize(mcpSharkLogs);
11
+ const logController = new LogController(logService, logger);
15
12
 
16
- router.exportLogs = (_req, res) => {
17
- try {
18
- const logsText = mcpSharkLogs
19
- .map((log) => `[${log.timestamp}] [${log.type.toUpperCase()}] ${log.line}`)
20
- .join('\n');
13
+ const router = {};
21
14
 
22
- const filename = `mcp-shark-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
23
- res.setHeader('Content-Type', 'text/plain');
24
- res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
25
- res.send(logsText);
26
- } catch (error) {
27
- res.status(500).json({ error: 'Failed to export logs', details: error.message });
28
- }
29
- };
15
+ router.getLogs = logController.getLogs;
16
+ router.clearLogs = logController.clearLogs;
17
+ router.exportLogs = logController.exportLogs;
30
18
 
31
19
  return router;
32
20
  }
@@ -1,181 +1,18 @@
1
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
- import logger from '../utils/logger.js';
1
+ import { McpClientController } from '#ui/server/controllers/index.js';
4
2
 
5
- const MCP_SERVER_BASE_URL = 'http://localhost:9851/mcp';
3
+ /**
4
+ * Create playground routes
5
+ * Routes delegate to McpClientController which calls McpClientService
6
+ */
7
+ export function createPlaygroundRoutes(container) {
8
+ const mcpClientService = container.getService('mcpClient');
9
+ const logger = container.getLibrary('logger');
10
+ const mcpClientController = new McpClientController(mcpClientService, logger);
6
11
 
7
- // Store client connections per server and session
8
- const clientSessions = new Map();
9
-
10
- function getSessionKey(serverName, sessionId) {
11
- return `${serverName}:${sessionId}`;
12
- }
13
-
14
- export function createPlaygroundRoutes() {
15
12
  const router = {};
16
13
 
17
- // Get or create client for a session and server
18
- async function getClient(serverName, sessionId) {
19
- const sessionKey = getSessionKey(serverName, sessionId);
20
- if (clientSessions.has(sessionKey)) {
21
- return clientSessions.get(sessionKey);
22
- }
23
-
24
- if (!serverName) {
25
- throw new Error('Server name is required');
26
- }
27
-
28
- const mcpServerUrl = `${MCP_SERVER_BASE_URL}/${encodeURIComponent(serverName)}`;
29
-
30
- const client = new Client(
31
- { name: 'mcp-shark-playground', version: '1.0.0' },
32
- {
33
- capabilities: {
34
- tools: {},
35
- resources: {},
36
- prompts: {},
37
- },
38
- }
39
- );
40
-
41
- const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl));
42
- await client.connect(transport);
43
-
44
- const clientWrapper = {
45
- client,
46
- transport,
47
- close: async () => {
48
- await client.close();
49
- transport.close?.();
50
- },
51
- };
52
-
53
- clientSessions.set(sessionKey, clientWrapper);
54
- return clientWrapper;
55
- }
56
-
57
- router.proxyRequest = async (req, res) => {
58
- try {
59
- const { method, params, serverName } = req.body;
60
-
61
- if (!method) {
62
- return res.status(400).json({
63
- error: 'Invalid request',
64
- message: 'method field is required',
65
- });
66
- }
67
-
68
- if (!serverName) {
69
- return res.status(400).json({
70
- error: 'Invalid request',
71
- message: 'serverName field is required',
72
- });
73
- }
74
-
75
- // Get or create session ID
76
- const sessionId =
77
- req.headers['mcp-session-id'] ||
78
- req.headers['x-mcp-session-id'] ||
79
- `playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
80
-
81
- const { client } = await getClient(serverName, sessionId);
82
-
83
- const executeMethod = async () => {
84
- switch (method) {
85
- case 'tools/list':
86
- return await client.listTools();
87
- case 'tools/call':
88
- if (!params?.name) {
89
- return res.status(400).json({
90
- error: 'Invalid request',
91
- message: 'Tool name is required',
92
- });
93
- }
94
- return await client.callTool({
95
- name: params.name,
96
- arguments: params.arguments || {},
97
- });
98
- case 'prompts/list':
99
- return await client.listPrompts();
100
- case 'prompts/get':
101
- if (!params?.name) {
102
- return res.status(400).json({
103
- error: 'Invalid request',
104
- message: 'Prompt name is required',
105
- });
106
- }
107
- return await client.getPrompt({
108
- name: params.name,
109
- arguments: params.arguments || {},
110
- });
111
- case 'resources/list':
112
- return await client.listResources();
113
- case 'resources/read':
114
- if (!params?.uri) {
115
- return res.status(400).json({
116
- error: 'Invalid request',
117
- message: 'Resource URI is required',
118
- });
119
- }
120
- return await client.readResource({ uri: params.uri });
121
- default:
122
- return res.status(400).json({
123
- error: 'Unsupported method',
124
- message: `Method ${method} is not supported`,
125
- });
126
- }
127
- };
128
-
129
- const result = await executeMethod();
130
-
131
- // Return session ID in response
132
- res.setHeader('Mcp-Session-Id', sessionId);
133
- res.json({
134
- result,
135
- _sessionId: sessionId,
136
- });
137
- } catch (error) {
138
- logger.error({ error: error.message }, 'Error in playground proxy');
139
-
140
- // Check if it's a connection error
141
- if (error.message?.includes('ECONNREFUSED') || error.message?.includes('connect')) {
142
- return res.status(503).json({
143
- error: 'MCP server unavailable',
144
- message: error.message,
145
- details: 'Make sure the MCP Shark server is running on port 9851',
146
- });
147
- }
148
-
149
- res.status(500).json({
150
- error: 'Internal server error',
151
- message: error.message || 'Unknown error',
152
- });
153
- }
154
- };
155
-
156
- // Cleanup endpoint to close client connections
157
- router.cleanup = async (req, res) => {
158
- const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'];
159
- const { serverName } = req.body || {};
160
-
161
- if (serverName && sessionId) {
162
- const sessionKey = getSessionKey(serverName, sessionId);
163
- if (clientSessions.has(sessionKey)) {
164
- const clientWrapper = clientSessions.get(sessionKey);
165
- await clientWrapper.close();
166
- clientSessions.delete(sessionKey);
167
- }
168
- } else if (sessionId) {
169
- // Cleanup all sessions for this sessionId across all servers
170
- for (const [key, clientWrapper] of clientSessions.entries()) {
171
- if (key.endsWith(`:${sessionId}`)) {
172
- await clientWrapper.close();
173
- clientSessions.delete(key);
174
- }
175
- }
176
- }
177
- res.json({ success: true });
178
- };
14
+ router.proxyRequest = mcpClientController.proxyRequest;
15
+ router.cleanup = mcpClientController.cleanup;
179
16
 
180
17
  return router;
181
18
  }