@mcp-shark/mcp-shark 1.5.3 → 1.5.5

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 +134 -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 +62 -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 +162 -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 +169 -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 -111
  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
@@ -7,7 +7,7 @@
7
7
  <title>MCP Shark</title>
8
8
  <link rel="icon" type="image/png" href="/og-image.png" />
9
9
  <link rel="apple-touch-icon" href="/og-image.png" />
10
- <script type="module" crossorigin src="/assets/index-CFHeMNwd.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-CArYxKxS.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-Cc-IUa83.css">
12
12
  </head>
13
13
  <body>
@@ -0,0 +1,129 @@
1
+ import { StatusCodes } from '#core/constants/index.js';
2
+ import { NotFoundError } from '#core/libraries/index.js';
3
+ import { handleError, handleValidationError } from '../utils/errorHandler.js';
4
+
5
+ /**
6
+ * Controller for backup-related HTTP endpoints
7
+ */
8
+ export class BackupController {
9
+ constructor(backupService, serverManagementService, logger) {
10
+ this.backupService = backupService;
11
+ this.serverManagementService = serverManagementService;
12
+ this.logger = logger;
13
+ }
14
+
15
+ /**
16
+ * Get restore message based on result
17
+ * @private
18
+ */
19
+ _getRestoreMessage(result) {
20
+ if (result.wasPatched && result.repatched) {
21
+ return 'Config file restored from backup and automatically repatched (server is running)';
22
+ }
23
+ if (result.wasPatched && !result.repatched) {
24
+ return 'Config file restored from backup (was patched, but server is not running)';
25
+ }
26
+ return 'Config file restored from backup';
27
+ }
28
+
29
+ listBackups = (_req, res) => {
30
+ try {
31
+ const backups = this.backupService.listBackups();
32
+ res.json({ backups });
33
+ } catch (error) {
34
+ handleError(error, res, this.logger, 'Error listing backups');
35
+ }
36
+ };
37
+
38
+ viewBackup = (req, res) => {
39
+ try {
40
+ const { backupPath } = req.query;
41
+
42
+ if (!backupPath) {
43
+ return handleValidationError('backupPath is required', res, this.logger);
44
+ }
45
+
46
+ const result = this.backupService.viewBackup(backupPath);
47
+
48
+ if (!result) {
49
+ return handleError(
50
+ new NotFoundError('Backup file not found', null),
51
+ res,
52
+ this.logger,
53
+ 'Error viewing backup'
54
+ );
55
+ }
56
+
57
+ res.json({
58
+ success: true,
59
+ ...result,
60
+ });
61
+ } catch (error) {
62
+ handleError(error, res, this.logger, 'Error viewing backup');
63
+ }
64
+ };
65
+
66
+ restoreBackup = (req, res) => {
67
+ try {
68
+ const { backupPath, originalPath } = req.body;
69
+
70
+ if (!backupPath) {
71
+ return handleValidationError('backupPath is required', res, this.logger);
72
+ }
73
+
74
+ // Check if server is running
75
+ const serverStatus = this.serverManagementService.getServerStatus();
76
+ const serverIsRunning = serverStatus.running;
77
+
78
+ const result = this.backupService.restoreBackup(backupPath, originalPath, serverIsRunning);
79
+
80
+ if (!result.success) {
81
+ return res.status(StatusCodes.BAD_REQUEST).json({
82
+ error: 'ValidationError',
83
+ message: result.error || 'Failed to restore backup',
84
+ });
85
+ }
86
+
87
+ const message = this._getRestoreMessage(result);
88
+
89
+ res.json({
90
+ success: true,
91
+ message,
92
+ originalPath: result.originalPath,
93
+ wasPatched: result.wasPatched,
94
+ repatched: result.repatched,
95
+ });
96
+ } catch (error) {
97
+ handleError(error, res, this.logger, 'Error restoring backup');
98
+ }
99
+ };
100
+
101
+ deleteBackup = (req, res) => {
102
+ try {
103
+ const { backupPath } = req.body;
104
+
105
+ if (!backupPath) {
106
+ return handleValidationError('backupPath is required', res, this.logger);
107
+ }
108
+
109
+ const result = this.backupService.deleteBackup(backupPath);
110
+
111
+ if (!result.success) {
112
+ return handleError(
113
+ new NotFoundError(result.error || 'Backup file not found', null),
114
+ res,
115
+ this.logger,
116
+ 'Error deleting backup'
117
+ );
118
+ }
119
+
120
+ res.json({
121
+ success: true,
122
+ message: 'Backup file deleted successfully',
123
+ backupPath: result.backupPath,
124
+ });
125
+ } catch (error) {
126
+ handleError(error, res, this.logger, 'Error deleting backup');
127
+ }
128
+ };
129
+ }
@@ -0,0 +1,92 @@
1
+ import { StatusCodes } from '#core/constants/index.js';
2
+
3
+ /**
4
+ * Controller for configuration-related HTTP endpoints
5
+ * Handles HTTP request/response translation for config operations
6
+ */
7
+ export class ConfigController {
8
+ constructor(configService, logger) {
9
+ this.configService = configService;
10
+ this.logger = logger;
11
+ }
12
+
13
+ /**
14
+ * Extract services from config file
15
+ */
16
+ extractServices = (req, res) => {
17
+ try {
18
+ const { filePath, fileContent } = req.body;
19
+
20
+ if (!filePath && !fileContent) {
21
+ return res.status(StatusCodes.BAD_REQUEST).json({
22
+ error: 'Either filePath or fileContent is required',
23
+ });
24
+ }
25
+
26
+ const result = this.configService.extractServicesFromFile(filePath, fileContent);
27
+
28
+ if (!result.success) {
29
+ const statusCode =
30
+ result.error === 'File not found' ? StatusCodes.NOT_FOUND : StatusCodes.BAD_REQUEST;
31
+ return res.status(statusCode).json(result);
32
+ }
33
+
34
+ res.json(result);
35
+ } catch (error) {
36
+ this.logger?.error({ error: error.message }, 'Error extracting services');
37
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
38
+ error: 'Failed to extract services',
39
+ details: error.message,
40
+ });
41
+ }
42
+ };
43
+
44
+ /**
45
+ * Read config file
46
+ */
47
+ readConfig = (req, res) => {
48
+ try {
49
+ const { filePath } = req.query;
50
+
51
+ if (!filePath) {
52
+ return res.status(StatusCodes.BAD_REQUEST).json({ error: 'filePath is required' });
53
+ }
54
+
55
+ const result = this.configService.readConfigFileWithMetadata(filePath);
56
+
57
+ if (!result.success) {
58
+ return res.status(StatusCodes.NOT_FOUND).json(result);
59
+ }
60
+
61
+ res.json(result);
62
+ } catch (error) {
63
+ this.logger?.error({ error: error.message }, 'Error reading config');
64
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
65
+ error: 'Failed to read file',
66
+ details: error.message,
67
+ });
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Detect config files on the system
73
+ */
74
+ detectConfig = (_req, res) => {
75
+ try {
76
+ const detected = this.configService.detectConfigFiles();
77
+ const homeDir = this.configService.getHomeDir();
78
+
79
+ res.json({
80
+ detected,
81
+ platform: process.platform,
82
+ homeDir,
83
+ });
84
+ } catch (error) {
85
+ this.logger?.error({ error: error.message }, 'Error detecting config');
86
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
87
+ error: 'Failed to detect config files',
88
+ details: error.message,
89
+ });
90
+ }
91
+ };
92
+ }
@@ -0,0 +1,41 @@
1
+ import { StatusCodes } from '#core/constants/StatusCodes.js';
2
+ import { ConversationFilters } from '#core/models/ConversationFilters.js';
3
+
4
+ /**
5
+ * Controller for conversation-related HTTP endpoints
6
+ */
7
+
8
+ export class ConversationController {
9
+ constructor(conversationService, serializationLib, logger) {
10
+ this.conversationService = conversationService;
11
+ this.serializationLib = serializationLib;
12
+ this.logger = logger;
13
+ }
14
+
15
+ /**
16
+ * GET /api/conversations
17
+ */
18
+ getConversations(req, res) {
19
+ try {
20
+ const filters = new ConversationFilters({
21
+ sessionId: req.query.sessionId || null,
22
+ method: req.query.method || null,
23
+ status: req.query.status || null,
24
+ jsonrpcId: req.query.jsonrpcId || null,
25
+ startTime: req.query.startTime || null,
26
+ endTime: req.query.endTime || null,
27
+ limit: req.query.limit,
28
+ offset: req.query.offset,
29
+ });
30
+ const conversations = this.conversationService.getConversations(filters);
31
+ const serialized = this.serializationLib.serializeBigInt(conversations);
32
+ res.json(serialized);
33
+ } catch (error) {
34
+ this.logger.error({ error: error.message }, 'Error in getConversations');
35
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
36
+ error: 'Failed to query conversations',
37
+ details: error.message,
38
+ });
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,44 @@
1
+ import { Defaults } from '#core/constants/index.js';
2
+ import { handleError } from '../utils/errorHandler.js';
3
+
4
+ /**
5
+ * Controller for log-related HTTP endpoints
6
+ */
7
+ export class LogController {
8
+ constructor(logService, logger) {
9
+ this.logService = logService;
10
+ this.logger = logger;
11
+ }
12
+
13
+ getLogs = (req, res) => {
14
+ try {
15
+ const limit = Number.parseInt(req.query.limit) || Defaults.DEFAULT_LIMIT;
16
+ const offset = Number.parseInt(req.query.offset) || 0;
17
+ const logs = this.logService.getLogs({ limit, offset });
18
+ res.json(logs);
19
+ } catch (error) {
20
+ handleError(error, res, this.logger, 'Error getting logs');
21
+ }
22
+ };
23
+
24
+ clearLogs = (_req, res) => {
25
+ try {
26
+ const result = this.logService.clearLogs();
27
+ res.json(result);
28
+ } catch (error) {
29
+ handleError(error, res, this.logger, 'Error clearing logs');
30
+ }
31
+ };
32
+
33
+ exportLogs = (_req, res) => {
34
+ try {
35
+ const logsText = this.logService.exportLogs();
36
+ const filename = `mcp-shark-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
37
+ res.setHeader('Content-Type', 'text/plain');
38
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
39
+ res.send(logsText);
40
+ } catch (error) {
41
+ handleError(error, res, this.logger, 'Error exporting logs');
42
+ }
43
+ };
44
+ }
@@ -0,0 +1,60 @@
1
+ import { handleError, handleValidationError } from '../utils/errorHandler.js';
2
+
3
+ /**
4
+ * Controller for MCP client (Playground) HTTP endpoints
5
+ */
6
+ export class McpClientController {
7
+ constructor(mcpClientService, logger) {
8
+ this.mcpClientService = mcpClientService;
9
+ this.logger = logger;
10
+ }
11
+
12
+ proxyRequest = async (req, res) => {
13
+ try {
14
+ const { method, params, serverName } = req.body;
15
+
16
+ if (!method) {
17
+ return handleValidationError('method field is required', res, this.logger);
18
+ }
19
+
20
+ if (!serverName) {
21
+ return handleValidationError('serverName field is required', res, this.logger);
22
+ }
23
+
24
+ const sessionId =
25
+ req.headers['mcp-session-id'] ||
26
+ req.headers['x-mcp-session-id'] ||
27
+ `playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
28
+
29
+ const { client } = await this.mcpClientService.getOrCreateClient(serverName, sessionId);
30
+ this.mcpClientService.updateLastAccessed(serverName, sessionId);
31
+
32
+ const result = await this.mcpClientService.executeMethod(client, method, params);
33
+
34
+ res.setHeader('Mcp-Session-Id', sessionId);
35
+ res.json({
36
+ result,
37
+ _sessionId: sessionId,
38
+ });
39
+ } catch (error) {
40
+ handleError(error, res, this.logger, 'Error in playground proxy');
41
+ }
42
+ };
43
+
44
+ cleanup = async (req, res) => {
45
+ try {
46
+ const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'];
47
+ const { serverName } = req.body || {};
48
+
49
+ if (serverName && sessionId) {
50
+ await this.mcpClientService.closeClient(serverName, sessionId);
51
+ } else if (sessionId) {
52
+ await this.mcpClientService.cleanupSession(sessionId);
53
+ }
54
+
55
+ res.json({ success: true });
56
+ } catch (error) {
57
+ handleError(error, res, this.logger, 'Error cleaning up client');
58
+ }
59
+ };
60
+ }
@@ -0,0 +1,44 @@
1
+ import { NotFoundError, ValidationError } from '#core/libraries/index.js';
2
+ import { handleError } from '../utils/errorHandler.js';
3
+
4
+ /**
5
+ * Controller for MCP discovery HTTP endpoints
6
+ */
7
+ export class McpDiscoveryController {
8
+ constructor(mcpDiscoveryService, logger) {
9
+ this.mcpDiscoveryService = mcpDiscoveryService;
10
+ this.logger = logger;
11
+ }
12
+
13
+ discoverServers = async (_req, res) => {
14
+ try {
15
+ const result = await this.mcpDiscoveryService.discoverAllServers();
16
+
17
+ if (!result.success) {
18
+ if (result.error === 'MCP config file not found') {
19
+ return handleError(
20
+ new NotFoundError('Config file not found', null),
21
+ res,
22
+ this.logger,
23
+ 'Error discovering servers'
24
+ );
25
+ }
26
+ if (result.error === 'No servers found in config') {
27
+ return handleError(
28
+ new ValidationError('The config file does not contain any MCP servers', null),
29
+ res,
30
+ this.logger,
31
+ 'Error discovering servers'
32
+ );
33
+ }
34
+ }
35
+
36
+ return res.json({
37
+ success: true,
38
+ servers: result.servers,
39
+ });
40
+ } catch (error) {
41
+ handleError(error, res, this.logger, 'Error discovering servers');
42
+ }
43
+ };
44
+ }
@@ -0,0 +1,129 @@
1
+ import { StatusCodes } from '#core/constants/StatusCodes.js';
2
+ import { ExportFormat } from '#core/models/ExportFormat.js';
3
+ import { RequestFilters } from '#core/models/RequestFilters.js';
4
+
5
+ /**
6
+ * Controller for request-related HTTP endpoints
7
+ * Handles HTTP concerns: extraction, sanitization, serialization, formatting
8
+ */
9
+
10
+ export class RequestController {
11
+ constructor(requestService, exportService, serializationLib, logger) {
12
+ this.requestService = requestService;
13
+ this.exportService = exportService;
14
+ this.serializationLib = serializationLib;
15
+ this.logger = logger;
16
+ }
17
+
18
+ /**
19
+ * Sanitize search parameter
20
+ */
21
+ _sanitizeSearch(value) {
22
+ if (value !== undefined && value !== null) {
23
+ const trimmed = String(value).trim();
24
+ return trimmed.length > 0 ? trimmed : null;
25
+ }
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Extract and sanitize filters from HTTP query
31
+ */
32
+ _extractFilters(reqQuery) {
33
+ return new RequestFilters({
34
+ sessionId: reqQuery.sessionId ? String(reqQuery.sessionId).trim() : null,
35
+ direction: reqQuery.direction ? String(reqQuery.direction).trim() : null,
36
+ method: reqQuery.method ? String(reqQuery.method).trim() : null,
37
+ jsonrpcMethod: reqQuery.jsonrpcMethod ? String(reqQuery.jsonrpcMethod).trim() : null,
38
+ statusCode: reqQuery.statusCode ? Number.parseInt(reqQuery.statusCode) : null,
39
+ jsonrpcId: reqQuery.jsonrpcId ? String(reqQuery.jsonrpcId).trim() : null,
40
+ search: this._sanitizeSearch(reqQuery.search),
41
+ serverName: reqQuery.serverName ? String(reqQuery.serverName).trim() : null,
42
+ startTime: reqQuery.startTime || null,
43
+ endTime: reqQuery.endTime || null,
44
+ limit: reqQuery.limit,
45
+ offset: reqQuery.offset,
46
+ });
47
+ }
48
+
49
+ /**
50
+ * GET /api/requests
51
+ */
52
+ getRequests(req, res) {
53
+ try {
54
+ const filters = this._extractFilters(req.query);
55
+ const requests = this.requestService.getRequests(filters);
56
+ const serialized = this.serializationLib.serializeBigInt(requests);
57
+ res.json(serialized);
58
+ } catch (error) {
59
+ this.logger.error({ error: error.message }, 'Error in getRequests');
60
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
61
+ error: 'Failed to query requests',
62
+ details: error.message,
63
+ });
64
+ }
65
+ }
66
+
67
+ /**
68
+ * GET /api/requests/:frameNumber
69
+ */
70
+ getRequest(req, res) {
71
+ try {
72
+ const request = this.requestService.getRequest(req.params.frameNumber);
73
+ if (!request) {
74
+ return res.status(StatusCodes.NOT_FOUND).json({ error: 'Request not found' });
75
+ }
76
+ const serialized = this.serializationLib.serializeBigInt(request);
77
+ res.json(serialized);
78
+ } catch (error) {
79
+ this.logger.error({ error: error.message }, 'Error in getRequest');
80
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
81
+ error: 'Failed to get request',
82
+ details: error.message,
83
+ });
84
+ }
85
+ }
86
+
87
+ /**
88
+ * POST /api/requests/clear
89
+ */
90
+ clearRequests(_req, res) {
91
+ try {
92
+ const result = this.requestService.clearRequests();
93
+ res.json({
94
+ success: true,
95
+ message: `Cleared ${result.clearedTables.length} table(s): ${result.clearedTables.join(', ')}. All captured traffic has been cleared.`,
96
+ });
97
+ } catch (error) {
98
+ this.logger.error({ error: error.message }, 'Error clearing requests');
99
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
100
+ error: 'Failed to clear traffic',
101
+ details: error.message,
102
+ });
103
+ }
104
+ }
105
+
106
+ /**
107
+ * GET /api/requests/export
108
+ */
109
+ exportRequests(req, res) {
110
+ try {
111
+ const format = req.query.format || ExportFormat.JSON;
112
+ const filters = this._extractFilters(req.query);
113
+ const requests = this.requestService.getRequestsForExport(filters);
114
+
115
+ const result = this.exportService.exportRequests(requests, format, this.serializationLib);
116
+
117
+ const filename = `mcp-shark-traffic-${new Date().toISOString().replace(/[:.]/g, '-')}.${result.extension}`;
118
+ res.setHeader('Content-Type', result.contentType);
119
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
120
+ res.send(result.content);
121
+ } catch (error) {
122
+ this.logger.error({ error: error.message }, 'Error exporting requests');
123
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
124
+ error: 'Failed to export traffic',
125
+ details: error.message,
126
+ });
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,122 @@
1
+ import { StatusCodes } from '#core/constants/index.js';
2
+ import { handleError, handleValidationError } from '../utils/errorHandler.js';
3
+
4
+ /**
5
+ * Controller for Smart Scan HTTP endpoints
6
+ */
7
+ export class ScanController {
8
+ constructor(scanService, scanCacheService, logger) {
9
+ this.scanService = scanService;
10
+ this.scanCacheService = scanCacheService;
11
+ this.logger = logger;
12
+ }
13
+
14
+ createScan = async (req, res) => {
15
+ try {
16
+ const { apiToken, scanData } = req.body;
17
+
18
+ if (!apiToken) {
19
+ return handleValidationError('API token is required', res, this.logger);
20
+ }
21
+
22
+ if (!scanData) {
23
+ return handleValidationError('Scan data is required', res, this.logger);
24
+ }
25
+
26
+ const result = await this.scanService.createScan(scanData, apiToken);
27
+ return res.status(result.status).json(result.data || { error: result.error });
28
+ } catch (error) {
29
+ handleError(error, res, this.logger, 'Smart Scan API error');
30
+ }
31
+ };
32
+
33
+ createBatchScans = async (req, res) => {
34
+ try {
35
+ const { apiToken, servers } = req.body;
36
+
37
+ if (!apiToken) {
38
+ return handleValidationError('API token is required', res, this.logger);
39
+ }
40
+
41
+ if (!servers || !Array.isArray(servers) || servers.length === 0) {
42
+ return handleValidationError('Servers array is required', res, this.logger);
43
+ }
44
+
45
+ const results = await this.scanService.createBatchScans(servers, apiToken);
46
+ return res.json({
47
+ success: true,
48
+ results,
49
+ });
50
+ } catch (error) {
51
+ handleError(error, res, this.logger, 'Smart Scan batch API error');
52
+ }
53
+ };
54
+
55
+ getScan = async (req, res) => {
56
+ try {
57
+ const { scanId } = req.params;
58
+ const apiToken = req.headers.authorization?.replace('Bearer ', '');
59
+
60
+ if (!apiToken) {
61
+ return res.status(StatusCodes.UNAUTHORIZED).json({
62
+ error: 'API token is required',
63
+ });
64
+ }
65
+
66
+ if (!scanId) {
67
+ return res.status(StatusCodes.BAD_REQUEST).json({
68
+ error: 'Scan ID is required',
69
+ });
70
+ }
71
+
72
+ const result = await this.scanService.getScan(scanId, apiToken);
73
+ return res.status(result.status).json(result.data || { error: result.error });
74
+ } catch (error) {
75
+ handleError(error, res, this.logger, 'Smart Scan API error');
76
+ }
77
+ };
78
+
79
+ listScans = (_req, res) => {
80
+ try {
81
+ const cachedScans = this.scanCacheService.getAllCachedScanResults();
82
+ return res.json({
83
+ scans: cachedScans,
84
+ cached: true,
85
+ count: cachedScans.length,
86
+ });
87
+ } catch (error) {
88
+ handleError(error, res, this.logger, 'Error loading cached scans');
89
+ }
90
+ };
91
+
92
+ getCachedResults = (req, res) => {
93
+ try {
94
+ const { servers } = req.body;
95
+
96
+ if (!servers || !Array.isArray(servers) || servers.length === 0) {
97
+ return handleValidationError('Servers array is required', res, this.logger);
98
+ }
99
+
100
+ const results = this.scanService.getCachedResults(servers);
101
+ return res.json({
102
+ success: true,
103
+ results,
104
+ });
105
+ } catch (error) {
106
+ handleError(error, res, this.logger, 'Error getting cached results');
107
+ }
108
+ };
109
+
110
+ clearCache = (_req, res) => {
111
+ try {
112
+ const deletedCount = this.scanCacheService.clearAllScanResults();
113
+ return res.json({
114
+ success: true,
115
+ message: `Cleared ${deletedCount} cached scan result${deletedCount !== 1 ? 's' : ''}`,
116
+ deletedCount,
117
+ });
118
+ } catch (error) {
119
+ handleError(error, res, this.logger, 'Error clearing cache');
120
+ }
121
+ };
122
+ }