@mcp-shark/mcp-shark 1.4.2 → 1.5.2

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 (204) hide show
  1. package/README.md +84 -645
  2. package/bin/mcp-shark.js +30 -36
  3. package/mcp-server/index.js +115 -0
  4. package/mcp-server/lib/auditor/audit.js +22 -38
  5. package/mcp-server/lib/common/error.js +1 -1
  6. package/mcp-server/lib/server/external/all.js +5 -6
  7. package/mcp-server/lib/server/external/config.js +1 -3
  8. package/mcp-server/lib/server/external/kv.js +4 -12
  9. package/mcp-server/lib/server/external/single/request.js +3 -6
  10. package/mcp-server/lib/server/external/single/run.js +8 -19
  11. package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
  12. package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
  13. package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
  14. package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
  15. package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
  16. package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
  17. package/mcp-server/lib/server/internal/run.js +4 -16
  18. package/mcp-server/lib/server/internal/server.js +6 -7
  19. package/mcp-server/lib/server/internal/session.js +2 -15
  20. package/mcp-server/mcp-shark.js +16 -66
  21. package/package.json +23 -38
  22. package/shared/logger.js +90 -0
  23. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  24. package/ui/dist/assets/index-srLDlk97.js +35 -0
  25. package/ui/dist/index.html +17 -0
  26. package/ui/dist/og-image.png +0 -0
  27. package/ui/server/routes/backups/deleteBackup.js +54 -0
  28. package/ui/server/routes/backups/index.js +15 -0
  29. package/ui/server/routes/backups/listBackups.js +75 -0
  30. package/ui/server/routes/backups/restoreBackup.js +83 -0
  31. package/ui/server/routes/backups/viewBackup.js +47 -0
  32. package/ui/server/routes/composite/index.js +46 -0
  33. package/ui/server/routes/composite/servers.js +18 -0
  34. package/ui/server/routes/composite/setup.js +129 -0
  35. package/ui/server/routes/composite/status.js +7 -0
  36. package/ui/server/routes/composite/stop.js +39 -0
  37. package/ui/server/routes/composite/utils.js +45 -0
  38. package/ui/server/routes/config.js +34 -30
  39. package/ui/server/routes/conversations.js +3 -3
  40. package/ui/server/routes/help.js +2 -2
  41. package/ui/server/routes/logs.js +5 -5
  42. package/ui/server/routes/playground.js +45 -47
  43. package/ui/server/routes/requests.js +112 -108
  44. package/ui/server/routes/sessions.js +4 -4
  45. package/ui/server/routes/settings.js +199 -0
  46. package/ui/server/routes/smartscan/discover.js +7 -6
  47. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  48. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  49. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  51. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  52. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  53. package/ui/server/routes/smartscan/scans.js +3 -3
  54. package/ui/server/routes/smartscan/token.js +4 -3
  55. package/ui/server/routes/smartscan/transport.js +1 -1
  56. package/ui/server/routes/smartscan.js +1 -1
  57. package/ui/server/routes/statistics.js +13 -10
  58. package/ui/server/utils/config-update.js +7 -6
  59. package/ui/server/utils/config.js +4 -4
  60. package/ui/server/utils/logger.js +2 -0
  61. package/ui/server/utils/paths.js +210 -2
  62. package/ui/server/utils/port.js +2 -2
  63. package/ui/server/utils/process.js +0 -67
  64. package/ui/server/utils/scan-cache/all-results.js +76 -59
  65. package/ui/server/utils/scan-cache/directory.js +1 -1
  66. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  67. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  68. package/ui/server/utils/serialization.js +9 -3
  69. package/ui/server/utils/smartscan-token.js +4 -3
  70. package/ui/server.js +86 -41
  71. package/ui/src/App.jsx +5 -5
  72. package/ui/src/CompositeLogs.jsx +20 -20
  73. package/ui/src/CompositeSetup.jsx +9 -9
  74. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  75. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  76. package/ui/src/HelpGuide.jsx +17 -4
  77. package/ui/src/IntroTour.jsx +19 -5
  78. package/ui/src/LogDetail.jsx +1 -0
  79. package/ui/src/LogTable.jsx +24 -6
  80. package/ui/src/PacketDetail.jsx +21 -16
  81. package/ui/src/PacketFilters.jsx +29 -14
  82. package/ui/src/PacketList.jsx +4 -5
  83. package/ui/src/SmartScan.jsx +5 -5
  84. package/ui/src/TabNavigation.jsx +5 -5
  85. package/ui/src/components/App/HelpButton.jsx +4 -0
  86. package/ui/src/components/App/TrafficTab.jsx +4 -4
  87. package/ui/src/components/App/useAppState.js +118 -24
  88. package/ui/src/components/BackupList.jsx +6 -2
  89. package/ui/src/components/CollapsibleSection.jsx +16 -2
  90. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  91. package/ui/src/components/ConfirmationModal.jsx +20 -3
  92. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  93. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  94. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  95. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  97. package/ui/src/components/DetectedPathsList.jsx +5 -2
  98. package/ui/src/components/FileInput.jsx +3 -1
  99. package/ui/src/components/GroupHeader.jsx +14 -0
  100. package/ui/src/components/GroupedByMcpView.jsx +3 -4
  101. package/ui/src/components/GroupedByServerView.jsx +1 -1
  102. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  103. package/ui/src/components/HexTab.jsx +17 -4
  104. package/ui/src/components/LogsToolbar.jsx +3 -1
  105. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
  108. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
  109. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
  112. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
  113. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
  116. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
  117. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  118. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
  119. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
  120. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
  121. package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
  122. package/ui/src/components/McpPlayground.jsx +5 -2
  123. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  124. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  125. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  126. package/ui/src/components/RawTab.jsx +15 -2
  127. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  128. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  129. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  130. package/ui/src/components/RequestRow.jsx +17 -9
  131. package/ui/src/components/ServerControl.jsx +3 -1
  132. package/ui/src/components/ServiceSelector.jsx +2 -0
  133. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  136. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  137. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  138. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  139. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  140. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  141. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  142. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  143. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  144. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  145. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  146. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  147. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  148. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  149. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  150. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  151. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  152. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  153. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  154. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  155. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  156. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  157. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  158. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  159. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  160. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  161. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  162. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  163. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  164. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  165. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  166. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  167. package/ui/src/components/SmartScan/utils.js +3 -1
  168. package/ui/src/components/SmartScanIcons.jsx +6 -3
  169. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  170. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  171. package/ui/src/components/TabNavigation.jsx +8 -3
  172. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  173. package/ui/src/components/TourOverlay.jsx +1 -1
  174. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  175. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  176. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  177. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  178. package/ui/src/components/TourTooltip.jsx +11 -3
  179. package/ui/src/components/ViewModeTabs.jsx +3 -1
  180. package/ui/src/config/tourSteps.jsx +0 -2
  181. package/ui/src/hooks/useAnimation.js +15 -12
  182. package/ui/src/hooks/useConfigManagement.js +8 -8
  183. package/ui/src/hooks/useServiceExtraction.js +1 -1
  184. package/ui/src/index.css +3 -8
  185. package/ui/src/theme.js +3 -3
  186. package/ui/src/utils/hexUtils.js +11 -5
  187. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  188. package/ui/src/utils/requestPairing.js +89 -0
  189. package/ui/src/utils/requestUtils.js +32 -101
  190. package/ui/vite.config.js +1 -1
  191. package/mcp-server/.editorconfig +0 -15
  192. package/mcp-server/.prettierignore +0 -11
  193. package/mcp-server/.prettierrc +0 -12
  194. package/mcp-server/README.md +0 -280
  195. package/mcp-server/commitlint.config.cjs +0 -42
  196. package/mcp-server/eslint.config.js +0 -131
  197. package/mcp-server/package-lock.json +0 -4784
  198. package/mcp-server/package.json +0 -30
  199. package/ui/README.md +0 -212
  200. package/ui/package-lock.json +0 -3574
  201. package/ui/package.json +0 -12
  202. package/ui/paths.js +0 -282
  203. package/ui/server/routes/backups.js +0 -251
  204. package/ui/server/routes/composite.js +0 -260
