@mcp-shark/mcp-shark 1.4.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 (212) hide show
  1. package/LICENSE +85 -0
  2. package/README.md +724 -0
  3. package/bin/mcp-shark.js +93 -0
  4. package/mcp-server/.editorconfig +15 -0
  5. package/mcp-server/.prettierignore +11 -0
  6. package/mcp-server/.prettierrc +12 -0
  7. package/mcp-server/README.md +280 -0
  8. package/mcp-server/commitlint.config.cjs +42 -0
  9. package/mcp-server/eslint.config.js +131 -0
  10. package/mcp-server/lib/auditor/audit.js +228 -0
  11. package/mcp-server/lib/common/error.js +15 -0
  12. package/mcp-server/lib/server/external/all.js +32 -0
  13. package/mcp-server/lib/server/external/config.js +59 -0
  14. package/mcp-server/lib/server/external/kv.js +102 -0
  15. package/mcp-server/lib/server/external/single/client.js +35 -0
  16. package/mcp-server/lib/server/external/single/request.js +49 -0
  17. package/mcp-server/lib/server/external/single/run.js +75 -0
  18. package/mcp-server/lib/server/external/single/transport.js +57 -0
  19. package/mcp-server/lib/server/internal/handlers/common.js +20 -0
  20. package/mcp-server/lib/server/internal/handlers/error.js +7 -0
  21. package/mcp-server/lib/server/internal/handlers/prompts-get.js +22 -0
  22. package/mcp-server/lib/server/internal/handlers/prompts-list.js +12 -0
  23. package/mcp-server/lib/server/internal/handlers/resources-list.js +12 -0
  24. package/mcp-server/lib/server/internal/handlers/resources-read.js +19 -0
  25. package/mcp-server/lib/server/internal/handlers/tools-call.js +37 -0
  26. package/mcp-server/lib/server/internal/handlers/tools-list.js +14 -0
  27. package/mcp-server/lib/server/internal/run.js +49 -0
  28. package/mcp-server/lib/server/internal/server.js +63 -0
  29. package/mcp-server/lib/server/internal/session.js +39 -0
  30. package/mcp-server/mcp-shark.js +72 -0
  31. package/mcp-server/package-lock.json +4784 -0
  32. package/mcp-server/package.json +30 -0
  33. package/package.json +103 -0
  34. package/ui/README.md +212 -0
  35. package/ui/index.html +16 -0
  36. package/ui/package-lock.json +3574 -0
  37. package/ui/package.json +12 -0
  38. package/ui/paths.js +282 -0
  39. package/ui/public/og-image.png +0 -0
  40. package/ui/server/routes/backups.js +251 -0
  41. package/ui/server/routes/composite.js +244 -0
  42. package/ui/server/routes/config.js +175 -0
  43. package/ui/server/routes/conversations.js +25 -0
  44. package/ui/server/routes/help.js +43 -0
  45. package/ui/server/routes/logs.js +32 -0
  46. package/ui/server/routes/playground.js +152 -0
  47. package/ui/server/routes/requests.js +235 -0
  48. package/ui/server/routes/sessions.js +27 -0
  49. package/ui/server/routes/smartscan/discover.js +117 -0
  50. package/ui/server/routes/smartscan/scans/clearCache.js +22 -0
  51. package/ui/server/routes/smartscan/scans/createBatchScans.js +123 -0
  52. package/ui/server/routes/smartscan/scans/createScan.js +42 -0
  53. package/ui/server/routes/smartscan/scans/getCachedResults.js +51 -0
  54. package/ui/server/routes/smartscan/scans/getScan.js +41 -0
  55. package/ui/server/routes/smartscan/scans/listScans.js +24 -0
  56. package/ui/server/routes/smartscan/scans.js +13 -0
  57. package/ui/server/routes/smartscan/token.js +56 -0
  58. package/ui/server/routes/smartscan/transport.js +53 -0
  59. package/ui/server/routes/smartscan.js +24 -0
  60. package/ui/server/routes/statistics.js +83 -0
  61. package/ui/server/utils/config-update.js +212 -0
  62. package/ui/server/utils/config.js +98 -0
  63. package/ui/server/utils/paths.js +23 -0
  64. package/ui/server/utils/port.js +28 -0
  65. package/ui/server/utils/process.js +80 -0
  66. package/ui/server/utils/scan-cache/all-results.js +180 -0
  67. package/ui/server/utils/scan-cache/directory.js +35 -0
  68. package/ui/server/utils/scan-cache/file-operations.js +104 -0
  69. package/ui/server/utils/scan-cache/hash.js +47 -0
  70. package/ui/server/utils/scan-cache/server-operations.js +80 -0
  71. package/ui/server/utils/scan-cache.js +12 -0
  72. package/ui/server/utils/serialization.js +13 -0
  73. package/ui/server/utils/smartscan-token.js +42 -0
  74. package/ui/server.js +199 -0
  75. package/ui/src/App.jsx +153 -0
  76. package/ui/src/CompositeLogs.jsx +164 -0
  77. package/ui/src/CompositeSetup.jsx +285 -0
  78. package/ui/src/HelpGuide/HelpGuideContent.jsx +118 -0
  79. package/ui/src/HelpGuide/HelpGuideFooter.jsx +58 -0
  80. package/ui/src/HelpGuide/HelpGuideHeader.jsx +56 -0
  81. package/ui/src/HelpGuide.jsx +65 -0
  82. package/ui/src/IntroTour.jsx +140 -0
  83. package/ui/src/LogDetail.jsx +122 -0
  84. package/ui/src/LogTable.jsx +242 -0
  85. package/ui/src/PacketDetail.jsx +190 -0
  86. package/ui/src/PacketFilters.jsx +222 -0
  87. package/ui/src/PacketList.jsx +183 -0
  88. package/ui/src/SmartScan.jsx +178 -0
  89. package/ui/src/TabNavigation.jsx +143 -0
  90. package/ui/src/components/App/HelpButton.jsx +64 -0
  91. package/ui/src/components/App/TrafficTab.jsx +69 -0
  92. package/ui/src/components/App/useAppState.js +163 -0
  93. package/ui/src/components/BackupList.jsx +192 -0
  94. package/ui/src/components/CollapsibleSection.jsx +82 -0
  95. package/ui/src/components/ConfigFileSection.jsx +84 -0
  96. package/ui/src/components/ConfigViewerModal.jsx +141 -0
  97. package/ui/src/components/ConfirmationModal.jsx +129 -0
  98. package/ui/src/components/DetailsTab/BodySection.jsx +27 -0
  99. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +70 -0
  100. package/ui/src/components/DetailsTab/HeadersSection.jsx +25 -0
  101. package/ui/src/components/DetailsTab/InfoSection.jsx +28 -0
  102. package/ui/src/components/DetailsTab/NetworkInfoSection.jsx +63 -0
  103. package/ui/src/components/DetailsTab/ProtocolInfoSection.jsx +75 -0
  104. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +46 -0
  105. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +66 -0
  106. package/ui/src/components/DetailsTab.jsx +31 -0
  107. package/ui/src/components/DetectedPathsList.jsx +171 -0
  108. package/ui/src/components/FileInput.jsx +144 -0
  109. package/ui/src/components/GroupHeader.jsx +76 -0
  110. package/ui/src/components/GroupedByMcpView.jsx +103 -0
  111. package/ui/src/components/GroupedByServerView.jsx +134 -0
  112. package/ui/src/components/GroupedBySessionView.jsx +127 -0
  113. package/ui/src/components/GroupedViews.jsx +2 -0
  114. package/ui/src/components/HexTab.jsx +188 -0
  115. package/ui/src/components/LogsDisplay.jsx +93 -0
  116. package/ui/src/components/LogsToolbar.jsx +193 -0
  117. package/ui/src/components/McpPlayground/LoadingModal.jsx +113 -0
  118. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +125 -0
  119. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +48 -0
  120. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +45 -0
  121. package/ui/src/components/McpPlayground/PromptsSection.jsx +106 -0
  122. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +89 -0
  123. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +59 -0
  124. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +45 -0
  125. package/ui/src/components/McpPlayground/ResourcesSection.jsx +91 -0
  126. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +125 -0
  127. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +48 -0
  128. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +45 -0
  129. package/ui/src/components/McpPlayground/ToolsSection.jsx +107 -0
  130. package/ui/src/components/McpPlayground/common/EmptyState.jsx +17 -0
  131. package/ui/src/components/McpPlayground/common/ErrorState.jsx +17 -0
  132. package/ui/src/components/McpPlayground/common/LoadingState.jsx +17 -0
  133. package/ui/src/components/McpPlayground/useMcpPlayground.js +280 -0
  134. package/ui/src/components/McpPlayground.jsx +171 -0
  135. package/ui/src/components/MessageDisplay.jsx +28 -0
  136. package/ui/src/components/PacketDetailHeader.jsx +88 -0
  137. package/ui/src/components/PacketFilters/ExportControls.jsx +126 -0
  138. package/ui/src/components/PacketFilters/FilterInput.jsx +59 -0
  139. package/ui/src/components/RawTab.jsx +142 -0
  140. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +155 -0
  141. package/ui/src/components/RequestRow/RequestRowMain.jsx +240 -0
  142. package/ui/src/components/RequestRow/ResponseRow.jsx +158 -0
  143. package/ui/src/components/RequestRow.jsx +70 -0
  144. package/ui/src/components/ServerControl.jsx +133 -0
  145. package/ui/src/components/ServiceSelector.jsx +209 -0
  146. package/ui/src/components/SetupHeader.jsx +30 -0
  147. package/ui/src/components/SharkLogo.jsx +21 -0
  148. package/ui/src/components/SmartScan/AnalysisResult.jsx +64 -0
  149. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +215 -0
  150. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +94 -0
  151. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +26 -0
  152. package/ui/src/components/SmartScan/DebugInfoSection.jsx +53 -0
  153. package/ui/src/components/SmartScan/EmptyState.jsx +57 -0
  154. package/ui/src/components/SmartScan/ErrorDisplay.jsx +48 -0
  155. package/ui/src/components/SmartScan/ExpandableSection.jsx +93 -0
  156. package/ui/src/components/SmartScan/FindingsTable.jsx +257 -0
  157. package/ui/src/components/SmartScan/ListViewContent.jsx +75 -0
  158. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +75 -0
  159. package/ui/src/components/SmartScan/OverallSummarySection.jsx +72 -0
  160. package/ui/src/components/SmartScan/RawDataSection.jsx +52 -0
  161. package/ui/src/components/SmartScan/RecommendationsSection.jsx +78 -0
  162. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +92 -0
  163. package/ui/src/components/SmartScan/ScanDetailView.jsx +141 -0
  164. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +49 -0
  165. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +201 -0
  166. package/ui/src/components/SmartScan/ScanListView.jsx +73 -0
  167. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +123 -0
  168. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +35 -0
  169. package/ui/src/components/SmartScan/ScanViewContent.jsx +68 -0
  170. package/ui/src/components/SmartScan/ScanningProgress.jsx +47 -0
  171. package/ui/src/components/SmartScan/ServerInfoSection.jsx +43 -0
  172. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +207 -0
  173. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +269 -0
  174. package/ui/src/components/SmartScan/SmartScanControls.jsx +290 -0
  175. package/ui/src/components/SmartScan/SmartScanHeader.jsx +77 -0
  176. package/ui/src/components/SmartScan/ViewModeTabs.jsx +57 -0
  177. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +34 -0
  178. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +121 -0
  179. package/ui/src/components/SmartScan/hooks/useScanList.js +193 -0
  180. package/ui/src/components/SmartScan/hooks/useScanOperations.js +87 -0
  181. package/ui/src/components/SmartScan/hooks/useServerStatus.js +26 -0
  182. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +53 -0
  183. package/ui/src/components/SmartScan/scanDataUtils.js +98 -0
  184. package/ui/src/components/SmartScan/useSmartScan.js +72 -0
  185. package/ui/src/components/SmartScan/utils.js +19 -0
  186. package/ui/src/components/SmartScanIcons.jsx +58 -0
  187. package/ui/src/components/TabNavigation/DesktopTabs.jsx +111 -0
  188. package/ui/src/components/TabNavigation/MobileDropdown.jsx +140 -0
  189. package/ui/src/components/TabNavigation.jsx +97 -0
  190. package/ui/src/components/TabNavigationIcons.jsx +40 -0
  191. package/ui/src/components/TableHeader.jsx +164 -0
  192. package/ui/src/components/TourOverlay.jsx +117 -0
  193. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +117 -0
  194. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +70 -0
  195. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +45 -0
  196. package/ui/src/components/TourTooltip/useTooltipPosition.js +108 -0
  197. package/ui/src/components/TourTooltip.jsx +83 -0
  198. package/ui/src/components/ViewModeTabs.jsx +91 -0
  199. package/ui/src/components/WhatThisDoesSection.jsx +61 -0
  200. package/ui/src/config/tourSteps.jsx +141 -0
  201. package/ui/src/hooks/useAnimation.js +92 -0
  202. package/ui/src/hooks/useConfigManagement.js +124 -0
  203. package/ui/src/hooks/useServiceExtraction.js +51 -0
  204. package/ui/src/index.css +42 -0
  205. package/ui/src/main.jsx +10 -0
  206. package/ui/src/theme.js +65 -0
  207. package/ui/src/utils/animations.js +170 -0
  208. package/ui/src/utils/groupingUtils.js +93 -0
  209. package/ui/src/utils/hexUtils.js +24 -0
  210. package/ui/src/utils/mcpGroupingUtils.js +262 -0
  211. package/ui/src/utils/requestUtils.js +297 -0
  212. package/ui/vite.config.js +18 -0
