@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,28 +1,28 @@
1
1
  import { createConnection } from 'node:net';
2
+ import { Defaults } from '#core/constants/Defaults.js';
2
3
 
3
- export function checkPortReady(port, host = 'localhost', timeout = 10000) {
4
- return new Promise((resolve, reject) => {
5
- const startTime = Date.now();
4
+ function tryConnect(port, host, startTime, timeout, resolve, reject) {
5
+ const socket = createConnection(port, host);
6
6
 
7
- const tryConnect = () => {
8
- const socket = createConnection(port, host);
9
-
10
- socket.on('connect', () => {
11
- socket.destroy();
12
- resolve(true);
13
- });
7
+ socket.on('connect', () => {
8
+ socket.destroy();
9
+ resolve(true);
10
+ });
14
11
 
15
- socket.on('error', (_err) => {
16
- socket.destroy();
17
- const elapsed = Date.now() - startTime;
18
- if (elapsed >= timeout) {
19
- reject(new Error(`Port ${port} not ready after ${timeout}ms`));
20
- } else {
21
- setTimeout(tryConnect, 200);
22
- }
23
- });
24
- };
12
+ socket.on('error', (_err) => {
13
+ socket.destroy();
14
+ const elapsed = Date.now() - startTime;
15
+ if (elapsed >= timeout) {
16
+ reject(new Error(`Port ${port} not ready after ${timeout}ms`));
17
+ } else {
18
+ setTimeout(() => tryConnect(port, host, startTime, timeout, resolve, reject), 200);
19
+ }
20
+ });
21
+ }
25
22
 
26
- tryConnect();
23
+ export function checkPortReady(port, host = 'localhost', timeout = Defaults.PORT_CHECK_TIMEOUT) {
24
+ return new Promise((resolve, reject) => {
25
+ const startTime = Date.now();
26
+ tryConnect(port, host, startTime, timeout, resolve, reject);
27
27
  });
28
28
  }
@@ -1,13 +1,21 @@
1
- const MAX_LOG_LINES = 10000;
1
+ import { Defaults } from '#core/constants/Defaults.js';
2
+
3
+ const MAX_LOG_LINES = Defaults.MAX_LOG_LINES;
4
+
5
+ function logEntry(mcpSharkLogs, broadcastLogUpdate, type, data) {
6
+ const timestamp = new Date().toISOString();
7
+ const line = data.toString();
8
+ mcpSharkLogs.push({ timestamp, type, line });
9
+ if (mcpSharkLogs.length > MAX_LOG_LINES) {
10
+ mcpSharkLogs.shift();
11
+ }
12
+ broadcastLogUpdate({ timestamp, type, line });
13
+ }
14
+
15
+ function createLogEntryWrapper(mcpSharkLogs, broadcastLogUpdate, type, data) {
16
+ return logEntry(mcpSharkLogs, broadcastLogUpdate, type, data);
17
+ }
2
18
 
3
19
  export function createLogEntry(mcpSharkLogs, broadcastLogUpdate) {
4
- return function logEntry(type, data) {
5
- const timestamp = new Date().toISOString();
6
- const line = data.toString();
7
- mcpSharkLogs.push({ timestamp, type, line });
8
- if (mcpSharkLogs.length > MAX_LOG_LINES) {
9
- mcpSharkLogs.shift();
10
- }
11
- broadcastLogUpdate({ timestamp, type, line });
12
- };
20
+ return (type, data) => createLogEntryWrapper(mcpSharkLogs, broadcastLogUpdate, type, data);
13
21
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Set MCP Shark server process in state
3
+ * @param {object} processState - Process state object
4
+ * @param {object} server - Server instance
5
+ */
6
+ export function setMcpSharkProcess(processState, server) {
7
+ processState.mcpSharkServer = server;
8
+ }
9
+
10
+ /**
11
+ * Get MCP Shark server process from state
12
+ * @param {object} processState - Process state object
13
+ * @returns {object|null} Server instance or null
14
+ */
15
+ export function getMcpSharkProcess(processState) {
16
+ return processState.mcpSharkServer;
17
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Handle shutdown signal (SIGTERM, SIGINT)
3
+ * @param {Function} cleanup - Cleanup function to execute
4
+ * @param {object} logger - Logger instance
5
+ */
6
+ export async function shutdown(cleanup, logger) {
7
+ try {
8
+ // Set a timeout to force exit if cleanup takes too long
9
+ const timeout = setTimeout(() => {
10
+ logger?.warn('Shutdown timeout reached, forcing exit');
11
+ process.exit(0);
12
+ }, 2000); // 2 second timeout
13
+
14
+ await cleanup();
15
+ clearTimeout(timeout);
16
+ } catch (err) {
17
+ logger?.warn({ error: err.message }, 'Error during shutdown, exiting anyway');
18
+ } finally {
19
+ // Always exit, even if cleanup failed
20
+ process.exit(0);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Handle process exit event
26
+ * @param {Function} cleanup - Cleanup function to execute
27
+ */
28
+ export async function handleExit(cleanup) {
29
+ try {
30
+ await cleanup();
31
+ } catch (_err) {
32
+ // Ignore errors during exit
33
+ }
34
+ }
@@ -0,0 +1,33 @@
1
+ import { RequestFilters } from '#core';
2
+
3
+ /**
4
+ * Broadcast log update to all connected WebSocket clients
5
+ * @param {Set} clients - Set of connected WebSocket clients
6
+ * @param {object} logEntry - Log entry to broadcast
7
+ */
8
+ export function broadcastLogUpdate(clients, logEntry) {
9
+ const message = JSON.stringify({ type: 'log', data: logEntry });
10
+ for (const client of clients) {
11
+ if (client.readyState === 1) {
12
+ client.send(message);
13
+ }
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Notify all connected clients about request updates
19
+ * @param {Set} clients - Set of connected WebSocket clients
20
+ * @param {object} requestService - Request service instance
21
+ * @param {object} serializationLib - Serialization library instance
22
+ */
23
+ export function notifyClients(clients, requestService, serializationLib) {
24
+ const filters = new RequestFilters({ limit: 100 });
25
+ const requests = requestService.getRequests(filters);
26
+ const serialized = serializationLib.serializeBigInt(requests);
27
+ const message = JSON.stringify({ type: 'update', data: serialized });
28
+ for (const client of clients) {
29
+ if (client.readyState === 1) {
30
+ client.send(message);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,52 @@
1
+ import { Server as ServerConstants } from '#core/constants/Server.js';
2
+
3
+ /**
4
+ * Handle WebSocket connection with heartbeat and timeout
5
+ * @param {Set} clients - Set of connected WebSocket clients
6
+ * @param {WebSocket} ws - WebSocket connection
7
+ * @param {object} logger - Logger instance
8
+ */
9
+ export function handleWebSocketConnection(clients, ws, logger) {
10
+ clients.add(ws);
11
+
12
+ // Set up timeout to close stale connections
13
+ let timeoutId = setTimeout(() => {
14
+ if (ws.readyState === 1) {
15
+ logger?.warn('WebSocket connection timeout, closing');
16
+ ws.close();
17
+ }
18
+ }, ServerConstants.WEBSOCKET_TIMEOUT_MS);
19
+
20
+ // Set up heartbeat to keep connection alive
21
+ const heartbeatInterval = setInterval(() => {
22
+ if (ws.readyState === 1) {
23
+ ws.ping();
24
+ } else {
25
+ clearInterval(heartbeatInterval);
26
+ }
27
+ }, ServerConstants.WEBSOCKET_HEARTBEAT_INTERVAL_MS);
28
+
29
+ ws.on('pong', () => {
30
+ // Reset timeout on pong
31
+ clearTimeout(timeoutId);
32
+ timeoutId = setTimeout(() => {
33
+ if (ws.readyState === 1) {
34
+ logger?.warn('WebSocket connection timeout, closing');
35
+ ws.close();
36
+ }
37
+ }, ServerConstants.WEBSOCKET_TIMEOUT_MS);
38
+ });
39
+
40
+ ws.on('close', () => {
41
+ clearTimeout(timeoutId);
42
+ clearInterval(heartbeatInterval);
43
+ clients.delete(ws);
44
+ });
45
+
46
+ ws.on('error', (error) => {
47
+ logger?.error({ error: error.message }, 'WebSocket error');
48
+ clearTimeout(timeoutId);
49
+ clearInterval(heartbeatInterval);
50
+ clients.delete(ws);
51
+ });
52
+ }
package/ui/server.js CHANGED
@@ -1,245 +1,66 @@
1
- import { createServer } from 'node:http';
2
- import * as path from 'node:path';
3
- import { fileURLToPath, pathToFileURL } from 'node:url';
4
- import express from 'express';
5
- import { WebSocketServer } from 'ws';
1
+ import { pathToFileURL } from 'node:url';
6
2
 
7
- import { getDatabaseFile, prepareAppDataSpaces } from '#common/configs';
8
- import { openDb } from '#common/db/init';
9
- import { queryRequests } from '#common/db/query';
10
- import { restoreOriginalConfig } from './server/utils/config.js';
3
+ import { Environment } from '#core/configs/index.js';
4
+ import { bootstrapLogger } from '#core/libraries/index.js';
11
5
 
12
- import { createBackupRoutes } from './server/routes/backups/index.js';
13
- import { createCompositeRoutes } from './server/routes/composite/index.js';
14
- import { createConfigRoutes } from './server/routes/config.js';
15
- import { createConversationsRoutes } from './server/routes/conversations.js';
16
- import { createHelpRoutes } from './server/routes/help.js';
17
- import { createLogsRoutes } from './server/routes/logs.js';
18
- import { createPlaygroundRoutes } from './server/routes/playground.js';
19
- import { createRequestsRoutes } from './server/routes/requests.js';
20
- import { createSessionsRoutes } from './server/routes/sessions.js';
21
- import { createSettingsRoutes } from './server/routes/settings.js';
22
- import { createSmartScanRoutes } from './server/routes/smartscan.js';
23
- import { createStatisticsRoutes } from './server/routes/statistics.js';
24
- import { serializeBigInt } from './server/utils/serialization.js';
25
-
26
- const __filename = fileURLToPath(import.meta.url);
27
- const __dirname = path.dirname(__filename);
28
-
29
- const _MAX_LOG_LINES = 10000;
30
-
31
- export function createUIServer() {
32
- prepareAppDataSpaces();
33
-
34
- const db = openDb(getDatabaseFile());
35
- const app = express();
36
- const server = createServer(app);
37
- const wss = new WebSocketServer({ server });
38
-
39
- app.use(express.json());
40
-
41
- const clients = new Set();
42
- const mcpSharkLogs = [];
43
- const processState = { mcpSharkServer: null };
44
-
45
- const setMcpSharkProcess = (server) => {
46
- processState.mcpSharkServer = server;
47
- };
48
-
49
- wss.on('connection', (ws) => {
50
- clients.add(ws);
51
- ws.on('close', () => clients.delete(ws));
52
- });
53
-
54
- const broadcastLogUpdate = (logEntry) => {
55
- const message = JSON.stringify({ type: 'log', data: logEntry });
56
- clients.forEach((client) => {
57
- if (client.readyState === 1) {
58
- client.send(message);
59
- }
60
- });
61
- };
62
-
63
- const restoreConfig = () => {
64
- return restoreOriginalConfig(mcpSharkLogs, broadcastLogUpdate);
65
- };
66
-
67
- const requestsRoutes = createRequestsRoutes(db);
68
- const conversationsRoutes = createConversationsRoutes(db);
69
- const sessionsRoutes = createSessionsRoutes(db);
70
- const statisticsRoutes = createStatisticsRoutes(db);
71
- const logsRoutes = createLogsRoutes(mcpSharkLogs, broadcastLogUpdate);
72
- const configRoutes = createConfigRoutes();
73
- const backupRoutes = createBackupRoutes();
74
- const getMcpSharkProcess = () => processState.mcpSharkServer;
75
- const compositeRoutes = createCompositeRoutes(
76
- getMcpSharkProcess,
77
- setMcpSharkProcess,
78
- mcpSharkLogs,
79
- broadcastLogUpdate
80
- );
81
- const helpRoutes = createHelpRoutes();
82
- const playgroundRoutes = createPlaygroundRoutes();
83
- const smartScanRoutes = createSmartScanRoutes();
84
- const settingsRoutes = createSettingsRoutes();
85
-
86
- app.get('/api/requests', requestsRoutes.getRequests);
87
- app.get('/api/packets', requestsRoutes.getRequests);
88
- app.get('/api/requests/:frameNumber', requestsRoutes.getRequest);
89
- app.get('/api/packets/:frameNumber', requestsRoutes.getRequest);
90
- app.get('/api/requests/export', requestsRoutes.exportRequests);
91
- app.post('/api/requests/clear', requestsRoutes.clearRequests);
92
-
93
- app.get('/api/conversations', conversationsRoutes.getConversations);
94
-
95
- app.get('/api/sessions', sessionsRoutes.getSessions);
96
- app.get('/api/sessions/:sessionId/requests', sessionsRoutes.getSessionRequests);
97
- app.get('/api/sessions/:sessionId/packets', sessionsRoutes.getSessionRequests);
98
-
99
- app.get('/api/statistics', statisticsRoutes.getStatistics);
100
-
101
- app.get('/api/composite/logs', logsRoutes.getLogs);
102
- app.post('/api/composite/logs/clear', logsRoutes.clearLogs);
103
- app.get('/api/composite/logs/export', logsRoutes.exportLogs);
104
-
105
- app.post('/api/config/services', configRoutes.extractServices);
106
- app.get('/api/config/read', configRoutes.readConfig);
107
- app.get('/api/config/detect', configRoutes.detectConfig);
108
- app.get('/api/config/backups', backupRoutes.listBackups);
109
- app.get('/api/config/backup/view', backupRoutes.viewBackup);
110
- app.post('/api/config/restore', (req, res) => {
111
- backupRoutes.restoreBackup(req, res, mcpSharkLogs, broadcastLogUpdate);
112
- });
113
- app.post('/api/config/backup/delete', (req, res) => {
114
- backupRoutes.deleteBackup(req, res, mcpSharkLogs, broadcastLogUpdate);
115
- });
116
-
117
- app.post('/api/composite/setup', compositeRoutes.setup);
118
- app.post('/api/composite/stop', (req, res) => {
119
- compositeRoutes.stop(req, res, restoreConfig);
120
- });
121
- app.get('/api/composite/status', compositeRoutes.getStatus);
122
- app.get('/api/composite/servers', compositeRoutes.getServers);
123
-
124
- app.get('/api/help/state', helpRoutes.getState);
125
- app.post('/api/help/dismiss', helpRoutes.dismiss);
126
- app.post('/api/help/reset', helpRoutes.reset);
127
-
128
- app.post('/api/playground/proxy', playgroundRoutes.proxyRequest);
129
-
130
- app.post('/api/smartscan/scans', smartScanRoutes.createScan);
131
- app.get('/api/smartscan/scans', smartScanRoutes.listScans);
132
- app.get('/api/smartscan/scans/:scanId', smartScanRoutes.getScan);
133
- app.get('/api/smartscan/token', smartScanRoutes.getToken);
134
- app.post('/api/smartscan/token', smartScanRoutes.saveToken);
135
- app.get('/api/smartscan/discover', smartScanRoutes.discoverServers);
136
- app.post('/api/smartscan/scans/batch', smartScanRoutes.createBatchScans);
137
- app.post('/api/smartscan/cached-results', smartScanRoutes.getCachedResults);
138
- app.post('/api/smartscan/cache/clear', smartScanRoutes.clearCache);
139
-
140
- app.get('/api/settings', settingsRoutes.getSettings);
141
-
142
- const staticPath = path.join(__dirname, 'dist');
143
- app.use(express.static(staticPath));
144
-
145
- app.get('*', (_req, res) => {
146
- res.sendFile(path.join(staticPath, 'index.html'));
147
- });
148
-
149
- const notifyClients = async () => {
150
- const requests = queryRequests(db, { limit: 100 });
151
- const message = JSON.stringify({ type: 'update', data: serializeBigInt(requests) });
152
- clients.forEach((client) => {
153
- if (client.readyState === 1) {
154
- client.send(message);
155
- }
156
- });
157
- };
158
-
159
- const timestampState = { lastTs: 0 };
160
- const intervalId = setInterval(() => {
161
- const lastCheck = db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets').get();
162
- if (lastCheck && lastCheck.max_ts > timestampState.lastTs) {
163
- timestampState.lastTs = lastCheck.max_ts;
164
- notifyClients();
165
- }
166
- }, 500);
167
-
168
- const cleanup = async () => {
169
- console.log('Shutting down UI server...');
170
-
171
- // Clear interval
172
- clearInterval(intervalId);
173
-
174
- // Stop MCP Shark server if running
175
- if (processState.mcpSharkServer) {
176
- try {
177
- if (processState.mcpSharkServer.stop) {
178
- await processState.mcpSharkServer.stop();
179
- }
180
- processState.mcpSharkServer = null;
181
- } catch (err) {
182
- console.error('Error stopping MCP Shark server:', err);
183
- }
184
- }
185
-
186
- // Close WebSocket connections
187
- clients.forEach((client) => {
188
- if (client.readyState === 1) {
189
- client.close();
190
- }
191
- });
192
- clients.clear();
193
-
194
- // Close WebSocket server
195
- wss.close();
196
-
197
- // Restore config
198
- restoreConfig();
199
-
200
- // Close HTTP server
201
- return new Promise((resolve) => {
202
- server.close(() => {
203
- console.log('UI server stopped');
204
- resolve();
205
- });
206
- });
207
- };
208
-
209
- return { server, cleanup };
210
- }
6
+ import { createUIServer } from './server/setup.js';
7
+ import { handleExit, shutdown } from './server/utils/signals.js';
211
8
 
9
+ /**
10
+ * Run UI server with signal handlers
11
+ */
212
12
  export async function runUIServer() {
213
- const port = Number.parseInt(process.env.UI_PORT) || 9853;
214
- const { server, cleanup } = createUIServer();
215
-
216
- const shutdown = async () => {
217
- try {
218
- await cleanup();
219
- } catch (err) {
220
- console.error('Error during shutdown:', err);
221
- } finally {
222
- process.exit(0);
223
- }
224
- };
13
+ const port = Environment.getUiPort();
14
+ const { server, cleanup, logger, wss } = createUIServer();
225
15
 
226
16
  // Register signal handlers
227
- process.on('SIGTERM', shutdown);
228
- process.on('SIGINT', shutdown);
229
- process.on('exit', async () => {
230
- // Final cleanup on exit
231
- try {
232
- await cleanup();
233
- } catch (_err) {
234
- // Ignore errors during exit
17
+ process.on('SIGTERM', () => shutdown(cleanup, logger));
18
+ process.on('SIGINT', () => shutdown(cleanup, logger));
19
+ process.on('exit', () => handleExit(cleanup));
20
+
21
+ // Handle server errors (e.g., port already in use)
22
+ server.on('error', (error) => {
23
+ if (error.code === 'EADDRINUSE') {
24
+ logger?.error(
25
+ {
26
+ port,
27
+ error: error.message,
28
+ },
29
+ `Port ${port} is already in use. Please stop the existing server or use a different port.`
30
+ );
31
+ bootstrapLogger.error(
32
+ `\n❌ Port ${port} is already in use.\n Please stop the existing server or set MCP_SHARK_PORT environment variable to use a different port.\n`
33
+ );
34
+ } else {
35
+ logger?.error({ error: error.message, stack: error.stack }, 'Server error');
36
+ bootstrapLogger.error({ error: error.message }, 'Server error');
235
37
  }
38
+ process.exit(1);
236
39
  });
237
40
 
41
+ // Handle WebSocket server errors
42
+ if (wss) {
43
+ wss.on('error', (error) => {
44
+ logger?.error({ error: error.message }, 'WebSocket server error');
45
+ if (error.code === 'EADDRINUSE') {
46
+ bootstrapLogger.error(
47
+ `\n❌ WebSocket port conflict. Port ${port} is already in use.\n Please stop the existing server or use a different port.\n`
48
+ );
49
+ }
50
+ });
51
+ }
52
+
238
53
  server.listen(port, '0.0.0.0', () => {
239
- console.log(`UI server listening on http://localhost:${port}`);
54
+ logger?.info({ port }, 'UI server listening');
240
55
  });
241
56
  }
242
57
 
243
58
  if (import.meta.url === pathToFileURL(process.argv[1]).href) {
244
- runUIServer().catch(console.error);
59
+ runUIServer().catch((error) => {
60
+ bootstrapLogger.error(
61
+ { error: error.message, stack: error.stack },
62
+ 'Failed to start UI server'
63
+ );
64
+ process.exit(1);
65
+ });
245
66
  }
package/ui/src/App.jsx CHANGED
@@ -4,6 +4,7 @@ import CompositeSetup from './CompositeSetup';
4
4
  import IntroTour from './IntroTour';
5
5
  import SmartScan from './SmartScan';
6
6
  import TabNavigation from './TabNavigation';
7
+ import ApiDocsButton from './components/App/ApiDocsButton';
7
8
  import HelpButton from './components/App/HelpButton';
8
9
  import TrafficTab from './components/App/TrafficTab';
9
10
  import { useAppState } from './components/App/useAppState';
@@ -83,6 +84,7 @@ function App() {
83
84
  <div style={{ position: 'relative' }} data-tour="tabs">
84
85
  <TabNavigation activeTab={activeTab} onTabChange={setActiveTab} />
85
86
  </div>
87
+ <ApiDocsButton />
86
88
  <HelpButton
87
89
  onClick={() => {
88
90
  if (showTour) {
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
2
2
  import BackupList from './components/BackupList';
3
3
  import ConfigFileSection from './components/ConfigFileSection';
4
4
  import ConfigViewerModal from './components/ConfigViewerModal';
5
+ import ConfirmationModal from './components/ConfirmationModal';
5
6
  import MessageDisplay from './components/MessageDisplay';
6
7
  import ServerControl from './components/ServerControl';
7
8
  import SetupHeader from './components/SetupHeader';
@@ -18,6 +19,8 @@ function CompositeSetup() {
18
19
  const [loading, setLoading] = useState(false);
19
20
  const [message, setMessage] = useState(null);
20
21
  const [error, setError] = useState(null);
22
+ const [showRestoreModal, setShowRestoreModal] = useState(false);
23
+ const [pendingRestore, setPendingRestore] = useState({ backupPath: null, originalPath: null });
21
24
 
22
25
  const { services, selectedServices, setSelectedServices } = useServiceExtraction(
23
26
  fileContent,
@@ -52,14 +55,14 @@ function CompositeSetup() {
52
55
  return () => clearInterval(interval);
53
56
  }, []);
54
57
 
55
- const handleRestore = async (backupPath, originalPath) => {
56
- if (
57
- !confirm(
58
- 'Are you sure you want to restore this backup? This will overwrite the current config file.'
59
- )
60
- ) {
61
- return;
62
- }
58
+ const handleRestoreClick = (backupPath, originalPath) => {
59
+ setPendingRestore({ backupPath, originalPath });
60
+ setShowRestoreModal(true);
61
+ };
62
+
63
+ const handleRestore = async () => {
64
+ const { backupPath, originalPath } = pendingRestore;
65
+ setShowRestoreModal(false);
63
66
 
64
67
  try {
65
68
  const res = await fetch('/api/config/restore', {
@@ -256,7 +259,7 @@ function CompositeSetup() {
256
259
  backups={backups}
257
260
  loadingBackups={loadingBackups}
258
261
  onRefresh={loadBackups}
259
- onRestore={handleRestore}
262
+ onRestore={handleRestoreClick}
260
263
  onView={handleViewBackup}
261
264
  onDelete={handleDelete}
262
265
  />
@@ -264,6 +267,17 @@ function CompositeSetup() {
264
267
  <WhatThisDoesSection filePath={filePath} updatePath={updatePath} />
265
268
  </div>
266
269
 
270
+ <ConfirmationModal
271
+ isOpen={showRestoreModal}
272
+ onClose={() => setShowRestoreModal(false)}
273
+ onConfirm={handleRestore}
274
+ title="Restore Backup"
275
+ message="Are you sure you want to restore this backup? This will overwrite the current config file."
276
+ confirmText="Restore"
277
+ cancelText="Cancel"
278
+ danger={false}
279
+ />
280
+
267
281
  <ConfigViewerModal
268
282
  viewingConfig={viewingConfig}
269
283
  configContent={configContent}
@@ -1,6 +1,7 @@
1
1
  import { IconSearch, IconTrash } from '@tabler/icons-react';
2
2
  import anime from 'animejs';
3
3
  import { useEffect, useRef, useState } from 'react';
4
+ import AlertModal from './components/AlertModal';
4
5
  import ConfirmationModal from './components/ConfirmationModal';
5
6
  import ExportControls from './components/PacketFilters/ExportControls';
6
7
  import FilterInput from './components/PacketFilters/FilterInput';
@@ -10,6 +11,8 @@ import { fadeIn } from './utils/animations';
10
11
  function RequestFilters({ filters, onFilterChange, stats, onClear }) {
11
12
  const filtersRef = useRef(null);
12
13
  const [showClearModal, setShowClearModal] = useState(false);
14
+ const [showAlertModal, setShowAlertModal] = useState(false);
15
+ const [alertMessage, setAlertMessage] = useState('');
13
16
 
14
17
  useEffect(() => {
15
18
  if (filtersRef.current) {
@@ -56,7 +59,8 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
56
59
  document.body.removeChild(a);
57
60
  } catch (error) {
58
61
  console.error('Failed to export traffic:', error);
59
- alert('Failed to export traffic. Please try again.');
62
+ setAlertMessage('Failed to export traffic. Please try again.');
63
+ setShowAlertModal(true);
60
64
  }
61
65
  };
62
66
 
@@ -217,11 +221,13 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
217
221
  }
218
222
  } else {
219
223
  const error = await response.json();
220
- alert(`Failed to clear traffic: ${error.error || 'Unknown error'}`);
224
+ setAlertMessage(`Failed to clear traffic: ${error.error || 'Unknown error'}`);
225
+ setShowAlertModal(true);
221
226
  }
222
227
  } catch (error) {
223
228
  console.error('Failed to clear traffic:', error);
224
- alert('Failed to clear traffic. Please try again.');
229
+ setAlertMessage('Failed to clear traffic. Please try again.');
230
+ setShowAlertModal(true);
225
231
  }
226
232
  }}
227
233
  title="Clear All Captured Traffic"
@@ -230,6 +236,14 @@ function RequestFilters({ filters, onFilterChange, stats, onClear }) {
230
236
  cancelText="Cancel"
231
237
  danger={true}
232
238
  />
239
+
240
+ <AlertModal
241
+ isOpen={showAlertModal}
242
+ onClose={() => setShowAlertModal(false)}
243
+ title="Error"
244
+ message={alertMessage}
245
+ type="error"
246
+ />
233
247
  </div>
234
248
  );
235
249
  }