@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,180 @@
1
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getScanResultsDirectory } from './directory.js';
4
+
5
+ /**
6
+ * Extract server name from cached scan data
7
+ * @param {Object} data - Cached file data
8
+ * @param {Object} scanData - Scan data from API
9
+ * @returns {string} Server name
10
+ */
11
+ function extractServerName(data, scanData) {
12
+ let serverName = data.serverName; // From cache file metadata (most reliable)
13
+
14
+ // If not found at top level, try to extract from scan data (API response)
15
+ if (!serverName || serverName === 'Unknown Server') {
16
+ const actualScanData = scanData.data || scanData;
17
+
18
+ serverName =
19
+ actualScanData.serverName ||
20
+ actualScanData.server_name ||
21
+ actualScanData.server?.name ||
22
+ actualScanData.mcp_server_data?.server?.name ||
23
+ scanData.serverName ||
24
+ scanData.server_name ||
25
+ scanData.server?.name ||
26
+ scanData.mcp_server_data?.server?.name ||
27
+ 'Unknown Server';
28
+ }
29
+
30
+ return serverName;
31
+ }
32
+
33
+ /**
34
+ * Process a single cached scan file
35
+ * @param {string} file - File name
36
+ * @param {string} scanResultsDir - Directory path
37
+ * @returns {Object|null} Processed scan result or null if error
38
+ */
39
+ function processScanFile(file, scanResultsDir) {
40
+ try {
41
+ const filePath = join(scanResultsDir, file);
42
+ console.log(`[getAllCachedScanResults] Reading file: ${file}`);
43
+
44
+ const fileContent = readFileSync(filePath, 'utf8');
45
+ const data = JSON.parse(fileContent);
46
+
47
+ // Validate that this is a scan result file
48
+ if (!data || typeof data !== 'object') {
49
+ console.warn(`[getAllCachedScanResults] Invalid data in file ${file}: not an object`);
50
+ return null;
51
+ }
52
+
53
+ // 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
+ });
61
+
62
+ // Create a scan-like object with id, server name, and scan data
63
+ const scanData = data.scanData || data;
64
+ const scanId = scanData.id || scanData.scan_id || data.hash || file.replace('.json', '');
65
+ const serverName = extractServerName(data, scanData);
66
+
67
+ // Log if we couldn't find server name
68
+ 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(', ')}`
74
+ );
75
+ if (scanData?.data) {
76
+ console.warn(
77
+ `[getAllCachedScanResults] scanData.data keys: ${Object.keys(scanData.data || {}).join(', ')}`
78
+ );
79
+ }
80
+ }
81
+
82
+ const scanResult = {
83
+ id: scanId,
84
+ scan_id: scanId,
85
+ server: {
86
+ name: serverName,
87
+ },
88
+ server_name: serverName,
89
+ serverName: serverName,
90
+ status: 'completed',
91
+ risk_level: scanData.overall_risk_level || scanData.risk_level || 'unknown',
92
+ overall_risk_level: scanData.overall_risk_level || scanData.risk_level || 'unknown',
93
+ created_at: data.createdAt || data.created_at || scanData.created_at,
94
+ updated_at: data.updatedAt || data.updated_at || scanData.updated_at,
95
+ cached: true,
96
+ hash: data.hash || file.replace('.json', ''),
97
+ data: scanData,
98
+ result: scanData,
99
+ };
100
+
101
+ console.log(`[getAllCachedScanResults] Successfully loaded scan: ${serverName} (${scanId})`);
102
+ return scanResult;
103
+ } catch (error) {
104
+ console.warn(
105
+ `[getAllCachedScanResults] Error reading scan result file ${file}:`,
106
+ error.message
107
+ );
108
+ console.warn(`[getAllCachedScanResults] File path: ${join(scanResultsDir, file)}`);
109
+ if (error.stack) {
110
+ console.warn(`[getAllCachedScanResults] Stack: ${error.stack}`);
111
+ }
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get all cached scan results (across all servers)
118
+ * @returns {Array} Array of cached scan results
119
+ */
120
+ export function getAllCachedScanResults() {
121
+ try {
122
+ const scanResultsDir = getScanResultsDirectory();
123
+ console.log('[getAllCachedScanResults] Reading cached scans from directory:', scanResultsDir);
124
+
125
+ // Check if directory exists (don't create it, just check)
126
+ if (!existsSync(scanResultsDir)) {
127
+ console.log(
128
+ '[getAllCachedScanResults] Scan results directory does not exist:',
129
+ scanResultsDir
130
+ );
131
+ return [];
132
+ }
133
+
134
+ // Read all files in the directory
135
+ const allFiles = readdirSync(scanResultsDir);
136
+ console.log(`[getAllCachedScanResults] Total files in directory: ${allFiles.length}`);
137
+
138
+ // Filter for JSON files only
139
+ const jsonFiles = allFiles.filter((f) => f.endsWith('.json'));
140
+ console.log(
141
+ `[getAllCachedScanResults] Found ${jsonFiles.length} JSON files in ${scanResultsDir}`
142
+ );
143
+
144
+ if (jsonFiles.length === 0) {
145
+ console.log('[getAllCachedScanResults] No cached scan JSON files found');
146
+ return [];
147
+ }
148
+
149
+ const results = [];
150
+ let successCount = 0;
151
+ let errorCount = 0;
152
+
153
+ // 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
+ }
163
+
164
+ // Sort by updatedAt descending (most recent first)
165
+ results.sort((a, b) => {
166
+ const aTime = a.updated_at || a.created_at || 0;
167
+ const bTime = b.updated_at || b.created_at || 0;
168
+ return bTime - aTime;
169
+ });
170
+
171
+ console.log(
172
+ `[getAllCachedScanResults] Summary: ${successCount} successful, ${errorCount} errors, ${results.length} total scans loaded`
173
+ );
174
+ return results;
175
+ } catch (error) {
176
+ console.error('[getAllCachedScanResults] Fatal error getting all cached scan results:', error);
177
+ console.error('[getAllCachedScanResults] Error stack:', error.stack);
178
+ return [];
179
+ }
180
+ }
@@ -0,0 +1,35 @@
1
+ import { existsSync, mkdirSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getWorkingDirectory } from 'mcp-shark-common/configs/index.js';
4
+
5
+ const SCAN_RESULTS_DIR_NAME = 'scan-results';
6
+
7
+ /**
8
+ * Get the scan results directory path
9
+ * @returns {string} Path to scan results directory
10
+ */
11
+ export function getScanResultsDirectory() {
12
+ return join(getWorkingDirectory(), SCAN_RESULTS_DIR_NAME);
13
+ }
14
+
15
+ /**
16
+ * Ensure the scan results directory exists
17
+ * @returns {string} Path to scan results directory
18
+ */
19
+ export function ensureScanResultsDirectory() {
20
+ const scanResultsDir = getScanResultsDirectory();
21
+ if (!existsSync(scanResultsDir)) {
22
+ mkdirSync(scanResultsDir, { recursive: true });
23
+ }
24
+ return scanResultsDir;
25
+ }
26
+
27
+ /**
28
+ * Get file path for a scan result based on hash
29
+ * @param {string} hash - SHA-256 hash of MCP server data
30
+ * @returns {string} File path
31
+ */
32
+ export function getScanResultFilePath(hash) {
33
+ ensureScanResultsDirectory();
34
+ return join(getScanResultsDirectory(), `${hash}.json`);
35
+ }
@@ -0,0 +1,104 @@
1
+ import { readFileSync, writeFileSync, existsSync, unlinkSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getScanResultFilePath, getScanResultsDirectory } from './directory.js';
4
+
5
+ /**
6
+ * Get cached scan result by hash
7
+ * @param {string} hash - SHA-256 hash of MCP server data
8
+ * @returns {Object|null} Cached scan result or null if not found
9
+ */
10
+ export function getCachedScanResult(hash) {
11
+ try {
12
+ const filePath = getScanResultFilePath(hash);
13
+
14
+ if (!existsSync(filePath)) {
15
+ return null;
16
+ }
17
+
18
+ const fileContent = readFileSync(filePath, 'utf8');
19
+ const data = JSON.parse(fileContent);
20
+
21
+ return {
22
+ ...data.scanData,
23
+ cached: true,
24
+ cachedAt: data.createdAt,
25
+ updatedAt: data.updatedAt,
26
+ serverName: data.serverName,
27
+ };
28
+ } catch (error) {
29
+ console.error('Error getting cached scan result:', error);
30
+ return null;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Store scan result in cache
36
+ * @param {string} serverName - Name of the MCP server
37
+ * @param {string} hash - SHA-256 hash of MCP server data
38
+ * @param {Object} scanData - Scan result data to store
39
+ * @returns {boolean} Success status
40
+ */
41
+ export function storeScanResult(serverName, hash, scanData) {
42
+ try {
43
+ const filePath = getScanResultFilePath(hash);
44
+ const now = Date.now();
45
+
46
+ // Check if file exists to preserve original creation time
47
+ let createdAt = now;
48
+ if (existsSync(filePath)) {
49
+ try {
50
+ const existingContent = readFileSync(filePath, 'utf8');
51
+ 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;
56
+ }
57
+ }
58
+
59
+ const dataToStore = {
60
+ serverName,
61
+ hash,
62
+ scanData,
63
+ createdAt,
64
+ updatedAt: now,
65
+ };
66
+
67
+ writeFileSync(filePath, JSON.stringify(dataToStore, null, 2), 'utf8');
68
+ return true;
69
+ } catch (error) {
70
+ console.error('Error storing scan result:', error);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Clear all cached scan results
77
+ * @returns {number} Number of files deleted
78
+ */
79
+ export function clearAllScanResults() {
80
+ try {
81
+ const scanResultsDir = getScanResultsDirectory();
82
+ if (!existsSync(scanResultsDir)) {
83
+ return 0;
84
+ }
85
+
86
+ const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
87
+ let deletedCount = 0;
88
+
89
+ for (const file of files) {
90
+ try {
91
+ const filePath = join(scanResultsDir, file);
92
+ unlinkSync(filePath);
93
+ deletedCount++;
94
+ } catch (error) {
95
+ console.warn(`Error deleting scan result file ${file}:`, error);
96
+ }
97
+ }
98
+
99
+ return deletedCount;
100
+ } catch (error) {
101
+ console.error('Error clearing all scan results:', error);
102
+ return 0;
103
+ }
104
+ }
@@ -0,0 +1,47 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ /**
4
+ * Compute SHA-256 hash of MCP server data for change detection
5
+ * @param {Object} serverData - MCP server data (name, tools, resources, prompts)
6
+ * @returns {string} SHA-256 hash in hex format
7
+ */
8
+ export function computeMcpHash(serverData) {
9
+ // Normalize the data to ensure consistent hashing
10
+ // Sort arrays to ensure order doesn't matter
11
+ const normalized = {
12
+ name: serverData.name || '',
13
+ tools: (serverData.tools || [])
14
+ .map((tool) => ({
15
+ name: tool.name || '',
16
+ description: tool.description || '',
17
+ inputSchema: tool.inputSchema || tool.input_schema || null,
18
+ outputSchema: tool.outputSchema || tool.output_schema || null,
19
+ }))
20
+ .sort((a, b) => (a.name || '').localeCompare(b.name || '')),
21
+ resources: (serverData.resources || [])
22
+ .map((resource) => ({
23
+ uri: resource.uri || '',
24
+ name: resource.name || '',
25
+ description: resource.description || '',
26
+ mimeType: resource.mimeType || resource.mime_type || null,
27
+ }))
28
+ .sort((a, b) => (a.uri || '').localeCompare(b.uri || '')),
29
+ prompts: (serverData.prompts || [])
30
+ .map((prompt) => ({
31
+ name: prompt.name || '',
32
+ description: prompt.description || '',
33
+ arguments: (prompt.arguments || []).sort((a, b) => {
34
+ const aName = (a.name || '').toString();
35
+ const bName = (b.name || '').toString();
36
+ return aName.localeCompare(bName);
37
+ }),
38
+ }))
39
+ .sort((a, b) => (a.name || '').localeCompare(b.name || '')),
40
+ };
41
+
42
+ // Create deterministic JSON string (sorted keys)
43
+ const jsonString = JSON.stringify(normalized);
44
+
45
+ // Compute SHA-256 hash
46
+ return createHash('sha256').update(jsonString).digest('hex');
47
+ }
@@ -0,0 +1,80 @@
1
+ import { readFileSync, existsSync, readdirSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ensureScanResultsDirectory } from './directory.js';
4
+
5
+ /**
6
+ * Get all cached scan results for a server
7
+ * @param {string} serverName - Name of the MCP server
8
+ * @returns {Array} Array of cached scan results
9
+ */
10
+ export function getCachedScanResultsForServer(serverName) {
11
+ try {
12
+ const scanResultsDir = ensureScanResultsDirectory();
13
+ const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
14
+ const results = [];
15
+
16
+ for (const file of files) {
17
+ try {
18
+ const filePath = join(scanResultsDir, file);
19
+ const fileContent = readFileSync(filePath, 'utf8');
20
+ const data = JSON.parse(fileContent);
21
+
22
+ if (data.serverName === serverName) {
23
+ results.push({
24
+ ...data.scanData,
25
+ cached: true,
26
+ cachedAt: data.createdAt,
27
+ updatedAt: data.updatedAt,
28
+ hash: data.hash,
29
+ });
30
+ }
31
+ } catch (error) {
32
+ // Skip files that can't be parsed
33
+ console.warn(`Error reading scan result file ${file}:`, error);
34
+ }
35
+ }
36
+
37
+ // Sort by updatedAt descending
38
+ results.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
39
+
40
+ return results;
41
+ } catch (error) {
42
+ console.error('Error getting cached scan results for server:', error);
43
+ return [];
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Clear old scan results (optional cleanup function)
49
+ * @param {number} maxAgeMs - Maximum age in milliseconds (default: 30 days)
50
+ * @returns {number} Number of files deleted
51
+ */
52
+ export function clearOldScanResults(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
53
+ try {
54
+ const scanResultsDir = ensureScanResultsDirectory();
55
+ const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
56
+ const cutoffTime = Date.now() - maxAgeMs;
57
+ let deletedCount = 0;
58
+
59
+ for (const file of files) {
60
+ try {
61
+ const filePath = join(scanResultsDir, file);
62
+ const fileContent = readFileSync(filePath, 'utf8');
63
+ const data = JSON.parse(fileContent);
64
+
65
+ if (data.updatedAt && data.updatedAt < cutoffTime) {
66
+ unlinkSync(filePath);
67
+ deletedCount++;
68
+ }
69
+ } catch (error) {
70
+ // Skip files that can't be parsed
71
+ console.warn(`Error processing scan result file ${file} for cleanup:`, error);
72
+ }
73
+ }
74
+
75
+ return deletedCount;
76
+ } catch (error) {
77
+ console.error('Error clearing old scan results:', error);
78
+ return 0;
79
+ }
80
+ }
@@ -0,0 +1,12 @@
1
+ // Main entry point - re-export all functions
2
+ export { computeMcpHash } from './scan-cache/hash.js';
3
+ export {
4
+ getCachedScanResult,
5
+ storeScanResult,
6
+ clearAllScanResults,
7
+ } from './scan-cache/file-operations.js';
8
+ export {
9
+ getCachedScanResultsForServer,
10
+ clearOldScanResults,
11
+ } from './scan-cache/server-operations.js';
12
+ export { getAllCachedScanResults } from './scan-cache/all-results.js';
@@ -0,0 +1,13 @@
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);
5
+ if (typeof obj === 'object') {
6
+ const result = {};
7
+ for (const [key, value] of Object.entries(obj)) {
8
+ result[key] = serializeBigInt(value);
9
+ }
10
+ return result;
11
+ }
12
+ return obj;
13
+ }
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { getWorkingDirectory, prepareAppDataSpaces } from 'mcp-shark-common/configs/index.js';
3
+ import { join } from 'node:path';
4
+
5
+ const SMART_SCAN_TOKEN_NAME = 'smart-scan-token.json';
6
+
7
+ function getSmartScanTokenPath() {
8
+ return join(getWorkingDirectory(), SMART_SCAN_TOKEN_NAME);
9
+ }
10
+
11
+ export function readSmartScanToken() {
12
+ try {
13
+ const tokenPath = getSmartScanTokenPath();
14
+ if (existsSync(tokenPath)) {
15
+ const content = readFileSync(tokenPath, 'utf8');
16
+ const data = JSON.parse(content);
17
+ return data.token || null;
18
+ }
19
+ return null;
20
+ } catch (error) {
21
+ console.error('Error reading Smart Scan token:', error);
22
+ return null;
23
+ }
24
+ }
25
+
26
+ export function writeSmartScanToken(token) {
27
+ try {
28
+ const tokenPath = getSmartScanTokenPath();
29
+ prepareAppDataSpaces(); // Ensure directory exists
30
+
31
+ const data = {
32
+ token: token || null,
33
+ updatedAt: new Date().toISOString(),
34
+ };
35
+
36
+ writeFileSync(tokenPath, JSON.stringify(data, null, 2), { mode: 0o600 }); // Read/write for owner only
37
+ return true;
38
+ } catch (error) {
39
+ console.error('Error writing Smart Scan token:', error);
40
+ return false;
41
+ }
42
+ }