@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.
- package/README.md +84 -645
- package/bin/mcp-shark.js +30 -36
- package/mcp-server/index.js +115 -0
- package/mcp-server/lib/auditor/audit.js +34 -42
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +21 -40
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +5 -7
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-read.js +5 -4
- package/mcp-server/lib/server/internal/handlers/tools-call.js +8 -10
- package/mcp-server/lib/server/internal/handlers/tools-list.js +6 -5
- package/mcp-server/lib/server/internal/run.js +5 -17
- package/mcp-server/lib/server/internal/server.js +14 -15
- package/mcp-server/lib/server/internal/session.js +8 -13
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +91 -62
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +140 -112
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +87 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -10
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +52 -23
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +13 -11
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +66 -34
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +13 -11
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +52 -23
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +13 -11
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +70 -0
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +90 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +118 -159
- package/ui/src/components/McpPlayground.jsx +105 -23
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +37 -105
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -244
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
107
|
+
logger.debug({ serverName, scanId }, 'Successfully loaded scan');
|
|
102
108
|
return scanResult;
|
|
103
109
|
} catch (error) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
} catch (
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
const deletedCount = files.reduce((count, file) => {
|
|
90
92
|
try {
|
|
91
93
|
const filePath = join(scanResultsDir, file);
|
|
92
94
|
unlinkSync(filePath);
|
|
93
|
-
|
|
95
|
+
return count + 1;
|
|
94
96
|
} catch (error) {
|
|
95
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
return count + 1;
|
|
68
68
|
}
|
|
69
|
+
return count;
|
|
69
70
|
} catch (error) {
|
|
70
71
|
// Skip files that can't be parsed
|
|
71
|
-
|
|
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
|
-
|
|
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)
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
17
|
-
import {
|
|
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 {
|
|
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
|
|
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 = {
|
|
43
|
+
const processState = { mcpSharkServer: null };
|
|
46
44
|
|
|
47
|
-
const setMcpSharkProcess = (
|
|
48
|
-
processState.
|
|
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.
|
|
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
|
-
|
|
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('*', (
|
|
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
|
-
|
|
189
|
-
|
|
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.
|
|
193
|
-
|
|
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
|
|
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 {
|
|
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 {
|