@@ -1,5 +1,6 @@
1
- import { readFileSync, existsSync, readdirSync, unlinkSync } from 'node:fs';
1
+ import { readFileSync, readdirSync, unlinkSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import logger from '../logger.js';
3
4
  import { ensureScanResultsDirectory } from './directory.js';
4
5
 
5
6
  /**
@@ -30,7 +31,7 @@ export function getCachedScanResultsForServer(serverName) {
30
31
  }
31
32
  } catch (error) {
32
33
  // Skip files that can't be parsed
33
- console.warn(`Error reading scan result file ${file}:`, error);
34
+ logger.warn({ file, error: error.message }, 'Error reading scan result file');
34
35
  }
35
36
  }
36
37
 
@@ -39,7 +40,7 @@ export function getCachedScanResultsForServer(serverName) {
39
40
 
40
41
  return results;
41
42
  } catch (error) {
42
- console.error('Error getting cached scan results for server:', error);
43
+ logger.error({ error: error.message }, 'Error getting cached scan results for server');
43
44
  return [];
44
45
  }
45
46
  }
@@ -54,9 +55,8 @@ export function clearOldScanResults(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
54
55
  const scanResultsDir = ensureScanResultsDirectory();
55
56
  const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
56
57
  const cutoffTime = Date.now() - maxAgeMs;
57
- let deletedCount = 0;
58
58
 
59
- for (const file of files) {
59
+ const deletedCount = files.reduce((count, file) => {
60
60
  try {
61
61
  const filePath = join(scanResultsDir, file);
62
62
  const fileContent = readFileSync(filePath, 'utf8');
@@ -64,17 +64,22 @@ export function clearOldScanResults(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
64
64
 
65
65
  if (data.updatedAt && data.updatedAt < cutoffTime) {
66
66
  unlinkSync(filePath);
67
- deletedCount++;
67
+ return count + 1;
68
68
  }
69
+ return count;
69
70
  } catch (error) {
70
71
  // Skip files that can't be parsed
71
- console.warn(`Error processing scan result file ${file} for cleanup:`, error);
72
+ logger.warn(
73
+ { file, error: error.message },
74
+ 'Error processing scan result file for cleanup'
75
+ );
76
+ return count;
72
77
  }
73
- }
78
+ }, 0);
74
79
 
75
80
  return deletedCount;
76
81
  } catch (error) {
77
- console.error('Error clearing old scan results:', error);
82
+ logger.error({ error: error.message }, 'Error clearing old scan results');
78
83
  return 0;
79
84
  }
80
85
  }
@@ -1,7 +1,13 @@
1
1
  export function serializeBigInt(obj) {
2
- if (obj === null || obj === undefined) return obj;
3
- if (typeof obj === 'bigint') return obj.toString();
4
- if (Array.isArray(obj)) return obj.map(serializeBigInt);
2
+ if (obj === null || obj === undefined) {
3
+ return obj;
4
+ }
5
+ if (typeof obj === 'bigint') {
6
+ return obj.toString();
7
+ }
8
+ if (Array.isArray(obj)) {
9
+ return obj.map(serializeBigInt);
10
+ }
5
11
  if (typeof obj === 'object') {
6
12
  const result = {};
7
13
  for (const [key, value] of Object.entries(obj)) {
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { getWorkingDirectory, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
3
2
  import { join } from 'node:path';
3
+ import { getWorkingDirectory, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
4
+ import logger from './logger.js';
4
5
 
5
6
  const SMART_SCAN_TOKEN_NAME = 'smart-scan-token.json';
6
7
 
@@ -18,7 +19,7 @@ export function readSmartScanToken() {
18
19
  }
19
20
  return null;
20
21
  } catch (error) {
21
- console.error('Error reading Smart Scan token:', error);
22
+ logger.error({ error: error.message }, 'Error reading Smart Scan token');
22
23
  return null;
23
24
  }
24
25
  }
@@ -36,7 +37,7 @@ export function writeSmartScanToken(token) {
36
37
  writeFileSync(tokenPath, JSON.stringify(data, null, 2), { mode: 0o600 }); // Read/write for owner only
37
38
  return true;
38
39
  } catch (error) {
39
- console.error('Error writing Smart Scan token:', error);
40
+ logger.error({ error: error.message }, 'Error writing Smart Scan token');
40
41
  return false;
41
42
  }
42
43
  }
package/ui/server.js CHANGED
@@ -1,34 +1,32 @@
1
+ import { createServer } from 'node:http';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
1
4
  import express from 'express';
2
- import { createServer } from 'http';
3
5
  import { WebSocketServer } from 'ws';
4
- import * as path from 'node:path';
5
- import { fileURLToPath, pathToFileURL } from 'url';
6
6
 
7
+ import { getDatabaseFile, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
7
8
  import { openDb } from 'mcp-shark-common/db/init.js';
8
- import {
9
- getDatabaseFile,
10
- prepareAppDataSpaces,
11
- getMcpConfigPath,
12
- } from 'mcp-shark-common/configs/index.js';
13
9
  import { queryRequests } from 'mcp-shark-common/db/query.js';
14
10
  import { restoreOriginalConfig } from './server/utils/config.js';
15
11
 
16
- import { createRequestsRoutes } from './server/routes/requests.js';
17
- import { createConversationsRoutes } from './server/routes/conversations.js';
18
- import { createSessionsRoutes } from './server/routes/sessions.js';
19
- import { createStatisticsRoutes } from './server/routes/statistics.js';
20
- import { createLogsRoutes } from './server/routes/logs.js';
12
+ import { createBackupRoutes } from './server/routes/backups/index.js';
13
+ import { createCompositeRoutes } from './server/routes/composite/index.js';
21
14
  import { createConfigRoutes } from './server/routes/config.js';
22
- import { createBackupRoutes } from './server/routes/backups.js';
23
- import { createCompositeRoutes } from './server/routes/composite.js';
15
+ import { createConversationsRoutes } from './server/routes/conversations.js';
24
16
  import { createHelpRoutes } from './server/routes/help.js';
17
+ import { createLogsRoutes } from './server/routes/logs.js';
25
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';
26
22
  import { createSmartScanRoutes } from './server/routes/smartscan.js';
23
+ import { createStatisticsRoutes } from './server/routes/statistics.js';
24
+ import { serializeBigInt } from './server/utils/serialization.js';
27
25
 
28
26
  const __filename = fileURLToPath(import.meta.url);
29
27
  const __dirname = path.dirname(__filename);
30
28
 
31
- const MAX_LOG_LINES = 10000;
29
+ const _MAX_LOG_LINES = 10000;
32
30
 
33
31
  export function createUIServer() {
34
32
  prepareAppDataSpaces();
@@ -42,10 +40,10 @@ export function createUIServer() {
42
40
 
43
41
  const clients = new Set();
44
42
  const mcpSharkLogs = [];
45
- const processState = { mcpSharkProcess: null };
43
+ const processState = { mcpSharkServer: null };
46
44
 
47
- const setMcpSharkProcess = (process) => {
48
- processState.mcpSharkProcess = process;
45
+ const setMcpSharkProcess = (server) => {
46
+ processState.mcpSharkServer = server;
49
47
  };
50
48
 
51
49
  wss.on('connection', (ws) => {
@@ -73,7 +71,7 @@ export function createUIServer() {
73
71
  const logsRoutes = createLogsRoutes(mcpSharkLogs, broadcastLogUpdate);
74
72
  const configRoutes = createConfigRoutes();
75
73
  const backupRoutes = createBackupRoutes();
76
- const getMcpSharkProcess = () => processState.mcpSharkProcess;
74
+ const getMcpSharkProcess = () => processState.mcpSharkServer;
77
75
  const compositeRoutes = createCompositeRoutes(
78
76
  getMcpSharkProcess,
79
77
  setMcpSharkProcess,
@@ -83,6 +81,7 @@ export function createUIServer() {
83
81
  const helpRoutes = createHelpRoutes();
84
82
  const playgroundRoutes = createPlaygroundRoutes();
85
83
  const smartScanRoutes = createSmartScanRoutes();
84
+ const settingsRoutes = createSettingsRoutes();
86
85
 
87
86
  app.get('/api/requests', requestsRoutes.getRequests);
88
87
  app.get('/api/packets', requestsRoutes.getRequests);
@@ -138,30 +137,17 @@ export function createUIServer() {
138
137
  app.post('/api/smartscan/cached-results', smartScanRoutes.getCachedResults);
139
138
  app.post('/api/smartscan/cache/clear', smartScanRoutes.clearCache);
140
139
 
141
- const cleanup = () => {
142
- if (processState.mcpSharkProcess) {
143
- processState.mcpSharkProcess.kill();
144
- processState.mcpSharkProcess = null;
145
- }
146
- restoreConfig();
147
- };
148
-
149
- process.on('SIGTERM', cleanup);
150
- process.on('SIGINT', cleanup);
151
- process.on('exit', () => {
152
- restoreConfig();
153
- });
140
+ app.get('/api/settings', settingsRoutes.getSettings);
154
141
 
155
142
  const staticPath = path.join(__dirname, 'dist');
156
143
  app.use(express.static(staticPath));
157
144
 
158
- app.get('*', (req, res) => {
145
+ app.get('*', (_req, res) => {
159
146
  res.sendFile(path.join(staticPath, 'index.html'));
160
147
  });
161
148
 
162
149
  const notifyClients = async () => {
163
150
  const requests = queryRequests(db, { limit: 100 });
164
- const { serializeBigInt } = await import('./server/utils/serialization.js');
165
151
  const message = JSON.stringify({ type: 'update', data: serializeBigInt(requests) });
166
152
  clients.forEach((client) => {
167
153
  if (client.readyState === 1) {
@@ -171,7 +157,7 @@ export function createUIServer() {
171
157
  };
172
158
 
173
159
  const timestampState = { lastTs: 0 };
174
- setInterval(() => {
160
+ const intervalId = setInterval(() => {
175
161
  const lastCheck = db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets').get();
176
162
  if (lastCheck && lastCheck.max_ts > timestampState.lastTs) {
177
163
  timestampState.lastTs = lastCheck.max_ts;
@@ -179,19 +165,78 @@ export function createUIServer() {
179
165
  }
180
166
  }, 500);
181
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
+
182
209
  return { server, cleanup };
183
210
  }
184
211
 
185
212
  export async function runUIServer() {
186
- const port = parseInt(process.env.UI_PORT) || 9853;
213
+ const port = Number.parseInt(process.env.UI_PORT) || 9853;
187
214
  const { server, cleanup } = createUIServer();
188
215
 
189
- server.listen(port, '0.0.0.0', () => {
190
- console.log(`UI server listening on http://localhost:${port}`);
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
+ };
225
+
226
+ // 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
235
+ }
191
236
  });
192
237
 
193
- server.on('close', () => {
194
- cleanup();
238
+ server.listen(port, '0.0.0.0', () => {
239
+ console.log(`UI server listening on http://localhost:${port}`);
195
240
  });
196
241
  }
197
242
 
package/ui/src/App.jsx CHANGED
@@ -1,16 +1,16 @@
1
1
  import { useEffect, useState } from 'react';
2
- import CompositeSetup from './CompositeSetup';
3
2
  import CompositeLogs from './CompositeLogs';
4
- import McpPlayground from './components/McpPlayground';
3
+ import CompositeSetup from './CompositeSetup';
4
+ import IntroTour from './IntroTour';
5
5
  import SmartScan from './SmartScan';
6
6
  import TabNavigation from './TabNavigation';
7
- import IntroTour from './IntroTour';
8
7
  import HelpButton from './components/App/HelpButton';
9
8
  import TrafficTab from './components/App/TrafficTab';
10
- import { colors } from './theme';
9
+ import { useAppState } from './components/App/useAppState';
10
+ import McpPlayground from './components/McpPlayground';
11
11
  import { tourSteps } from './config/tourSteps.jsx';
12
+ import { colors } from './theme';
12
13
  import { fadeIn } from './utils/animations';
13
- import { useAppState } from './components/App/useAppState';
14
14
 
15
15
  function App() {
16
16
  const {
@@ -1,7 +1,7 @@
1
- import { useState, useEffect, useRef } from 'react';
2
- import { colors } from './theme';
3
- import LogsToolbar from './components/LogsToolbar';
1
+ import { useEffect, useRef, useState } from 'react';
4
2
  import LogsDisplay from './components/LogsDisplay';
3
+ import LogsToolbar from './components/LogsToolbar';
4
+ import { colors } from './theme';
5
5
 
6
6
  function CompositeLogs() {
7
7
  const [logs, setLogs] = useState([]);
@@ -11,27 +11,23 @@ function CompositeLogs() {
11
11
  const logEndRef = useRef(null);
12
12
  const wsRef = useRef(null);
13
13
 
14
- const scrollToTop = () => {
14
+ useEffect(() => {
15
15
  if (autoScroll && logEndRef.current) {
16
16
  logEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
17
17
  }
18
- };
18
+ }, [autoScroll]);
19
19
 
20
20
  useEffect(() => {
21
- scrollToTop();
22
- }, [logs, autoScroll]);
23
-
24
- const loadLogs = async () => {
25
- try {
26
- const response = await fetch('/api/composite/logs?limit=5000');
27
- const data = await response.json();
28
- setLogs(data);
29
- } catch (error) {
30
- console.error('Failed to load logs:', error);
31
- }
32
- };
21
+ const loadLogs = async () => {
22
+ try {
23
+ const response = await fetch('/api/composite/logs?limit=5000');
24
+ const data = await response.json();
25
+ setLogs(data);
26
+ } catch (error) {
27
+ console.error('Failed to load logs:', error);
28
+ }
29
+ };
33
30
 
34
- useEffect(() => {
35
31
  loadLogs();
36
32
 
37
33
  const wsUrl = import.meta.env.DEV
@@ -106,8 +102,12 @@ function CompositeLogs() {
106
102
  };
107
103
 
108
104
  const filteredLogs = logs.filter((log) => {
109
- if (logType !== 'all' && log.type !== logType) return false;
110
- if (filter && !log.line.toLowerCase().includes(filter.toLowerCase())) return false;
105
+ if (logType !== 'all' && log.type !== logType) {
106
+ return false;
107
+ }
108
+ if (filter && !log.line.toLowerCase().includes(filter.toLowerCase())) {
109
+ return false;
110
+ }
111
111
  return true;
112
112
  });
113
113
 
@@ -1,14 +1,14 @@
1
- import { useState, useEffect } from 'react';
2
- import { colors } from './theme';
3
- import SetupHeader from './components/SetupHeader';
4
- import ConfigFileSection from './components/ConfigFileSection';
5
- import WhatThisDoesSection from './components/WhatThisDoesSection';
6
- import ServerControl from './components/ServerControl';
7
- import MessageDisplay from './components/MessageDisplay';
1
+ import { useEffect, useState } from 'react';
8
2
  import BackupList from './components/BackupList';
3
+ import ConfigFileSection from './components/ConfigFileSection';
9
4
  import ConfigViewerModal from './components/ConfigViewerModal';
10
- import { useServiceExtraction } from './hooks/useServiceExtraction';
5
+ import MessageDisplay from './components/MessageDisplay';
6
+ import ServerControl from './components/ServerControl';
7
+ import SetupHeader from './components/SetupHeader';
8
+ import WhatThisDoesSection from './components/WhatThisDoesSection';
11
9
  import { useConfigManagement } from './hooks/useConfigManagement';
10
+ import { useServiceExtraction } from './hooks/useServiceExtraction';
11
+ import { colors } from './theme';
12
12
 
13
13
  function CompositeSetup() {
14
14
  const [fileContent, setFileContent] = useState('');
@@ -181,7 +181,7 @@ function CompositeSetup() {
181
181
  if (res.ok) {
182
182
  const msg = data.message || 'MCP Shark server stopped';
183
183
  setMessage(
184
- data.message && data.message.includes('restored')
184
+ data.message?.includes('restored')
185
185
  ? 'MCP Shark server stopped and original config file restored'
186
186
  : msg
187
187
  );
@@ -32,6 +32,7 @@ export default function HelpGuideFooter({ dontShowAgain, setDontShowAgain, onClo
32
32
  Don't show this again
33
33
  </label>
34
34
  <button
35
+ type="button"
35
36
  onClick={onClose}
36
37
  style={{
37
38
  background: colors.buttonPrimary,
@@ -1,5 +1,5 @@
1
- import { colors, fonts } from '../theme';
2
1
  import { IconHelp, IconX } from '@tabler/icons-react';
2
+ import { colors, fonts } from '../theme';
3
3
 
4
4
  export default function HelpGuideHeader({ onClose }) {
5
5
  return (
@@ -29,6 +29,7 @@ export default function HelpGuideHeader({ onClose }) {
29
29
  </h2>
30
30
  </div>
31
31
  <button
32
+ type="button"
32
33
  onClick={onClose}
33
34
  style={{
34
35
  background: 'transparent',
@@ -1,8 +1,8 @@
1
1
  import { useState } from 'react';
2
- import { colors } from './theme';
3
- import HelpGuideHeader from './HelpGuide/HelpGuideHeader';
4
2
  import HelpGuideContent from './HelpGuide/HelpGuideContent';
5
3
  import HelpGuideFooter from './HelpGuide/HelpGuideFooter';
4
+ import HelpGuideHeader from './HelpGuide/HelpGuideHeader';
5
+ import { colors } from './theme';
6
6
 
7
7
  function HelpGuide({ onClose }) {
8
8
  const [dontShowAgain, setDontShowAgain] = useState(false);
@@ -19,7 +19,9 @@ function HelpGuide({ onClose }) {
19
19
  };
20
20
 
21
21
  return (
22
- <div
22
+ <dialog
23
+ open
24
+ aria-modal="true"
23
25
  style={{
24
26
  position: 'fixed',
25
27
  top: 0,
@@ -32,10 +34,20 @@ function HelpGuide({ onClose }) {
32
34
  alignItems: 'center',
33
35
  justifyContent: 'center',
34
36
  padding: '20px',
37
+ border: 'none',
38
+ margin: 0,
39
+ width: '100%',
40
+ height: '100%',
35
41
  }}
36
42
  onClick={handleClose}
43
+ onKeyDown={(e) => {
44
+ if (e.key === 'Escape') {
45
+ handleClose();
46
+ }
47
+ }}
37
48
  >
38
49
  <div
50
+ role="document"
39
51
  style={{
40
52
  background: colors.bgCard,
41
53
  border: `1px solid ${colors.borderLight}`,
@@ -47,6 +59,7 @@ function HelpGuide({ onClose }) {
47
59
  boxShadow: '0 8px 32px rgba(0, 0, 0, 0.5)',
48
60
  }}
49
61
  onClick={(e) => e.stopPropagation()}
62
+ onKeyDown={(e) => e.stopPropagation()}
50
63
  >
51
64
  <HelpGuideHeader onClose={handleClose} />
52
65
  <div style={{ padding: '24px' }}>
@@ -58,7 +71,7 @@ function HelpGuide({ onClose }) {
58
71
  />
59
72
  </div>
60
73
  </div>
61
- </div>
74
+ </dialog>
62
75
  );
63
76
  }
64
77
 
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
  import TourOverlay from './components/TourOverlay';
3
3
  import TourTooltip from './components/TourTooltip';
4
4
 
@@ -9,7 +9,9 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
9
9
  const overlayRef = useRef(null);
10
10
 
11
11
  useEffect(() => {
12
- if (!highlightedElement) return;
12
+ if (!highlightedElement) {
13
+ return;
14
+ }
13
15
 
14
16
  const updatePosition = () => {
15
17
  if (highlightedElement) {
@@ -31,10 +33,14 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
31
33
  }, [highlightedElement]);
32
34
 
33
35
  useEffect(() => {
34
- if (steps.length === 0) return;
36
+ if (steps.length === 0) {
37
+ return;
38
+ }
35
39
 
36
40
  const step = steps[currentStep];
37
- if (!step) return;
41
+ if (!step) {
42
+ return;
43
+ }
38
44
 
39
45
  if (onStepChange) {
40
46
  onStepChange(currentStep);
@@ -95,7 +101,9 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
95
101
 
96
102
  const handleSkip = () => {
97
103
  handleComplete();
98
- if (onSkip) onSkip();
104
+ if (onSkip) {
105
+ onSkip();
106
+ }
99
107
  };
100
108
 
101
109
  if (steps.length === 0 || currentStep >= steps.length) {
@@ -108,6 +116,7 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
108
116
  <>
109
117
  <div
110
118
  ref={overlayRef}
119
+ role="presentation"
111
120
  style={{
112
121
  position: 'fixed',
113
122
  top: 0,
@@ -118,6 +127,11 @@ function IntroTour({ steps, onComplete, onSkip, onStepChange }) {
118
127
  pointerEvents: 'auto',
119
128
  }}
120
129
  onClick={handleSkip}
130
+ onKeyDown={(e) => {
131
+ if (e.key === 'Escape') {
132
+ handleSkip();
133
+ }
134
+ }}
121
135
  >
122
136
  <TourOverlay elementRect={elementRect} />
123
137
  </div>
@@ -16,6 +16,7 @@ function LogDetail({ log, onClose }) {
16
16
  >
17
17
  <h3 style={{ fontSize: '14px', fontWeight: 'normal' }}>Details</h3>
18
18
  <button
19
+ type="button"
19
20
  onClick={onClose}
20
21
  style={{
21
22
  background: 'none',
@@ -1,10 +1,16 @@
1
- import { colors, fonts, withOpacity } from './theme';
1
+ import { colors, fonts } from './theme';
2
2
 
3
3
  function LogTable({ logs, selected, onSelect }) {
4
4
  const getStatusColor = (status) => {
5
- if (status === 'error') return colors.error;
6
- if (status === 'success') return colors.success;
7
- if (status === 'pending') return colors.warning;
5
+ if (status === 'error') {
6
+ return colors.error;
7
+ }
8
+ if (status === 'success') {
9
+ return colors.success;
10
+ }
11
+ if (status === 'pending') {
12
+ return colors.warning;
13
+ }
8
14
  return colors.textTertiary;
9
15
  };
10
16
 
@@ -144,6 +150,14 @@ function LogTable({ logs, selected, onSelect }) {
144
150
  <tr
145
151
  key={log.id}
146
152
  onClick={() => onSelect(log)}
153
+ onKeyDown={(e) => {
154
+ if (e.key === 'Enter' || e.key === ' ') {
155
+ e.preventDefault();
156
+ onSelect(log);
157
+ }
158
+ }}
159
+ tabIndex={0}
160
+ aria-label={`Select log entry ${log.id}`}
147
161
  style={{
148
162
  cursor: 'pointer',
149
163
  background: selected?.id === log.id ? colors.bgSelected : colors.bgCard,
@@ -151,10 +165,14 @@ function LogTable({ logs, selected, onSelect }) {
151
165
  transition: 'background-color 0.15s ease',
152
166
  }}
153
167
  onMouseEnter={(e) => {
154
- if (selected?.id !== log.id) e.currentTarget.style.background = colors.bgHover;
168
+ if (selected?.id !== log.id) {
169
+ e.currentTarget.style.background = colors.bgHover;
170
+ }
155
171
  }}
156
172
  onMouseLeave={(e) => {
157
- if (selected?.id !== log.id) e.currentTarget.style.background = colors.bgCard;
173
+ if (selected?.id !== log.id) {
174
+ e.currentTarget.style.background = colors.bgCard;
175
+ }
158
176
  }}
159
177
  >
160
178
  <td