@mcp-shark/mcp-shark 1.4.1 → 1.5.0

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 (203) 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 +34 -42
  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 +21 -40
  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 +5 -7
  12. package/mcp-server/lib/server/internal/handlers/prompts-list.js +5 -4
  13. package/mcp-server/lib/server/internal/handlers/resources-list.js +5 -4
  14. package/mcp-server/lib/server/internal/handlers/resources-read.js +5 -4
  15. package/mcp-server/lib/server/internal/handlers/tools-call.js +8 -10
  16. package/mcp-server/lib/server/internal/handlers/tools-list.js +6 -5
  17. package/mcp-server/lib/server/internal/run.js +5 -17
  18. package/mcp-server/lib/server/internal/server.js +14 -15
  19. package/mcp-server/lib/server/internal/session.js +8 -13
  20. package/mcp-server/mcp-shark.js +16 -66
  21. package/package.json +23 -38
  22. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  23. package/ui/dist/assets/index-srLDlk97.js +35 -0
  24. package/ui/dist/index.html +17 -0
  25. package/ui/dist/og-image.png +0 -0
  26. package/ui/server/routes/backups/deleteBackup.js +54 -0
  27. package/ui/server/routes/backups/index.js +15 -0
  28. package/ui/server/routes/backups/listBackups.js +75 -0
  29. package/ui/server/routes/backups/restoreBackup.js +83 -0
  30. package/ui/server/routes/backups/viewBackup.js +47 -0
  31. package/ui/server/routes/composite/index.js +46 -0
  32. package/ui/server/routes/composite/servers.js +18 -0
  33. package/ui/server/routes/composite/setup.js +129 -0
  34. package/ui/server/routes/composite/status.js +7 -0
  35. package/ui/server/routes/composite/stop.js +39 -0
  36. package/ui/server/routes/composite/utils.js +45 -0
  37. package/ui/server/routes/config.js +34 -30
  38. package/ui/server/routes/conversations.js +3 -3
  39. package/ui/server/routes/help.js +2 -2
  40. package/ui/server/routes/logs.js +5 -5
  41. package/ui/server/routes/playground.js +91 -62
  42. package/ui/server/routes/requests.js +112 -108
  43. package/ui/server/routes/sessions.js +4 -4
  44. package/ui/server/routes/settings.js +199 -0
  45. package/ui/server/routes/smartscan/discover.js +7 -6
  46. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  47. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  48. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  49. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  51. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  52. package/ui/server/routes/smartscan/scans.js +3 -3
  53. package/ui/server/routes/smartscan/token.js +4 -3
  54. package/ui/server/routes/smartscan/transport.js +1 -1
  55. package/ui/server/routes/smartscan.js +1 -1
  56. package/ui/server/routes/statistics.js +13 -10
  57. package/ui/server/utils/config-update.js +140 -112
  58. package/ui/server/utils/config.js +4 -4
  59. package/ui/server/utils/logger.js +2 -0
  60. package/ui/server/utils/paths.js +210 -2
  61. package/ui/server/utils/port.js +2 -2
  62. package/ui/server/utils/process.js +0 -67
  63. package/ui/server/utils/scan-cache/all-results.js +76 -59
  64. package/ui/server/utils/scan-cache/directory.js +1 -1
  65. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  66. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  67. package/ui/server/utils/serialization.js +9 -3
  68. package/ui/server/utils/smartscan-token.js +4 -3
  69. package/ui/server.js +87 -41
  70. package/ui/src/App.jsx +5 -5
  71. package/ui/src/CompositeLogs.jsx +20 -20
  72. package/ui/src/CompositeSetup.jsx +9 -9
  73. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  74. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  75. package/ui/src/HelpGuide.jsx +17 -4
  76. package/ui/src/IntroTour.jsx +19 -5
  77. package/ui/src/LogDetail.jsx +1 -0
  78. package/ui/src/LogTable.jsx +24 -6
  79. package/ui/src/PacketDetail.jsx +21 -16
  80. package/ui/src/PacketFilters.jsx +29 -14
  81. package/ui/src/PacketList.jsx +4 -5
  82. package/ui/src/SmartScan.jsx +5 -5
  83. package/ui/src/TabNavigation.jsx +5 -5
  84. package/ui/src/components/App/HelpButton.jsx +4 -0
  85. package/ui/src/components/App/TrafficTab.jsx +4 -4
  86. package/ui/src/components/App/useAppState.js +118 -24
  87. package/ui/src/components/BackupList.jsx +6 -2
  88. package/ui/src/components/CollapsibleSection.jsx +16 -2
  89. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  90. package/ui/src/components/ConfirmationModal.jsx +20 -3
  91. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  92. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  93. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  94. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  95. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetectedPathsList.jsx +5 -2
  97. package/ui/src/components/FileInput.jsx +3 -1
  98. package/ui/src/components/GroupHeader.jsx +14 -0
  99. package/ui/src/components/GroupedByMcpView.jsx +3 -10
  100. package/ui/src/components/GroupedByServerView.jsx +1 -1
  101. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  102. package/ui/src/components/HexTab.jsx +17 -4
  103. package/ui/src/components/LogsToolbar.jsx +3 -1
  104. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  105. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +52 -23
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +13 -11
  108. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  109. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +66 -34
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +13 -11
  112. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  113. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +52 -23
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +13 -11
  116. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  117. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
  118. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +70 -0
  119. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +90 -0
  120. package/ui/src/components/McpPlayground/useMcpPlayground.js +118 -159
  121. package/ui/src/components/McpPlayground.jsx +105 -23
  122. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  123. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  124. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  125. package/ui/src/components/RawTab.jsx +15 -2
  126. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  127. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  128. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  129. package/ui/src/components/RequestRow.jsx +17 -9
  130. package/ui/src/components/ServerControl.jsx +3 -1
  131. package/ui/src/components/ServiceSelector.jsx +2 -0
  132. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  133. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  136. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  137. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  138. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  139. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  140. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  141. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  142. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  143. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  144. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  145. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  146. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  147. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  148. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  149. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  150. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  151. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  152. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  153. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  154. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  155. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  156. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  157. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  158. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  159. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  160. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  161. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  162. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  163. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  164. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  165. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  166. package/ui/src/components/SmartScan/utils.js +3 -1
  167. package/ui/src/components/SmartScanIcons.jsx +6 -3
  168. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  169. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  170. package/ui/src/components/TabNavigation.jsx +8 -3
  171. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  172. package/ui/src/components/TourOverlay.jsx +1 -1
  173. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  174. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  175. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  176. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  177. package/ui/src/components/TourTooltip.jsx +11 -3
  178. package/ui/src/components/ViewModeTabs.jsx +3 -1
  179. package/ui/src/config/tourSteps.jsx +0 -2
  180. package/ui/src/hooks/useAnimation.js +15 -12
  181. package/ui/src/hooks/useConfigManagement.js +8 -8
  182. package/ui/src/hooks/useServiceExtraction.js +1 -1
  183. package/ui/src/index.css +3 -8
  184. package/ui/src/theme.js +3 -3
  185. package/ui/src/utils/hexUtils.js +11 -5
  186. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  187. package/ui/src/utils/requestPairing.js +89 -0
  188. package/ui/src/utils/requestUtils.js +37 -105
  189. package/ui/vite.config.js +1 -1
  190. package/mcp-server/.editorconfig +0 -15
  191. package/mcp-server/.prettierignore +0 -11
  192. package/mcp-server/.prettierrc +0 -12
  193. package/mcp-server/README.md +0 -280
  194. package/mcp-server/commitlint.config.cjs +0 -42
  195. package/mcp-server/eslint.config.js +0 -131
  196. package/mcp-server/package-lock.json +0 -4784
  197. package/mcp-server/package.json +0 -30
  198. package/ui/README.md +0 -212
  199. package/ui/package-lock.json +0 -3574
  200. package/ui/package.json +0 -12
  201. package/ui/paths.js +0 -282
  202. package/ui/server/routes/backups.js +0 -251
  203. package/ui/server/routes/composite.js +0 -244