@@ -0,0 +1,193 @@
1
+ import { useState } from 'react';
2
+
3
+ export function useScanList(apiToken, setError) {
4
+ const [allScans, setAllScans] = useState([]);
5
+ const [loadingScans, setLoadingScans] = useState(false);
6
+ const [selectedScan, setSelectedScan] = useState(null);
7
+ const [loadingScanDetail, setLoadingScanDetail] = useState(false);
8
+
9
+ const loadAllScans = async () => {
10
+ setLoadingScans(true);
11
+ setError(null);
12
+ try {
13
+ // Only load from local cache
14
+ console.log('Loading cached scans from local storage...');
15
+ const cacheResponse = await fetch('/api/smartscan/scans?cache=true');
16
+ const cacheData = await cacheResponse.json();
17
+ console.log('Cache response:', {
18
+ status: cacheResponse.status,
19
+ ok: cacheResponse.ok,
20
+ cacheData,
21
+ });
22
+
23
+ if (cacheResponse.ok) {
24
+ const scans = cacheData.scans || [];
25
+ console.log(`[useScanList] Loaded ${scans.length} cached scans from API`);
26
+ console.log(`[useScanList] Full cacheData:`, cacheData);
27
+
28
+ // Debug: Log first scan structure to see what we're receiving
29
+ if (scans.length > 0) {
30
+ console.log('[useScanList] First scan structure:', {
31
+ keys: Object.keys(scans[0]),
32
+ serverName: scans[0].serverName,
33
+ server_name: scans[0].server_name,
34
+ server: scans[0].server,
35
+ fullScan: scans[0],
36
+ });
37
+ }
38
+
39
+ // Transform cached scans to BatchResultsDisplay format
40
+ const scanResults = scans.map((scan, index) => {
41
+ // The scan structure from getAllCachedScanResults has:
42
+ // { id, scan_id, server: { name }, serverName, server_name, data, result, ... }
43
+
44
+ console.log(`[useScanList] Transforming scan ${index}:`, {
45
+ scanId: scan.id || scan.scan_id,
46
+ serverName: scan.serverName,
47
+ server_name: scan.server_name,
48
+ server: scan.server,
49
+ hasServerName: !!scan.serverName,
50
+ hasServerNameAlt: !!scan.server_name,
51
+ hasServer: !!scan.server,
52
+ });
53
+
54
+ // Extract server name from multiple possible locations
55
+ // Handle empty strings, null, undefined
56
+ const serverName =
57
+ (scan.serverName && scan.serverName.trim()) ||
58
+ (scan.server_name && scan.server_name.trim()) ||
59
+ (scan.server?.name && scan.server.name.trim()) ||
60
+ 'Unknown Server';
61
+
62
+ console.log(`[useScanList] Extracted serverName for scan ${index}: "${serverName}"`);
63
+
64
+ // Debug logging
65
+ if (serverName === 'Unknown Server') {
66
+ console.error('[useScanList] Could not find server name in scan:', {
67
+ scanId: scan.id || scan.scan_id,
68
+ hasServerName: !!scan.serverName,
69
+ hasServerNameAlt: !!scan.server_name,
70
+ hasServer: !!scan.server,
71
+ serverNameValue: scan.serverName,
72
+ serverNameAltValue: scan.server_name,
73
+ serverNameFromServer: scan.server?.name,
74
+ scanKeys: Object.keys(scan),
75
+ fullScanObject: scan,
76
+ });
77
+ }
78
+
79
+ // Get the actual scan data - it might be nested
80
+ // scan.data could be the scan result from API which has { success, data, scan_id, ... }
81
+ // or it could be the direct scan data
82
+ let scanData = scan.data || scan.result || scan;
83
+
84
+ // If scanData has a nested 'data' property (from API response), use that
85
+ if (scanData && scanData.data && typeof scanData.data === 'object') {
86
+ scanData = scanData.data;
87
+ }
88
+
89
+ const transformed = {
90
+ serverName: serverName,
91
+ success: true,
92
+ cached: true,
93
+ data: {
94
+ scan_id: scan.scan_id || scan.id,
95
+ data: scanData,
96
+ },
97
+ };
98
+
99
+ console.log(`[useScanList] Transformed result ${index}:`, {
100
+ serverName: transformed.serverName,
101
+ scan_id: transformed.data.scan_id,
102
+ });
103
+
104
+ return transformed;
105
+ });
106
+
107
+ console.log(
108
+ '[useScanList] Final transformed scanResults:',
109
+ scanResults.map((r) => ({
110
+ serverName: r.serverName,
111
+ scan_id: r.data.scan_id,
112
+ }))
113
+ );
114
+
115
+ console.log('[useScanList] Setting allScans state with:', scanResults.length, 'items');
116
+ console.log('[useScanList] First item serverName:', scanResults[0]?.serverName);
117
+ console.log('[useScanList] Second item serverName:', scanResults[1]?.serverName);
118
+
119
+ setAllScans(scanResults);
120
+ if (scanResults.length === 0) {
121
+ setError('No cached scans found. Please run a scan first to see results here.');
122
+ }
123
+ } else {
124
+ setError(cacheData.error || cacheData.message || 'Failed to load cached scans');
125
+ }
126
+ } catch (err) {
127
+ setError(err.message || 'Failed to load cached scans');
128
+ } finally {
129
+ setLoadingScans(false);
130
+ }
131
+ };
132
+
133
+ const loadScanDetail = async (scanId) => {
134
+ if (!scanId) {
135
+ setSelectedScan(null);
136
+ return;
137
+ }
138
+
139
+ // First, check if we already have this scan in allScans (cached scan)
140
+ // allScans is in BatchResultsDisplay format: { serverName, success, cached, data: { scan_id, data: {...} } }
141
+ const cachedResult = allScans.find(
142
+ (r) =>
143
+ r.data?.scan_id === scanId ||
144
+ r.data?.data?.id === scanId ||
145
+ r.data?.data?.scan_id === scanId
146
+ );
147
+ if (cachedResult && cachedResult.cached && cachedResult.data?.data) {
148
+ // Use the cached scan data directly
149
+ const scanData = cachedResult.data.data;
150
+ setSelectedScan({
151
+ ...scanData,
152
+ scan_id: cachedResult.data.scan_id || scanData.id || scanData.scan_id,
153
+ });
154
+ return;
155
+ }
156
+
157
+ // If not cached and no API token, can't load from API
158
+ if (!apiToken) {
159
+ setError('Please enter your API token to view scan details');
160
+ return;
161
+ }
162
+
163
+ setLoadingScanDetail(true);
164
+ setError(null);
165
+ try {
166
+ const response = await fetch(`/api/smartscan/scans/${scanId}`, {
167
+ headers: {
168
+ Authorization: `Bearer ${apiToken}`,
169
+ },
170
+ });
171
+ const data = await response.json();
172
+ if (response.ok) {
173
+ setSelectedScan(data.result || data);
174
+ } else {
175
+ setError(data.error || data.message || 'Failed to load scan details');
176
+ }
177
+ } catch (err) {
178
+ setError(err.message || 'Failed to load scan details');
179
+ } finally {
180
+ setLoadingScanDetail(false);
181
+ }
182
+ };
183
+
184
+ return {
185
+ allScans,
186
+ loadingScans,
187
+ loadAllScans,
188
+ selectedScan,
189
+ setSelectedScan,
190
+ loadingScanDetail,
191
+ loadScanDetail,
192
+ };
193
+ }
@@ -0,0 +1,87 @@
1
+ import { useState } from 'react';
2
+
3
+ export function useScanOperations(apiToken, discoveredServers, selectedServers, setError) {
4
+ const [scanning, setScanning] = useState(false);
5
+ const [scanResult, setScanResult] = useState(null);
6
+ const [scanResults, setScanResults] = useState([]);
7
+
8
+ const runScan = async () => {
9
+ if (!apiToken) {
10
+ setError('Please enter your API token');
11
+ return;
12
+ }
13
+
14
+ if (!discoveredServers || discoveredServers.length === 0) {
15
+ setError('Please discover MCP servers first');
16
+ return;
17
+ }
18
+
19
+ if (selectedServers.size === 0) {
20
+ setError('Please select at least one server to scan');
21
+ return;
22
+ }
23
+
24
+ setScanning(true);
25
+ setError(null);
26
+ setScanResult(null);
27
+ setScanResults([]);
28
+
29
+ const serversToScan = discoveredServers.filter((server) => selectedServers.has(server.name));
30
+
31
+ try {
32
+ const response = await fetch('/api/smartscan/scans/batch', {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({
38
+ apiToken,
39
+ servers: serversToScan,
40
+ }),
41
+ });
42
+
43
+ const data = await response.json();
44
+
45
+ if (!response.ok) {
46
+ let errorMessage = data.error || data.message || `API error: ${response.status}`;
47
+
48
+ if (response.status === 400 && data.details) {
49
+ if (Array.isArray(data.details)) {
50
+ errorMessage = `Validation failed: ${data.details
51
+ .map((d) => {
52
+ if (typeof d === 'string') return d;
53
+ if (d.field && d.message) return `${d.field}: ${d.message}`;
54
+ return JSON.stringify(d);
55
+ })
56
+ .join('; ')}`;
57
+ } else if (typeof data.details === 'string') {
58
+ errorMessage = data.details;
59
+ }
60
+ }
61
+
62
+ setError(errorMessage);
63
+ return;
64
+ }
65
+
66
+ if (data.results && Array.isArray(data.results)) {
67
+ setScanResults(data.results);
68
+
69
+ const firstSuccess = data.results.find((r) => r.success);
70
+ if (firstSuccess) {
71
+ setScanResult(firstSuccess.data);
72
+ }
73
+ }
74
+ } catch (err) {
75
+ setError(err.message || 'Failed to run scan');
76
+ } finally {
77
+ setScanning(false);
78
+ }
79
+ };
80
+
81
+ return {
82
+ scanning,
83
+ scanResult,
84
+ scanResults,
85
+ runScan,
86
+ };
87
+ }
@@ -0,0 +1,26 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export function useServerStatus() {
4
+ const [serverStatus, setServerStatus] = useState(null);
5
+
6
+ useEffect(() => {
7
+ checkServerStatus();
8
+ const interval = setInterval(checkServerStatus, 2000);
9
+ return () => clearInterval(interval);
10
+ }, []);
11
+
12
+ const checkServerStatus = async () => {
13
+ try {
14
+ const res = await fetch('/api/composite/status');
15
+ if (!res.ok) {
16
+ throw new Error('Server not available');
17
+ }
18
+ const data = await res.json();
19
+ setServerStatus(data);
20
+ } catch (err) {
21
+ setServerStatus({ running: false });
22
+ }
23
+ };
24
+
25
+ return { serverStatus };
26
+ }
@@ -0,0 +1,53 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+
3
+ export function useTokenManagement() {
4
+ const [apiToken, setApiToken] = useState('');
5
+ const saveTokenTimeoutRef = useRef(null);
6
+
7
+ useEffect(() => {
8
+ loadStoredToken();
9
+ return () => {
10
+ if (saveTokenTimeoutRef.current) {
11
+ clearTimeout(saveTokenTimeoutRef.current);
12
+ }
13
+ };
14
+ }, []);
15
+
16
+ const loadStoredToken = async () => {
17
+ try {
18
+ const response = await fetch('/api/smartscan/token');
19
+ if (response.ok) {
20
+ const data = await response.json();
21
+ if (data.token) {
22
+ setApiToken(data.token);
23
+ }
24
+ }
25
+ } catch (err) {
26
+ console.debug('No stored token found');
27
+ }
28
+ };
29
+
30
+ const saveToken = async (token) => {
31
+ try {
32
+ const response = await fetch('/api/smartscan/token', {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({ token }),
38
+ });
39
+ if (!response.ok) {
40
+ console.error('Failed to save token');
41
+ }
42
+ } catch (err) {
43
+ console.error('Error saving token:', err);
44
+ }
45
+ };
46
+
47
+ return {
48
+ apiToken,
49
+ setApiToken,
50
+ saveToken,
51
+ saveTokenTimeoutRef,
52
+ };
53
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Utility functions for normalizing scan data from various API response formats
3
+ */
4
+
5
+ export function getScanValue(scan, path) {
6
+ const paths = path.split('.');
7
+ let value = scan;
8
+ for (const p of paths) {
9
+ if (value && typeof value === 'object' && p in value) {
10
+ value = value[p];
11
+ } else {
12
+ return null;
13
+ }
14
+ }
15
+ return value;
16
+ }
17
+
18
+ export function normalizeScanData(scan) {
19
+ const actualScan = scan.result || scan.data || scan;
20
+
21
+ const scanId =
22
+ getScanValue(scan, 'result.id') ||
23
+ getScanValue(scan, 'id') ||
24
+ getScanValue(scan, 'scan_id') ||
25
+ getScanValue(scan, 'data.id') ||
26
+ getScanValue(scan, 'data.scan_id') ||
27
+ (actualScan && actualScan.id) ||
28
+ (actualScan && actualScan.scan_id);
29
+
30
+ const serverName =
31
+ scan.serverName || // Check top-level first (for cached scans)
32
+ scan.server_name ||
33
+ getScanValue(scan, 'result.mcp_server_data.server.name') ||
34
+ getScanValue(scan, 'mcp_server_data.server.name') ||
35
+ getScanValue(scan, 'server.name') ||
36
+ getScanValue(scan, 'data.server.name') ||
37
+ getScanValue(scan, 'data.data.server.name') ||
38
+ (scan.server && scan.server.name) || // Check nested server object
39
+ 'Unknown Server';
40
+
41
+ const status =
42
+ getScanValue(scan, 'result.status') ||
43
+ getScanValue(scan, 'status') ||
44
+ getScanValue(scan, 'data.status');
45
+
46
+ const overallRiskLevel =
47
+ getScanValue(scan, 'result.overall_risk_level') ||
48
+ getScanValue(scan, 'overall_risk_level') ||
49
+ getScanValue(scan, 'data.overall_risk_level') ||
50
+ getScanValue(scan, 'data.data.overall_risk_level');
51
+
52
+ const createdAt =
53
+ getScanValue(scan, 'result.created_at') ||
54
+ getScanValue(scan, 'created_at') ||
55
+ getScanValue(scan, 'data.created_at') ||
56
+ getScanValue(scan, 'data.data.created_at');
57
+
58
+ const updatedAt =
59
+ getScanValue(scan, 'result.updated_at') ||
60
+ getScanValue(scan, 'updated_at') ||
61
+ getScanValue(scan, 'data.updated_at') ||
62
+ getScanValue(scan, 'data.data.updated_at');
63
+
64
+ let analysisResult =
65
+ getScanValue(scan, 'result.analysis_result') ||
66
+ getScanValue(scan, 'analysis_result') ||
67
+ getScanValue(scan, 'data.analysis_result') ||
68
+ getScanValue(scan, 'data.data.analysis_result') ||
69
+ getScanValue(scan, 'data.data.data.analysis_result');
70
+
71
+ if (!analysisResult && actualScan && typeof actualScan === 'object') {
72
+ if (actualScan.tool_findings || actualScan.prompt_findings || actualScan.resource_findings) {
73
+ analysisResult = actualScan;
74
+ }
75
+ if (!analysisResult && actualScan.analysis_result) {
76
+ analysisResult = actualScan.analysis_result;
77
+ }
78
+ }
79
+
80
+ const serverData =
81
+ getScanValue(scan, 'result.mcp_server_data.server') ||
82
+ getScanValue(scan, 'mcp_server_data.server') ||
83
+ getScanValue(scan, 'server') ||
84
+ getScanValue(scan, 'data.server') ||
85
+ getScanValue(scan, 'data.data.server') ||
86
+ getScanValue(scan, 'mcp_server_data');
87
+
88
+ return {
89
+ scanId,
90
+ serverName,
91
+ status,
92
+ overallRiskLevel,
93
+ createdAt,
94
+ updatedAt,
95
+ analysisResult,
96
+ serverData,
97
+ };
98
+ }
@@ -0,0 +1,72 @@
1
+ import { useState } from 'react';
2
+ import { useTokenManagement } from './hooks/useTokenManagement';
3
+ import { useServerStatus } from './hooks/useServerStatus';
4
+ import { useMcpDiscovery } from './hooks/useMcpDiscovery';
5
+ import { useScanOperations } from './hooks/useScanOperations';
6
+ import { useScanList } from './hooks/useScanList';
7
+ import { useCacheManagement } from './hooks/useCacheManagement';
8
+
9
+ export function useSmartScan() {
10
+ const [error, setError] = useState(null);
11
+
12
+ const { apiToken, setApiToken, saveToken, saveTokenTimeoutRef } = useTokenManagement();
13
+ const { serverStatus } = useServerStatus();
14
+ const {
15
+ mcpData,
16
+ discoveredServers,
17
+ selectedServers,
18
+ setSelectedServers,
19
+ loadingData,
20
+ discoverMcpData,
21
+ makeMcpRequest,
22
+ } = useMcpDiscovery(setError);
23
+ const { scanning, scanResult, scanResults, runScan } = useScanOperations(
24
+ apiToken,
25
+ discoveredServers,
26
+ selectedServers,
27
+ setError
28
+ );
29
+ const {
30
+ allScans,
31
+ loadingScans,
32
+ loadAllScans,
33
+ selectedScan,
34
+ setSelectedScan,
35
+ loadingScanDetail,
36
+ loadScanDetail,
37
+ } = useScanList(apiToken, setError);
38
+ const { clearingCache, clearCache } = useCacheManagement(
39
+ discoveredServers,
40
+ discoverMcpData,
41
+ setError
42
+ );
43
+
44
+ return {
45
+ apiToken,
46
+ setApiToken,
47
+ serverStatus,
48
+ mcpData,
49
+ discoveredServers,
50
+ selectedServers,
51
+ setSelectedServers,
52
+ loadingData,
53
+ scanning,
54
+ scanResult,
55
+ scanResults,
56
+ error,
57
+ saveToken,
58
+ discoverMcpData,
59
+ runScan,
60
+ clearCache,
61
+ clearingCache,
62
+ saveTokenTimeoutRef,
63
+ allScans,
64
+ loadingScans,
65
+ loadAllScans,
66
+ selectedScan,
67
+ setSelectedScan,
68
+ loadingScanDetail,
69
+ loadScanDetail,
70
+ makeMcpRequest,
71
+ };
72
+ }
@@ -0,0 +1,19 @@
1
+ import { colors } from '../../theme';
2
+
3
+ export function getRiskLevelColor(riskLevel) {
4
+ if (!riskLevel) return colors.textTertiary;
5
+ switch (riskLevel.toLowerCase()) {
6
+ case 'none':
7
+ return colors.accentGreen;
8
+ case 'low':
9
+ return colors.accentBlue;
10
+ case 'medium':
11
+ return colors.accentOrange;
12
+ case 'high':
13
+ return colors.error;
14
+ case 'critical':
15
+ return colors.error;
16
+ default:
17
+ return colors.textTertiary;
18
+ }
19
+ }
@@ -0,0 +1,58 @@
1
+ import { colors } from '../theme';
2
+ import {
3
+ IconShield,
4
+ IconExternalLink,
5
+ IconAlertTriangle,
6
+ IconCheck,
7
+ IconClock,
8
+ } from '@tabler/icons-react';
9
+
10
+ export const ShieldIcon = ({ size = 24, color = 'currentColor' }) => (
11
+ <IconShield size={size} stroke={1.5} color={color} />
12
+ );
13
+
14
+ export const ExternalLinkIcon = ({ size = 16, color = 'currentColor' }) => (
15
+ <IconExternalLink size={size} stroke={1.5} color={color} />
16
+ );
17
+
18
+ export const AlertIcon = ({ size = 16, color = 'currentColor' }) => (
19
+ <IconAlertTriangle size={size} stroke={1.5} color={color} />
20
+ );
21
+
22
+ export const CheckIcon = ({ size = 16, color = 'currentColor' }) => (
23
+ <IconCheck size={size} stroke={1.5} color={color} />
24
+ );
25
+
26
+ export const CacheIcon = ({ size = 16, color = 'currentColor' }) => (
27
+ <IconClock size={size} stroke={1.5} color={color} />
28
+ );
29
+
30
+ export const LoadingSpinner = ({ size = 16, color = colors.accentBlue }) => (
31
+ <div
32
+ style={{
33
+ width: size,
34
+ height: size,
35
+ border: `2px solid ${colors.borderLight}`,
36
+ borderTop: `2px solid ${color}`,
37
+ borderRadius: '50%',
38
+ animation: 'spin 0.8s linear infinite',
39
+ }}
40
+ />
41
+ );
42
+
43
+ export const EmptyStateIcon = () => (
44
+ <svg
45
+ width={64}
46
+ height={64}
47
+ viewBox="0 0 24 24"
48
+ fill="none"
49
+ stroke={colors.textTertiary}
50
+ strokeWidth="1.5"
51
+ strokeLinecap="round"
52
+ strokeLinejoin="round"
53
+ style={{ opacity: 0.5 }}
54
+ >
55
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
56
+ <path d="M9 12l2 2 4-4" />
57
+ </svg>
58
+ );