@mcp-shark/mcp-shark 1.4.2 → 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 +22 -38
- 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 +4 -12
- 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 +3 -13
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
- package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
- package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
- package/mcp-server/lib/server/internal/run.js +4 -16
- package/mcp-server/lib/server/internal/server.js +6 -7
- package/mcp-server/lib/server/internal/session.js +2 -15
- 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 +45 -47
- 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 +7 -6
- 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 +86 -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 -4
- 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 +6 -2
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
- 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 +6 -2
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
- 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 +6 -2
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
- package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
- package/ui/src/components/McpPlayground.jsx +5 -2
- 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 +32 -101
- 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 -260
package/ui/server/utils/paths.js
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
-
import
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import logger from './logger.js';
|
|
4
7
|
|
|
5
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
9
|
const __dirname = path.dirname(__filename);
|
|
7
10
|
|
|
11
|
+
function getNvmNodeBinPaths(homeDir) {
|
|
12
|
+
try {
|
|
13
|
+
const nvmVersionsPath = path.join(homeDir, '.nvm', 'versions', 'node');
|
|
14
|
+
if (fs.existsSync(nvmVersionsPath)) {
|
|
15
|
+
return fs
|
|
16
|
+
.readdirSync(nvmVersionsPath, { withFileTypes: true })
|
|
17
|
+
.filter((dirent) => dirent.isDirectory())
|
|
18
|
+
.map((dirent) => path.join(nvmVersionsPath, dirent.name, 'bin'));
|
|
19
|
+
}
|
|
20
|
+
} catch (_e) {}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
export function findMcpServerPath() {
|
|
9
25
|
const pathsToCheck = [
|
|
10
26
|
path.join(process.cwd(), '../mcp-server'),
|
|
@@ -21,3 +37,195 @@ export function findMcpServerPath() {
|
|
|
21
37
|
|
|
22
38
|
return path.join(process.cwd(), '../mcp-server');
|
|
23
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get system PATH from the host machine's shell environment
|
|
43
|
+
* This works in Electron by executing a shell command to get the actual PATH
|
|
44
|
+
* Includes both system PATH and user's custom PATH from shell config files
|
|
45
|
+
*/
|
|
46
|
+
function getSystemPath() {
|
|
47
|
+
try {
|
|
48
|
+
if (process.platform === 'win32') {
|
|
49
|
+
const pathOutput = execSync('cmd /c echo %PATH%', {
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
timeout: 2000,
|
|
52
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
53
|
+
});
|
|
54
|
+
return pathOutput.trim();
|
|
55
|
+
}
|
|
56
|
+
const userShell = process.env.SHELL || '/bin/zsh';
|
|
57
|
+
const shells = [userShell, '/bin/zsh', '/bin/bash', '/bin/sh'];
|
|
58
|
+
|
|
59
|
+
for (const shell of shells) {
|
|
60
|
+
if (fs.existsSync(shell)) {
|
|
61
|
+
try {
|
|
62
|
+
const shellName = path.basename(shell);
|
|
63
|
+
|
|
64
|
+
const getPathOutput = (shell, shellName) => {
|
|
65
|
+
const execOptions = {
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
timeout: 2000,
|
|
68
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
69
|
+
maxBuffer: 1024 * 1024,
|
|
70
|
+
env: {
|
|
71
|
+
...Object.fromEntries(
|
|
72
|
+
Object.entries(process.env).filter(([key]) => key !== 'PATH')
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (shellName === 'zsh') {
|
|
78
|
+
try {
|
|
79
|
+
return execSync(`${shell} -i -c 'echo $PATH'`, execOptions);
|
|
80
|
+
} catch (_e) {
|
|
81
|
+
return execSync(`${shell} -l -c 'echo $PATH'`, execOptions);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return execSync(`${shell} -l -c 'echo $PATH'`, execOptions);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const pathOutput = getPathOutput(shell, shellName);
|
|
89
|
+
const systemPath = pathOutput.trim();
|
|
90
|
+
if (systemPath) {
|
|
91
|
+
logger.info({ shell, shellName }, 'Got PATH from shell');
|
|
92
|
+
return systemPath;
|
|
93
|
+
}
|
|
94
|
+
} catch (_e) {}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const homeDir = os.homedir();
|
|
99
|
+
const configFiles = [
|
|
100
|
+
{ file: path.join(homeDir, '.zshrc'), shell: 'zsh', interactive: true },
|
|
101
|
+
{ file: path.join(homeDir, '.zprofile'), shell: 'zsh', interactive: false },
|
|
102
|
+
{ file: path.join(homeDir, '.zlogin'), shell: 'zsh', interactive: false },
|
|
103
|
+
{ file: path.join(homeDir, '.bashrc'), shell: 'bash', interactive: true },
|
|
104
|
+
{ file: path.join(homeDir, '.bash_profile'), shell: 'bash', interactive: false },
|
|
105
|
+
{ file: path.join(homeDir, '.profile'), shell: 'sh', interactive: false },
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
for (const { file, shell: shellName, interactive } of configFiles) {
|
|
109
|
+
if (fs.existsSync(file)) {
|
|
110
|
+
try {
|
|
111
|
+
const flag = shellName === 'zsh' && interactive ? '-i' : '';
|
|
112
|
+
const pathOutput = execSync(
|
|
113
|
+
`/bin/${shellName} ${flag} -c 'source ${file} 2>/dev/null; echo $PATH'`,
|
|
114
|
+
{
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
timeout: 2000,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
118
|
+
maxBuffer: 1024 * 1024,
|
|
119
|
+
env: {
|
|
120
|
+
...Object.fromEntries(
|
|
121
|
+
Object.entries(process.env).filter(([key]) => key !== 'PATH')
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
const systemPath = pathOutput.trim();
|
|
127
|
+
if (systemPath && systemPath.length > 10) {
|
|
128
|
+
logger.info({ file }, 'Got PATH from file');
|
|
129
|
+
return systemPath;
|
|
130
|
+
}
|
|
131
|
+
} catch (_e) {}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
logger.warn({ error: error.message }, 'Could not get system PATH');
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Enhance PATH environment variable to include system paths and user paths
|
|
142
|
+
* This is especially important in Electron where PATH might not include system executables
|
|
143
|
+
*/
|
|
144
|
+
export function enhancePath(originalPath) {
|
|
145
|
+
const homeDir = os.homedir();
|
|
146
|
+
const pathSeparator = process.platform === 'win32' ? ';' : ':';
|
|
147
|
+
|
|
148
|
+
const systemPath = getSystemPath();
|
|
149
|
+
if (systemPath) {
|
|
150
|
+
logger.info('Using system PATH from host machine');
|
|
151
|
+
const userPaths = [
|
|
152
|
+
path.join(homeDir, '.local', 'bin'),
|
|
153
|
+
path.join(homeDir, '.npm-global', 'bin'),
|
|
154
|
+
path.join(homeDir, '.cargo', 'bin'),
|
|
155
|
+
path.join(homeDir, 'bin'),
|
|
156
|
+
path.join(homeDir, '.nvm', 'current', 'bin'),
|
|
157
|
+
...getNvmNodeBinPaths(homeDir),
|
|
158
|
+
path.join(homeDir, '.fnm', 'node-versions', 'v20.0.0', 'install', 'bin'),
|
|
159
|
+
path.join(homeDir, '.pyenv', 'shims'),
|
|
160
|
+
path.join(homeDir, '.pyenv', 'bin'),
|
|
161
|
+
path.join(homeDir, '.gvm', 'bin'),
|
|
162
|
+
path.join(homeDir, '.gvm', 'gos', 'current', 'bin'),
|
|
163
|
+
path.join(homeDir, '.cargo', 'bin'),
|
|
164
|
+
path.join(homeDir, 'go', 'bin'),
|
|
165
|
+
path.join(homeDir, '.go', 'bin'),
|
|
166
|
+
'/Applications/iTerm.app/Contents/Resources/utilities',
|
|
167
|
+
...(process.platform === 'win32'
|
|
168
|
+
? [
|
|
169
|
+
path.join(homeDir, 'AppData', 'Local', 'Programs'),
|
|
170
|
+
path.join(homeDir, 'AppData', 'Roaming', 'npm'),
|
|
171
|
+
path.join(homeDir, 'AppData', 'Local', 'Microsoft', 'WindowsApps'),
|
|
172
|
+
]
|
|
173
|
+
: []),
|
|
174
|
+
].filter((p) => {
|
|
175
|
+
if (p.includes('v20.0.0') || p.includes('current')) {
|
|
176
|
+
return fs.existsSync(path.dirname(p));
|
|
177
|
+
}
|
|
178
|
+
return fs.existsSync(p);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return [systemPath, ...userPaths, originalPath || ''].filter((p) => p).join(pathSeparator);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
logger.info('Could not get system PATH, adding common locations');
|
|
185
|
+
const pathsToAdd = [
|
|
186
|
+
'/usr/local/bin',
|
|
187
|
+
'/usr/bin',
|
|
188
|
+
'/opt/homebrew/bin',
|
|
189
|
+
'/usr/local/opt/node/bin',
|
|
190
|
+
'/opt/local/bin',
|
|
191
|
+
'/sbin',
|
|
192
|
+
'/usr/sbin',
|
|
193
|
+
...(process.platform === 'darwin'
|
|
194
|
+
? [
|
|
195
|
+
'/opt/homebrew/opt/python/bin',
|
|
196
|
+
'/usr/local/opt/python/bin',
|
|
197
|
+
'/Applications/Docker.app/Contents/Resources/bin',
|
|
198
|
+
]
|
|
199
|
+
: []),
|
|
200
|
+
...(process.platform === 'linux' ? ['/snap/bin', path.join(homeDir, '.local', 'bin')] : []),
|
|
201
|
+
...(process.platform === 'win32'
|
|
202
|
+
? [
|
|
203
|
+
path.join(process.env.ProgramFiles || '', 'nodejs'),
|
|
204
|
+
path.join(process.env['ProgramFiles(x86)'] || '', 'nodejs'),
|
|
205
|
+
path.join(homeDir, 'AppData', 'Roaming', 'npm'),
|
|
206
|
+
path.join(process.env.ProgramFiles || '', 'Docker', 'Docker', 'resources', 'bin'),
|
|
207
|
+
]
|
|
208
|
+
: []),
|
|
209
|
+
path.join(homeDir, '.local', 'bin'),
|
|
210
|
+
path.join(homeDir, '.npm-global', 'bin'),
|
|
211
|
+
path.join(homeDir, '.cargo', 'bin'),
|
|
212
|
+
path.join(homeDir, 'bin'),
|
|
213
|
+
path.join(homeDir, '.nvm', 'current', 'bin'),
|
|
214
|
+
...getNvmNodeBinPaths(homeDir),
|
|
215
|
+
path.join(homeDir, '.pyenv', 'shims'),
|
|
216
|
+
path.join(homeDir, '.pyenv', 'bin'),
|
|
217
|
+
path.join(homeDir, '.gvm', 'bin'),
|
|
218
|
+
path.join(homeDir, '.gvm', 'gos', 'current', 'bin'),
|
|
219
|
+
path.join(homeDir, 'go', 'bin'),
|
|
220
|
+
path.join(homeDir, '.go', 'bin'),
|
|
221
|
+
'/Applications/iTerm.app/Contents/Resources/utilities',
|
|
222
|
+
...(process.platform === 'win32'
|
|
223
|
+
? [
|
|
224
|
+
path.join(homeDir, 'AppData', 'Local', 'Programs'),
|
|
225
|
+
path.join(homeDir, 'AppData', 'Local', 'Microsoft', 'WindowsApps'),
|
|
226
|
+
]
|
|
227
|
+
: []),
|
|
228
|
+
].filter((p) => p && fs.existsSync(p));
|
|
229
|
+
|
|
230
|
+
return [...pathsToAdd, originalPath || ''].filter((p) => p).join(pathSeparator);
|
|
231
|
+
}
|
package/ui/server/utils/port.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createConnection } from 'net';
|
|
1
|
+
import { createConnection } from 'node:net';
|
|
2
2
|
|
|
3
3
|
export function checkPortReady(port, host = 'localhost', timeout = 10000) {
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
@@ -12,7 +12,7 @@ export function checkPortReady(port, host = 'localhost', timeout = 10000) {
|
|
|
12
12
|
resolve(true);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
socket.on('error', (
|
|
15
|
+
socket.on('error', (_err) => {
|
|
16
16
|
socket.destroy();
|
|
17
17
|
const elapsed = Date.now() - startTime;
|
|
18
18
|
if (elapsed >= timeout) {
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { findMcpServerPath } from './paths.js';
|
|
4
|
-
import { enhancePath } from '../../paths.js';
|
|
5
|
-
import { getMcpConfigPath, getWorkingDirectory } from 'mcp-shark-common/configs/index.js';
|
|
6
|
-
|
|
7
1
|
const MAX_LOG_LINES = 10000;
|
|
8
2
|
|
|
9
3
|
export function createLogEntry(mcpSharkLogs, broadcastLogUpdate) {
|
|
@@ -17,64 +11,3 @@ export function createLogEntry(mcpSharkLogs, broadcastLogUpdate) {
|
|
|
17
11
|
broadcastLogUpdate({ timestamp, type, line });
|
|
18
12
|
};
|
|
19
13
|
}
|
|
20
|
-
|
|
21
|
-
export function spawnMcpSharkServer(mcpSharkJsPath, mcpsJsonPath, logEntry) {
|
|
22
|
-
const mcpServerPath = findMcpServerPath();
|
|
23
|
-
const nodeExecutable = process.execPath || 'node';
|
|
24
|
-
const enhancedPath = enhancePath(process.env.PATH);
|
|
25
|
-
|
|
26
|
-
logEntry('info', `[UI Server] Spawning MCP-Shark server...`);
|
|
27
|
-
logEntry('info', `[UI Server] Executable: ${nodeExecutable}`);
|
|
28
|
-
logEntry('info', `[UI Server] Script: ${mcpSharkJsPath}`);
|
|
29
|
-
logEntry('info', `[UI Server] Config: ${mcpsJsonPath}`);
|
|
30
|
-
logEntry('info', `[UI Server] CWD: ${mcpServerPath}`);
|
|
31
|
-
logEntry('info', `[UI Server] Data dir: ${getWorkingDirectory()}`);
|
|
32
|
-
logEntry('info', `[UI Server] Enhanced PATH: ${enhancedPath}`);
|
|
33
|
-
|
|
34
|
-
const processHandle = spawn(nodeExecutable, [mcpSharkJsPath, mcpsJsonPath], {
|
|
35
|
-
cwd: mcpServerPath,
|
|
36
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
|
-
env: {
|
|
38
|
-
...process.env,
|
|
39
|
-
PATH: enhancedPath,
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
console.log(`[UI Server] MCP-Shark process spawned with PID: ${processHandle.pid}`);
|
|
44
|
-
|
|
45
|
-
processHandle.stdout.on('data', (data) => {
|
|
46
|
-
logEntry('stdout', data);
|
|
47
|
-
process.stdout.write(data);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
processHandle.stderr.on('data', (data) => {
|
|
51
|
-
logEntry('stderr', data);
|
|
52
|
-
process.stderr.write(data);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return processHandle;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function setupProcessHandlers(processHandle, logEntry, onError, onExit) {
|
|
59
|
-
processHandle.on('error', (err) => {
|
|
60
|
-
console.error('Failed to start mcp-shark server:', err);
|
|
61
|
-
logEntry('error', `Failed to start mcp-shark server: ${err.message}`);
|
|
62
|
-
if (onError) onError(err);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
processHandle.on('exit', (code, signal) => {
|
|
66
|
-
const message = `MCP Shark server process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`;
|
|
67
|
-
console.log(`[UI Server] ${message}`);
|
|
68
|
-
logEntry('exit', message);
|
|
69
|
-
if (code !== 0 && code !== null) {
|
|
70
|
-
console.error(`[UI Server] MCP-Shark process exited with non-zero code: ${code}`);
|
|
71
|
-
logEntry('error', `Process exited with code ${code}`);
|
|
72
|
-
}
|
|
73
|
-
if (onExit) onExit(code, signal);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getMcpSharkJsPath() {
|
|
78
|
-
const mcpServerPath = findMcpServerPath();
|
|
79
|
-
return path.join(mcpServerPath, 'mcp-shark.js');
|
|
80
|
-
}
|
|
@@ -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
|
}
|