@@ -1,5 +1,6 @@
1
- import { readFileSync, existsSync, readdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import logger from '../logger.js';
3
4
  import { getScanResultsDirectory } from './directory.js';
4
5
 
5
6
  /**
@@ -9,13 +10,13 @@ import { getScanResultsDirectory } from './directory.js';
9
10
  * @returns {string} Server name
10
11
  */
11
12
  function extractServerName(data, scanData) {
12
- let serverName = data.serverName; // From cache file metadata (most reliable)
13
+ const cachedServerName = data.serverName; // From cache file metadata (most reliable)
13
14
 
14
15
  // If not found at top level, try to extract from scan data (API response)
15
- if (!serverName || serverName === 'Unknown Server') {
16
+ if (!cachedServerName || cachedServerName === 'Unknown Server') {
16
17
  const actualScanData = scanData.data || scanData;
17
18
 
18
- serverName =
19
+ const serverName =
19
20
  actualScanData.serverName ||
20
21
  actualScanData.server_name ||
21
22
  actualScanData.server?.name ||
@@ -25,9 +26,11 @@ function extractServerName(data, scanData) {
25
26
  scanData.server?.name ||
26
27
  scanData.mcp_server_data?.server?.name ||
27
28
  'Unknown Server';
29
+
30
+ return serverName;
28
31
  }
29
32
 
30
- return serverName;
33
+ return cachedServerName;
31
34
  }
32
35
 
33
36
  /**
@@ -39,25 +42,29 @@ function extractServerName(data, scanData) {
39
42
  function processScanFile(file, scanResultsDir) {
40
43
  try {
41
44
  const filePath = join(scanResultsDir, file);
42
- console.log(`[getAllCachedScanResults] Reading file: ${file}`);
45
+ logger.debug({ file }, 'Reading cached scan result file');
43
46
 
44
47
  const fileContent = readFileSync(filePath, 'utf8');
45
48
  const data = JSON.parse(fileContent);
46
49
 
47
50
  // Validate that this is a scan result file
48
51
  if (!data || typeof data !== 'object') {
49
- console.warn(`[getAllCachedScanResults] Invalid data in file ${file}: not an object`);
52
+ logger.warn({ file }, 'Invalid data in file: not an object');
50
53
  return null;
51
54
  }
52
55
 
53
56
  // Debug: Log the structure to understand what we're working with
54
- console.log(`[getAllCachedScanResults] File ${file} structure:`, {
55
- hasServerName: !!data.serverName,
56
- serverName: data.serverName,
57
- hasScanData: !!data.scanData,
58
- scanDataKeys: data.scanData ? Object.keys(data.scanData) : [],
59
- topLevelKeys: Object.keys(data),
60
- });
57
+ logger.debug(
58
+ {
59
+ file,
60
+ hasServerName: !!data.serverName,
61
+ serverName: data.serverName,
62
+ hasScanData: !!data.scanData,
63
+ scanDataKeys: data.scanData ? Object.keys(data.scanData) : [],
64
+ topLevelKeys: Object.keys(data),
65
+ },
66
+ 'File structure'
67
+ );
61
68
 
62
69
  // Create a scan-like object with id, server name, and scan data
63
70
  const scanData = data.scanData || data;
@@ -66,17 +73,16 @@ function processScanFile(file, scanResultsDir) {
66
73
 
67
74
  // Log if we couldn't find server name
68
75
  if (serverName === 'Unknown Server') {
69
- console.warn(`[getAllCachedScanResults] Could not find server name in ${file}`);
70
- console.warn(`[getAllCachedScanResults] data.serverName: ${data.serverName}`);
71
- console.warn(`[getAllCachedScanResults] data keys: ${Object.keys(data || {}).join(', ')}`);
72
- console.warn(
73
- `[getAllCachedScanResults] scanData keys: ${Object.keys(scanData || {}).join(', ')}`
76
+ logger.warn(
77
+ {
78
+ file,
79
+ dataServerName: data.serverName,
80
+ dataKeys: Object.keys(data || {}).join(', '),
81
+ scanDataKeys: Object.keys(scanData || {}).join(', '),
82
+ scanDataDataKeys: scanData?.data ? Object.keys(scanData.data || {}).join(', ') : null,
83
+ },
84
+ 'Could not find server name in file'
74
85
  );
75
- if (scanData?.data) {
76
- console.warn(
77
- `[getAllCachedScanResults] scanData.data keys: ${Object.keys(scanData.data || {}).join(', ')}`
78
- );
79
- }
80
86
  }
81
87
 
82
88
  const scanResult = {
@@ -98,17 +104,18 @@ function processScanFile(file, scanResultsDir) {
98
104
  result: scanData,
99
105
  };
100
106
 
101
- console.log(`[getAllCachedScanResults] Successfully loaded scan: ${serverName} (${scanId})`);
107
+ logger.debug({ serverName, scanId }, 'Successfully loaded scan');
102
108
  return scanResult;
103
109
  } catch (error) {
104
- console.warn(
105
- `[getAllCachedScanResults] Error reading scan result file ${file}:`,
106
- error.message
110
+ logger.warn(
111
+ {
112
+ file,
113
+ filePath: join(scanResultsDir, file),
114
+ error: error.message,
115
+ stack: error.stack,
116
+ },
117
+ 'Error reading scan result file'
107
118
  );
108
- console.warn(`[getAllCachedScanResults] File path: ${join(scanResultsDir, file)}`);
109
- if (error.stack) {
110
- console.warn(`[getAllCachedScanResults] Stack: ${error.stack}`);
111
- }
112
119
  return null;
113
120
  }
114
121
  }
@@ -120,46 +127,46 @@ function processScanFile(file, scanResultsDir) {
120
127
  export function getAllCachedScanResults() {
121
128
  try {
122
129
  const scanResultsDir = getScanResultsDirectory();
123
- console.log('[getAllCachedScanResults] Reading cached scans from directory:', scanResultsDir);
130
+ logger.debug({ scanResultsDir }, 'Reading cached scans from directory');
124
131
 
125
132
  // Check if directory exists (don't create it, just check)
126
133
  if (!existsSync(scanResultsDir)) {
127
- console.log(
128
- '[getAllCachedScanResults] Scan results directory does not exist:',
129
- scanResultsDir
130
- );
134
+ logger.debug({ scanResultsDir }, 'Scan results directory does not exist');
131
135
  return [];
132
136
  }
133
137
 
134
138
  // Read all files in the directory
135
139
  const allFiles = readdirSync(scanResultsDir);
136
- console.log(`[getAllCachedScanResults] Total files in directory: ${allFiles.length}`);
140
+ logger.debug({ count: allFiles.length }, 'Total files in directory');
137
141
 
138
142
  // Filter for JSON files only
139
143
  const jsonFiles = allFiles.filter((f) => f.endsWith('.json'));
140
- console.log(
141
- `[getAllCachedScanResults] Found ${jsonFiles.length} JSON files in ${scanResultsDir}`
142
- );
144
+ logger.debug({ count: jsonFiles.length, scanResultsDir }, 'Found JSON files');
143
145
 
144
146
  if (jsonFiles.length === 0) {
145
- console.log('[getAllCachedScanResults] No cached scan JSON files found');
147
+ logger.debug('No cached scan JSON files found');
146
148
  return [];
147
149
  }
148
150
 
149
- const results = [];
150
- let successCount = 0;
151
- let errorCount = 0;
152
-
153
151
  // Read each file
154
- for (const file of jsonFiles) {
155
- const result = processScanFile(file, scanResultsDir);
156
- if (result) {
157
- results.push(result);
158
- successCount++;
159
- } else {
160
- errorCount++;
161
- }
162
- }
152
+ const { results, successCount, errorCount } = jsonFiles.reduce(
153
+ (acc, file) => {
154
+ const result = processScanFile(file, scanResultsDir);
155
+ if (result) {
156
+ return {
157
+ results: [...acc.results, result],
158
+ successCount: acc.successCount + 1,
159
+ errorCount: acc.errorCount,
160
+ };
161
+ }
162
+ return {
163
+ results: acc.results,
164
+ successCount: acc.successCount,
165
+ errorCount: acc.errorCount + 1,
166
+ };
167
+ },
168
+ { results: [], successCount: 0, errorCount: 0 }
169
+ );
163
170
 
164
171
  // Sort by updatedAt descending (most recent first)
165
172
  results.sort((a, b) => {
@@ -168,13 +175,23 @@ export function getAllCachedScanResults() {
168
175
  return bTime - aTime;
169
176
  });
170
177
 
171
- console.log(
172
- `[getAllCachedScanResults] Summary: ${successCount} successful, ${errorCount} errors, ${results.length} total scans loaded`
178
+ logger.info(
179
+ {
180
+ successCount,
181
+ errorCount,
182
+ total: results.length,
183
+ },
184
+ 'Summary: cached scans loaded'
173
185
  );
174
186
  return results;
175
187
  } catch (error) {
176
- console.error('[getAllCachedScanResults] Fatal error getting all cached scan results:', error);
177
- console.error('[getAllCachedScanResults] Error stack:', error.stack);
188
+ logger.error(
189
+ {
190
+ error: error.message,
191
+ stack: error.stack,
192
+ },
193
+ 'Fatal error getting all cached scan results'
194
+ );
178
195
  return [];
179
196
  }
180
197
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync } from 'node:fs';
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { getWorkingDirectory } from 'mcp-shark-common/configs/index.js';
4
4
 
@@ -1,5 +1,6 @@
1
- import { readFileSync, writeFileSync, existsSync, unlinkSync, readdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import logger from '../logger.js';
3
4
  import { getScanResultFilePath, getScanResultsDirectory } from './directory.js';
4
5
 
5
6
  /**
@@ -26,7 +27,7 @@ export function getCachedScanResult(hash) {
26
27
  serverName: data.serverName,
27
28
  };
28
29
  } catch (error) {
29
- console.error('Error getting cached scan result:', error);
30
+ logger.error({ hash, error: error.message }, 'Error getting cached scan result');
30
31
  return null;
31
32
  }
32
33
  }
@@ -44,17 +45,19 @@ export function storeScanResult(serverName, hash, scanData) {
44
45
  const now = Date.now();
45
46
 
46
47
  // Check if file exists to preserve original creation time
47
- let createdAt = now;
48
- if (existsSync(filePath)) {
48
+ const getCreatedAt = (filePath, defaultTime) => {
49
+ if (!existsSync(filePath)) {
50
+ return defaultTime;
51
+ }
49
52
  try {
50
53
  const existingContent = readFileSync(filePath, 'utf8');
51
54
  const existingData = JSON.parse(existingContent);
52
- createdAt = existingData.createdAt || now;
53
- } catch (e) {
54
- // If we can't read existing file, use current time
55
- createdAt = now;
55
+ return existingData.createdAt || defaultTime;
56
+ } catch (_e) {
57
+ return defaultTime;
56
58
  }
57
- }
59
+ };
60
+ const createdAt = getCreatedAt(filePath, now);
58
61
 
59
62
  const dataToStore = {
60
63
  serverName,
@@ -67,7 +70,7 @@ export function storeScanResult(serverName, hash, scanData) {
67
70
  writeFileSync(filePath, JSON.stringify(dataToStore, null, 2), 'utf8');
68
71
  return true;
69
72
  } catch (error) {
70
- console.error('Error storing scan result:', error);
73
+ logger.error({ serverName, hash, error: error.message }, 'Error storing scan result');
71
74
  return false;
72
75
  }
73
76
  }
@@ -84,21 +87,21 @@ export function clearAllScanResults() {
84
87
  }
85
88
 
86
89
  const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
87
- let deletedCount = 0;
88
90
 
89
- for (const file of files) {
91
+ const deletedCount = files.reduce((count, file) => {
90
92
  try {
91
93
  const filePath = join(scanResultsDir, file);
92
94
  unlinkSync(filePath);
93
- deletedCount++;
95
+ return count + 1;
94
96
  } catch (error) {
95
- console.warn(`Error deleting scan result file ${file}:`, error);
97
+ logger.warn({ file, error: error.message }, 'Error deleting scan result file');
98
+ return count;
96
99
  }
97
- }
100
+ }, 0);
98
101
 
99
102
  return deletedCount;
100
103
  } catch (error) {
101
- console.error('Error clearing all scan results:', error);
104
+ logger.error({ error: error.message }, 'Error clearing all scan results');
102
105
  return 0;
103
106
  }
104
107
  }
@@ -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);
@@ -120,6 +119,7 @@ export function createUIServer() {
120
119
  compositeRoutes.stop(req, res, restoreConfig);
121
120
  });
122
121
  app.get('/api/composite/status', compositeRoutes.getStatus);
122
+ app.get('/api/composite/servers', compositeRoutes.getServers);
123
123
 
124
124
  app.get('/api/help/state', helpRoutes.getState);
125
125
  app.post('/api/help/dismiss', helpRoutes.dismiss);
@@ -137,30 +137,17 @@ export function createUIServer() {
137
137
  app.post('/api/smartscan/cached-results', smartScanRoutes.getCachedResults);
138
138
  app.post('/api/smartscan/cache/clear', smartScanRoutes.clearCache);
139
139
 
140
- const cleanup = () => {
141
- if (processState.mcpSharkProcess) {
142
- processState.mcpSharkProcess.kill();
143
- processState.mcpSharkProcess = null;
144
- }
145
- restoreConfig();
146
- };
147
-
148
- process.on('SIGTERM', cleanup);
149
- process.on('SIGINT', cleanup);
150
- process.on('exit', () => {
151
- restoreConfig();
152
- });
140
+ app.get('/api/settings', settingsRoutes.getSettings);
153
141
 
154
142
  const staticPath = path.join(__dirname, 'dist');
155
143
  app.use(express.static(staticPath));
156
144
 
157
- app.get('*', (req, res) => {
145
+ app.get('*', (_req, res) => {
158
146
  res.sendFile(path.join(staticPath, 'index.html'));
159
147
  });
160
148
 
161
149
  const notifyClients = async () => {
162
150
  const requests = queryRequests(db, { limit: 100 });
163
- const { serializeBigInt } = await import('./server/utils/serialization.js');
164
151
  const message = JSON.stringify({ type: 'update', data: serializeBigInt(requests) });
165
152
  clients.forEach((client) => {
166
153
  if (client.readyState === 1) {
@@ -170,7 +157,7 @@ export function createUIServer() {
170
157
  };
171
158
 
172
159
  const timestampState = { lastTs: 0 };
173
- setInterval(() => {
160
+ const intervalId = setInterval(() => {
174
161
  const lastCheck = db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets').get();
175
162
  if (lastCheck && lastCheck.max_ts > timestampState.lastTs) {
176
163
  timestampState.lastTs = lastCheck.max_ts;
@@ -178,19 +165,78 @@ export function createUIServer() {
178
165
  }
179
166
  }, 500);
180
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
+
181
209
  return { server, cleanup };
182
210
  }
183
211
 
184
212
  export async function runUIServer() {
185
- const port = parseInt(process.env.UI_PORT) || 9853;
213
+ const port = Number.parseInt(process.env.UI_PORT) || 9853;
186
214
  const { server, cleanup } = createUIServer();
187
215
 
188
- server.listen(port, '0.0.0.0', () => {
189
- 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
+ }
190
236
  });
191
237
 
192
- server.on('close', () => {
193
- cleanup();
238
+ server.listen(port, '0.0.0.0', () => {
239
+ console.log(`UI server listening on http://localhost:${port}`);
194
240
  });
195
241
  }
196
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 {