@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,7 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
2
|
import { homedir } from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
4
|
import { storeOriginalConfig } from './config.js';
|
|
5
|
+
import logger from './logger.js';
|
|
5
6
|
|
|
6
7
|
function findLatestBackup(filePath) {
|
|
7
8
|
const dir = path.dirname(filePath);
|
|
@@ -51,143 +52,144 @@ function findLatestBackup(filePath) {
|
|
|
51
52
|
backups.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
52
53
|
return backups[0].backupPath;
|
|
53
54
|
} catch (error) {
|
|
54
|
-
|
|
55
|
+
logger.error({ error: error.message }, 'Error finding latest backup');
|
|
55
56
|
return null;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
selectedServiceNames,
|
|
60
|
+
function shouldCreateBackup(
|
|
61
|
+
latestBackupPath,
|
|
62
62
|
resolvedFilePath,
|
|
63
63
|
content,
|
|
64
64
|
mcpSharkLogs,
|
|
65
65
|
broadcastLogUpdate
|
|
66
66
|
) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const updatedConfig = { ...originalConfig };
|
|
71
|
-
|
|
72
|
-
if (hasMcpServers) {
|
|
73
|
-
const updatedMcpServers = {};
|
|
74
|
-
if (selectedServiceNames.size > 0) {
|
|
75
|
-
updatedMcpServers['mcp-shark-server'] = {
|
|
76
|
-
type: 'http',
|
|
77
|
-
url: 'http://localhost:9851/mcp',
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
Object.entries(originalConfig.mcpServers).forEach(([name, cfg]) => {
|
|
81
|
-
if (!selectedServiceNames.has(name)) {
|
|
82
|
-
updatedMcpServers[name] = cfg;
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
updatedConfig.mcpServers = updatedMcpServers;
|
|
86
|
-
} else if (hasServers) {
|
|
87
|
-
const updatedServers = {};
|
|
88
|
-
if (selectedServiceNames.size > 0) {
|
|
89
|
-
updatedServers['mcp-shark-server'] = {
|
|
90
|
-
type: 'http',
|
|
91
|
-
url: 'http://localhost:9851/mcp',
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
Object.entries(originalConfig.servers).forEach(([name, cfg]) => {
|
|
95
|
-
if (!selectedServiceNames.has(name)) {
|
|
96
|
-
updatedServers[name] = cfg;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
updatedConfig.servers = updatedServers;
|
|
100
|
-
} else {
|
|
101
|
-
updatedConfig.mcpServers = {
|
|
102
|
-
'mcp-shark-server': {
|
|
103
|
-
type: 'http',
|
|
104
|
-
url: 'http://localhost:9851/mcp',
|
|
105
|
-
},
|
|
106
|
-
};
|
|
67
|
+
if (!latestBackupPath || !fs.existsSync(latestBackupPath)) {
|
|
68
|
+
return true;
|
|
107
69
|
}
|
|
108
70
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const latestBackupPath = findLatestBackup(resolvedFilePath);
|
|
113
|
-
let shouldCreateBackup = true;
|
|
71
|
+
try {
|
|
72
|
+
const latestBackupContent = fs.readFileSync(latestBackupPath, 'utf-8');
|
|
73
|
+
const currentContent = content || fs.readFileSync(resolvedFilePath, 'utf-8');
|
|
114
74
|
|
|
115
|
-
|
|
75
|
+
// Normalize both contents for comparison (remove whitespace differences)
|
|
76
|
+
const normalizeContent = (str) => {
|
|
116
77
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
// Try to parse as JSON and re-stringify to normalize
|
|
124
|
-
return JSON.stringify(JSON.parse(str), null, 2);
|
|
125
|
-
} catch {
|
|
126
|
-
// If not valid JSON, just trim
|
|
127
|
-
return str.trim();
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const normalizedBackup = normalizeContent(latestBackupContent);
|
|
132
|
-
const normalizedCurrent = normalizeContent(currentContent);
|
|
133
|
-
|
|
134
|
-
if (normalizedBackup === normalizedCurrent) {
|
|
135
|
-
shouldCreateBackup = false;
|
|
136
|
-
const timestamp = new Date().toISOString();
|
|
137
|
-
const skipLog = {
|
|
138
|
-
timestamp,
|
|
139
|
-
type: 'stdout',
|
|
140
|
-
line: `[BACKUP] Skipped backup (no changes detected): ${resolvedFilePath.replace(homedir(), '~')}`,
|
|
141
|
-
};
|
|
142
|
-
mcpSharkLogs.push(skipLog);
|
|
143
|
-
if (mcpSharkLogs.length > 10000) {
|
|
144
|
-
mcpSharkLogs.shift();
|
|
145
|
-
}
|
|
146
|
-
broadcastLogUpdate(skipLog);
|
|
147
|
-
}
|
|
148
|
-
} catch (error) {
|
|
149
|
-
console.error('Error comparing with latest backup:', error);
|
|
150
|
-
// If comparison fails, create backup to be safe
|
|
151
|
-
shouldCreateBackup = true;
|
|
78
|
+
// Try to parse as JSON and re-stringify to normalize
|
|
79
|
+
return JSON.stringify(JSON.parse(str), null, 2);
|
|
80
|
+
} catch {
|
|
81
|
+
// If not valid JSON, just trim
|
|
82
|
+
return str.trim();
|
|
152
83
|
}
|
|
153
|
-
}
|
|
84
|
+
};
|
|
154
85
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const now = new Date();
|
|
158
|
-
// Format: YYYY-MM-DD_HH-MM-SS
|
|
159
|
-
const year = now.getFullYear();
|
|
160
|
-
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
161
|
-
const day = String(now.getDate()).padStart(2, '0');
|
|
162
|
-
const hours = String(now.getHours()).padStart(2, '0');
|
|
163
|
-
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
164
|
-
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
165
|
-
const datetimeStr = `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
166
|
-
const dir = path.dirname(resolvedFilePath);
|
|
167
|
-
const basename = path.basename(resolvedFilePath);
|
|
168
|
-
createdBackupPath = path.join(dir, `.${basename}-mcpshark.${datetimeStr}.json`);
|
|
169
|
-
fs.copyFileSync(resolvedFilePath, createdBackupPath);
|
|
170
|
-
storeOriginalConfig(resolvedFilePath, content, createdBackupPath);
|
|
86
|
+
const normalizedBackup = normalizeContent(latestBackupContent);
|
|
87
|
+
const normalizedCurrent = normalizeContent(currentContent);
|
|
171
88
|
|
|
89
|
+
if (normalizedBackup === normalizedCurrent) {
|
|
172
90
|
const timestamp = new Date().toISOString();
|
|
173
|
-
const
|
|
91
|
+
const skipLog = {
|
|
174
92
|
timestamp,
|
|
175
93
|
type: 'stdout',
|
|
176
|
-
line: `[BACKUP]
|
|
94
|
+
line: `[BACKUP] Skipped backup (no changes detected): ${resolvedFilePath.replace(homedir(), '~')}`,
|
|
177
95
|
};
|
|
178
|
-
mcpSharkLogs.push(
|
|
96
|
+
mcpSharkLogs.push(skipLog);
|
|
179
97
|
if (mcpSharkLogs.length > 10000) {
|
|
180
98
|
mcpSharkLogs.shift();
|
|
181
99
|
}
|
|
182
|
-
broadcastLogUpdate(
|
|
183
|
-
|
|
184
|
-
// Still store the original config reference even if we didn't create a new backup
|
|
185
|
-
// Use the latest backup path if available
|
|
186
|
-
storeOriginalConfig(resolvedFilePath, content, latestBackupPath);
|
|
100
|
+
broadcastLogUpdate(skipLog);
|
|
101
|
+
return false;
|
|
187
102
|
}
|
|
103
|
+
return true;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error({ error: error.message }, 'Error comparing with latest backup');
|
|
106
|
+
// If comparison fails, create backup to be safe
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
188
110
|
|
|
111
|
+
function createBackup(resolvedFilePath, content, mcpSharkLogs, broadcastLogUpdate) {
|
|
112
|
+
// Create backup with new format: .mcp.json-mcpshark.<datetime>.json
|
|
113
|
+
const datetimeStr = formatDateTimeForBackup();
|
|
114
|
+
const dir = path.dirname(resolvedFilePath);
|
|
115
|
+
const basename = path.basename(resolvedFilePath);
|
|
116
|
+
const backupPath = path.join(dir, `.${basename}-mcpshark.${datetimeStr}.json`);
|
|
117
|
+
fs.copyFileSync(resolvedFilePath, backupPath);
|
|
118
|
+
storeOriginalConfig(resolvedFilePath, content, backupPath);
|
|
119
|
+
|
|
120
|
+
const timestamp = new Date().toISOString();
|
|
121
|
+
const backupLog = {
|
|
122
|
+
timestamp,
|
|
123
|
+
type: 'stdout',
|
|
124
|
+
line: `[BACKUP] Created backup: ${backupPath.replace(homedir(), '~')}`,
|
|
125
|
+
};
|
|
126
|
+
mcpSharkLogs.push(backupLog);
|
|
127
|
+
if (mcpSharkLogs.length > 10000) {
|
|
128
|
+
mcpSharkLogs.shift();
|
|
129
|
+
}
|
|
130
|
+
broadcastLogUpdate(backupLog);
|
|
131
|
+
return backupPath;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function computeBackupPath(resolvedFilePath, content, mcpSharkLogs, broadcastLogUpdate) {
|
|
135
|
+
if (!resolvedFilePath || !fs.existsSync(resolvedFilePath)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if we need to create a backup by comparing with latest backup
|
|
140
|
+
const latestBackupPath = findLatestBackup(resolvedFilePath);
|
|
141
|
+
const needsBackup = shouldCreateBackup(
|
|
142
|
+
latestBackupPath,
|
|
143
|
+
resolvedFilePath,
|
|
144
|
+
content,
|
|
145
|
+
mcpSharkLogs,
|
|
146
|
+
broadcastLogUpdate
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (needsBackup) {
|
|
150
|
+
return createBackup(resolvedFilePath, content, mcpSharkLogs, broadcastLogUpdate);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Still store the original config reference even if we didn't create a new backup
|
|
154
|
+
// Use the latest backup path if available
|
|
155
|
+
storeOriginalConfig(resolvedFilePath, content, latestBackupPath);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function updateConfigFile(
|
|
160
|
+
originalConfig,
|
|
161
|
+
_selectedServiceNames,
|
|
162
|
+
resolvedFilePath,
|
|
163
|
+
content,
|
|
164
|
+
mcpSharkLogs,
|
|
165
|
+
broadcastLogUpdate
|
|
166
|
+
) {
|
|
167
|
+
const [serverObject, serverType] = getServerObject(originalConfig);
|
|
168
|
+
const updatedConfig = { ...originalConfig };
|
|
169
|
+
|
|
170
|
+
if (serverObject) {
|
|
171
|
+
const updatedServers = {};
|
|
172
|
+
// Transform all original servers to HTTP URLs pointing to MCP shark server
|
|
173
|
+
// Each server gets its own endpoint to avoid tool name prefixing issues
|
|
174
|
+
Object.entries(serverObject).forEach(([name, _cfg]) => {
|
|
175
|
+
updatedServers[name] = {
|
|
176
|
+
type: 'http',
|
|
177
|
+
url: `http://localhost:9851/mcp/${encodeURIComponent(name)}`,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
updatedConfig[serverType] = updatedServers;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const createdBackupPath = computeBackupPath(
|
|
184
|
+
resolvedFilePath,
|
|
185
|
+
content,
|
|
186
|
+
mcpSharkLogs,
|
|
187
|
+
broadcastLogUpdate
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (resolvedFilePath && fs.existsSync(resolvedFilePath)) {
|
|
189
191
|
fs.writeFileSync(resolvedFilePath, JSON.stringify(updatedConfig, null, 2));
|
|
190
|
-
|
|
192
|
+
logger.info({ path: resolvedFilePath }, 'Updated config file');
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
return { updatedConfig, backupPath: createdBackupPath };
|
|
@@ -210,3 +212,29 @@ export function getSelectedServiceNames(originalConfig, selectedServices) {
|
|
|
210
212
|
|
|
211
213
|
return selectedServiceNames;
|
|
212
214
|
}
|
|
215
|
+
|
|
216
|
+
function getServerObject(originalConfig) {
|
|
217
|
+
const hasMcpServers = originalConfig.mcpServers && typeof originalConfig.mcpServers === 'object';
|
|
218
|
+
const hasServers = originalConfig.servers && typeof originalConfig.servers === 'object';
|
|
219
|
+
|
|
220
|
+
if (hasMcpServers) {
|
|
221
|
+
return [originalConfig.mcpServers, 'mcpServers'];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (hasServers) {
|
|
225
|
+
return [originalConfig.servers, 'servers'];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return [null, null];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function formatDateTimeForBackup() {
|
|
232
|
+
const now = new Date();
|
|
233
|
+
const year = now.getFullYear();
|
|
234
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
235
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
236
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
237
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
238
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
239
|
+
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
240
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
|
-
import
|
|
2
|
+
import logger from './logger.js';
|
|
3
3
|
|
|
4
4
|
const state = { originalConfigData: null };
|
|
5
5
|
|
|
@@ -8,21 +8,21 @@ export function storeOriginalConfig(filePath, originalContent, backupPath) {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function restoreOriginalConfig(mcpSharkLogs, broadcastLogUpdate) {
|
|
11
|
-
if (state.originalConfigData
|
|
11
|
+
if (state.originalConfigData?.filePath) {
|
|
12
12
|
try {
|
|
13
13
|
if (fs.existsSync(state.originalConfigData.filePath)) {
|
|
14
14
|
fs.writeFileSync(
|
|
15
15
|
state.originalConfigData.filePath,
|
|
16
16
|
state.originalConfigData.originalContent
|
|
17
17
|
);
|
|
18
|
-
|
|
18
|
+
logger.info({ path: state.originalConfigData.filePath }, 'Restored original config');
|
|
19
19
|
state.originalConfigData = null;
|
|
20
20
|
return true;
|
|
21
21
|
}
|
|
22
22
|
state.originalConfigData = null;
|
|
23
23
|
return false;
|
|
24
24
|
} catch (error) {
|
|
25
|
-
|
|
25
|
+
logger.error({ error: error.message }, 'Failed to restore original config');
|
|
26
26
|
const timestamp = new Date().toISOString();
|
|
27
27
|
const errorLog = {
|
|
28
28
|
timestamp,
|
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
|
-
}
|