@mcp-shark/mcp-shark 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +85 -0
- package/README.md +724 -0
- package/bin/mcp-shark.js +93 -0
- package/mcp-server/.editorconfig +15 -0
- package/mcp-server/.prettierignore +11 -0
- package/mcp-server/.prettierrc +12 -0
- package/mcp-server/README.md +280 -0
- package/mcp-server/commitlint.config.cjs +42 -0
- package/mcp-server/eslint.config.js +131 -0
- package/mcp-server/lib/auditor/audit.js +228 -0
- package/mcp-server/lib/common/error.js +15 -0
- package/mcp-server/lib/server/external/all.js +32 -0
- package/mcp-server/lib/server/external/config.js +59 -0
- package/mcp-server/lib/server/external/kv.js +102 -0
- package/mcp-server/lib/server/external/single/client.js +35 -0
- package/mcp-server/lib/server/external/single/request.js +49 -0
- package/mcp-server/lib/server/external/single/run.js +75 -0
- package/mcp-server/lib/server/external/single/transport.js +57 -0
- package/mcp-server/lib/server/internal/handlers/common.js +20 -0
- package/mcp-server/lib/server/internal/handlers/error.js +7 -0
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +22 -0
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +12 -0
- package/mcp-server/lib/server/internal/handlers/resources-list.js +12 -0
- package/mcp-server/lib/server/internal/handlers/resources-read.js +19 -0
- package/mcp-server/lib/server/internal/handlers/tools-call.js +37 -0
- package/mcp-server/lib/server/internal/handlers/tools-list.js +14 -0
- package/mcp-server/lib/server/internal/run.js +49 -0
- package/mcp-server/lib/server/internal/server.js +63 -0
- package/mcp-server/lib/server/internal/session.js +39 -0
- package/mcp-server/mcp-shark.js +72 -0
- package/mcp-server/package-lock.json +4784 -0
- package/mcp-server/package.json +30 -0
- package/package.json +103 -0
- package/ui/README.md +212 -0
- package/ui/index.html +16 -0
- package/ui/package-lock.json +3574 -0
- package/ui/package.json +12 -0
- package/ui/paths.js +282 -0
- package/ui/public/og-image.png +0 -0
- package/ui/server/routes/backups.js +251 -0
- package/ui/server/routes/composite.js +244 -0
- package/ui/server/routes/config.js +175 -0
- package/ui/server/routes/conversations.js +25 -0
- package/ui/server/routes/help.js +43 -0
- package/ui/server/routes/logs.js +32 -0
- package/ui/server/routes/playground.js +152 -0
- package/ui/server/routes/requests.js +235 -0
- package/ui/server/routes/sessions.js +27 -0
- package/ui/server/routes/smartscan/discover.js +117 -0
- package/ui/server/routes/smartscan/scans/clearCache.js +22 -0
- package/ui/server/routes/smartscan/scans/createBatchScans.js +123 -0
- package/ui/server/routes/smartscan/scans/createScan.js +42 -0
- package/ui/server/routes/smartscan/scans/getCachedResults.js +51 -0
- package/ui/server/routes/smartscan/scans/getScan.js +41 -0
- package/ui/server/routes/smartscan/scans/listScans.js +24 -0
- package/ui/server/routes/smartscan/scans.js +13 -0
- package/ui/server/routes/smartscan/token.js +56 -0
- package/ui/server/routes/smartscan/transport.js +53 -0
- package/ui/server/routes/smartscan.js +24 -0
- package/ui/server/routes/statistics.js +83 -0
- package/ui/server/utils/config-update.js +212 -0
- package/ui/server/utils/config.js +98 -0
- package/ui/server/utils/paths.js +23 -0
- package/ui/server/utils/port.js +28 -0
- package/ui/server/utils/process.js +80 -0
- package/ui/server/utils/scan-cache/all-results.js +180 -0
- package/ui/server/utils/scan-cache/directory.js +35 -0
- package/ui/server/utils/scan-cache/file-operations.js +104 -0
- package/ui/server/utils/scan-cache/hash.js +47 -0
- package/ui/server/utils/scan-cache/server-operations.js +80 -0
- package/ui/server/utils/scan-cache.js +12 -0
- package/ui/server/utils/serialization.js +13 -0
- package/ui/server/utils/smartscan-token.js +42 -0
- package/ui/server.js +199 -0
- package/ui/src/App.jsx +153 -0
- package/ui/src/CompositeLogs.jsx +164 -0
- package/ui/src/CompositeSetup.jsx +285 -0
- package/ui/src/HelpGuide/HelpGuideContent.jsx +118 -0
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +58 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +56 -0
- package/ui/src/HelpGuide.jsx +65 -0
- package/ui/src/IntroTour.jsx +140 -0
- package/ui/src/LogDetail.jsx +122 -0
- package/ui/src/LogTable.jsx +242 -0
- package/ui/src/PacketDetail.jsx +190 -0
- package/ui/src/PacketFilters.jsx +222 -0
- package/ui/src/PacketList.jsx +183 -0
- package/ui/src/SmartScan.jsx +178 -0
- package/ui/src/TabNavigation.jsx +143 -0
- package/ui/src/components/App/HelpButton.jsx +64 -0
- package/ui/src/components/App/TrafficTab.jsx +69 -0
- package/ui/src/components/App/useAppState.js +163 -0
- package/ui/src/components/BackupList.jsx +192 -0
- package/ui/src/components/CollapsibleSection.jsx +82 -0
- package/ui/src/components/ConfigFileSection.jsx +84 -0
- package/ui/src/components/ConfigViewerModal.jsx +141 -0
- package/ui/src/components/ConfirmationModal.jsx +129 -0
- package/ui/src/components/DetailsTab/BodySection.jsx +27 -0
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +70 -0
- package/ui/src/components/DetailsTab/HeadersSection.jsx +25 -0
- package/ui/src/components/DetailsTab/InfoSection.jsx +28 -0
- package/ui/src/components/DetailsTab/NetworkInfoSection.jsx +63 -0
- package/ui/src/components/DetailsTab/ProtocolInfoSection.jsx +75 -0
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +46 -0
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +66 -0
- package/ui/src/components/DetailsTab.jsx +31 -0
- package/ui/src/components/DetectedPathsList.jsx +171 -0
- package/ui/src/components/FileInput.jsx +144 -0
- package/ui/src/components/GroupHeader.jsx +76 -0
- package/ui/src/components/GroupedByMcpView.jsx +103 -0
- package/ui/src/components/GroupedByServerView.jsx +134 -0
- package/ui/src/components/GroupedBySessionView.jsx +127 -0
- package/ui/src/components/GroupedViews.jsx +2 -0
- package/ui/src/components/HexTab.jsx +188 -0
- package/ui/src/components/LogsDisplay.jsx +93 -0
- package/ui/src/components/LogsToolbar.jsx +193 -0
- package/ui/src/components/McpPlayground/LoadingModal.jsx +113 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +125 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +48 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +45 -0
- package/ui/src/components/McpPlayground/PromptsSection.jsx +106 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +89 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +59 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +45 -0
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +91 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +125 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +48 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +45 -0
- package/ui/src/components/McpPlayground/ToolsSection.jsx +107 -0
- package/ui/src/components/McpPlayground/common/EmptyState.jsx +17 -0
- package/ui/src/components/McpPlayground/common/ErrorState.jsx +17 -0
- package/ui/src/components/McpPlayground/common/LoadingState.jsx +17 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +280 -0
- package/ui/src/components/McpPlayground.jsx +171 -0
- package/ui/src/components/MessageDisplay.jsx +28 -0
- package/ui/src/components/PacketDetailHeader.jsx +88 -0
- package/ui/src/components/PacketFilters/ExportControls.jsx +126 -0
- package/ui/src/components/PacketFilters/FilterInput.jsx +59 -0
- package/ui/src/components/RawTab.jsx +142 -0
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +155 -0
- package/ui/src/components/RequestRow/RequestRowMain.jsx +240 -0
- package/ui/src/components/RequestRow/ResponseRow.jsx +158 -0
- package/ui/src/components/RequestRow.jsx +70 -0
- package/ui/src/components/ServerControl.jsx +133 -0
- package/ui/src/components/ServiceSelector.jsx +209 -0
- package/ui/src/components/SetupHeader.jsx +30 -0
- package/ui/src/components/SharkLogo.jsx +21 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +64 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +215 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +94 -0
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +26 -0
- package/ui/src/components/SmartScan/DebugInfoSection.jsx +53 -0
- package/ui/src/components/SmartScan/EmptyState.jsx +57 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +48 -0
- package/ui/src/components/SmartScan/ExpandableSection.jsx +93 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +257 -0
- package/ui/src/components/SmartScan/ListViewContent.jsx +75 -0
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +75 -0
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +72 -0
- package/ui/src/components/SmartScan/RawDataSection.jsx +52 -0
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +78 -0
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +92 -0
- package/ui/src/components/SmartScan/ScanDetailView.jsx +141 -0
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +49 -0
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +201 -0
- package/ui/src/components/SmartScan/ScanListView.jsx +73 -0
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +123 -0
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +35 -0
- package/ui/src/components/SmartScan/ScanViewContent.jsx +68 -0
- package/ui/src/components/SmartScan/ScanningProgress.jsx +47 -0
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +43 -0
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +207 -0
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +269 -0
- package/ui/src/components/SmartScan/SmartScanControls.jsx +290 -0
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +77 -0
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +57 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +34 -0
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +121 -0
- package/ui/src/components/SmartScan/hooks/useScanList.js +193 -0
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +87 -0
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +26 -0
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +53 -0
- package/ui/src/components/SmartScan/scanDataUtils.js +98 -0
- package/ui/src/components/SmartScan/useSmartScan.js +72 -0
- package/ui/src/components/SmartScan/utils.js +19 -0
- package/ui/src/components/SmartScanIcons.jsx +58 -0
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +111 -0
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +140 -0
- package/ui/src/components/TabNavigation.jsx +97 -0
- package/ui/src/components/TabNavigationIcons.jsx +40 -0
- package/ui/src/components/TableHeader.jsx +164 -0
- package/ui/src/components/TourOverlay.jsx +117 -0
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +117 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +70 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +45 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +108 -0
- package/ui/src/components/TourTooltip.jsx +83 -0
- package/ui/src/components/ViewModeTabs.jsx +91 -0
- package/ui/src/components/WhatThisDoesSection.jsx +61 -0
- package/ui/src/config/tourSteps.jsx +141 -0
- package/ui/src/hooks/useAnimation.js +92 -0
- package/ui/src/hooks/useConfigManagement.js +124 -0
- package/ui/src/hooks/useServiceExtraction.js +51 -0
- package/ui/src/index.css +42 -0
- package/ui/src/main.jsx +10 -0
- package/ui/src/theme.js +65 -0
- package/ui/src/utils/animations.js +170 -0
- package/ui/src/utils/groupingUtils.js +93 -0
- package/ui/src/utils/hexUtils.js +24 -0
- package/ui/src/utils/mcpGroupingUtils.js +262 -0
- package/ui/src/utils/requestUtils.js +297 -0
- package/ui/vite.config.js +18 -0
package/ui/server.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { WebSocketServer } from 'ws';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
|
+
|
|
7
|
+
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
|
+
import { queryRequests } from 'mcp-shark-common/db/query.js';
|
|
14
|
+
import { restoreOriginalConfig } from './server/utils/config.js';
|
|
15
|
+
|
|
16
|
+
import { createRequestsRoutes } from './server/routes/requests.js';
|
|
17
|
+
import { createConversationsRoutes } from './server/routes/conversations.js';
|
|
18
|
+
import { createSessionsRoutes } from './server/routes/sessions.js';
|
|
19
|
+
import { createStatisticsRoutes } from './server/routes/statistics.js';
|
|
20
|
+
import { createLogsRoutes } from './server/routes/logs.js';
|
|
21
|
+
import { createConfigRoutes } from './server/routes/config.js';
|
|
22
|
+
import { createBackupRoutes } from './server/routes/backups.js';
|
|
23
|
+
import { createCompositeRoutes } from './server/routes/composite.js';
|
|
24
|
+
import { createHelpRoutes } from './server/routes/help.js';
|
|
25
|
+
import { createPlaygroundRoutes } from './server/routes/playground.js';
|
|
26
|
+
import { createSmartScanRoutes } from './server/routes/smartscan.js';
|
|
27
|
+
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
|
|
31
|
+
const MAX_LOG_LINES = 10000;
|
|
32
|
+
|
|
33
|
+
export function createUIServer() {
|
|
34
|
+
prepareAppDataSpaces();
|
|
35
|
+
|
|
36
|
+
const db = openDb(getDatabaseFile());
|
|
37
|
+
const app = express();
|
|
38
|
+
const server = createServer(app);
|
|
39
|
+
const wss = new WebSocketServer({ server });
|
|
40
|
+
|
|
41
|
+
app.use(express.json());
|
|
42
|
+
|
|
43
|
+
const clients = new Set();
|
|
44
|
+
const mcpSharkLogs = [];
|
|
45
|
+
const processState = { mcpSharkProcess: null };
|
|
46
|
+
|
|
47
|
+
const setMcpSharkProcess = (process) => {
|
|
48
|
+
processState.mcpSharkProcess = process;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
wss.on('connection', (ws) => {
|
|
52
|
+
clients.add(ws);
|
|
53
|
+
ws.on('close', () => clients.delete(ws));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const broadcastLogUpdate = (logEntry) => {
|
|
57
|
+
const message = JSON.stringify({ type: 'log', data: logEntry });
|
|
58
|
+
clients.forEach((client) => {
|
|
59
|
+
if (client.readyState === 1) {
|
|
60
|
+
client.send(message);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const restoreConfig = () => {
|
|
66
|
+
return restoreOriginalConfig(mcpSharkLogs, broadcastLogUpdate);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const requestsRoutes = createRequestsRoutes(db);
|
|
70
|
+
const conversationsRoutes = createConversationsRoutes(db);
|
|
71
|
+
const sessionsRoutes = createSessionsRoutes(db);
|
|
72
|
+
const statisticsRoutes = createStatisticsRoutes(db);
|
|
73
|
+
const logsRoutes = createLogsRoutes(mcpSharkLogs, broadcastLogUpdate);
|
|
74
|
+
const configRoutes = createConfigRoutes();
|
|
75
|
+
const backupRoutes = createBackupRoutes();
|
|
76
|
+
const getMcpSharkProcess = () => processState.mcpSharkProcess;
|
|
77
|
+
const compositeRoutes = createCompositeRoutes(
|
|
78
|
+
getMcpSharkProcess,
|
|
79
|
+
setMcpSharkProcess,
|
|
80
|
+
mcpSharkLogs,
|
|
81
|
+
broadcastLogUpdate
|
|
82
|
+
);
|
|
83
|
+
const helpRoutes = createHelpRoutes();
|
|
84
|
+
const playgroundRoutes = createPlaygroundRoutes();
|
|
85
|
+
const smartScanRoutes = createSmartScanRoutes();
|
|
86
|
+
|
|
87
|
+
app.get('/api/requests', requestsRoutes.getRequests);
|
|
88
|
+
app.get('/api/packets', requestsRoutes.getRequests);
|
|
89
|
+
app.get('/api/requests/:frameNumber', requestsRoutes.getRequest);
|
|
90
|
+
app.get('/api/packets/:frameNumber', requestsRoutes.getRequest);
|
|
91
|
+
app.get('/api/requests/export', requestsRoutes.exportRequests);
|
|
92
|
+
app.post('/api/requests/clear', requestsRoutes.clearRequests);
|
|
93
|
+
|
|
94
|
+
app.get('/api/conversations', conversationsRoutes.getConversations);
|
|
95
|
+
|
|
96
|
+
app.get('/api/sessions', sessionsRoutes.getSessions);
|
|
97
|
+
app.get('/api/sessions/:sessionId/requests', sessionsRoutes.getSessionRequests);
|
|
98
|
+
app.get('/api/sessions/:sessionId/packets', sessionsRoutes.getSessionRequests);
|
|
99
|
+
|
|
100
|
+
app.get('/api/statistics', statisticsRoutes.getStatistics);
|
|
101
|
+
|
|
102
|
+
app.get('/api/composite/logs', logsRoutes.getLogs);
|
|
103
|
+
app.post('/api/composite/logs/clear', logsRoutes.clearLogs);
|
|
104
|
+
app.get('/api/composite/logs/export', logsRoutes.exportLogs);
|
|
105
|
+
|
|
106
|
+
app.post('/api/config/services', configRoutes.extractServices);
|
|
107
|
+
app.get('/api/config/read', configRoutes.readConfig);
|
|
108
|
+
app.get('/api/config/detect', configRoutes.detectConfig);
|
|
109
|
+
app.get('/api/config/backups', backupRoutes.listBackups);
|
|
110
|
+
app.get('/api/config/backup/view', backupRoutes.viewBackup);
|
|
111
|
+
app.post('/api/config/restore', (req, res) => {
|
|
112
|
+
backupRoutes.restoreBackup(req, res, mcpSharkLogs, broadcastLogUpdate);
|
|
113
|
+
});
|
|
114
|
+
app.post('/api/config/backup/delete', (req, res) => {
|
|
115
|
+
backupRoutes.deleteBackup(req, res, mcpSharkLogs, broadcastLogUpdate);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
app.post('/api/composite/setup', compositeRoutes.setup);
|
|
119
|
+
app.post('/api/composite/stop', (req, res) => {
|
|
120
|
+
compositeRoutes.stop(req, res, restoreConfig);
|
|
121
|
+
});
|
|
122
|
+
app.get('/api/composite/status', compositeRoutes.getStatus);
|
|
123
|
+
|
|
124
|
+
app.get('/api/help/state', helpRoutes.getState);
|
|
125
|
+
app.post('/api/help/dismiss', helpRoutes.dismiss);
|
|
126
|
+
app.post('/api/help/reset', helpRoutes.reset);
|
|
127
|
+
|
|
128
|
+
app.post('/api/playground/proxy', playgroundRoutes.proxyRequest);
|
|
129
|
+
|
|
130
|
+
app.post('/api/smartscan/scans', smartScanRoutes.createScan);
|
|
131
|
+
app.get('/api/smartscan/scans', smartScanRoutes.listScans);
|
|
132
|
+
app.get('/api/smartscan/scans/:scanId', smartScanRoutes.getScan);
|
|
133
|
+
app.get('/api/smartscan/token', smartScanRoutes.getToken);
|
|
134
|
+
app.post('/api/smartscan/token', smartScanRoutes.saveToken);
|
|
135
|
+
app.get('/api/smartscan/discover', smartScanRoutes.discoverServers);
|
|
136
|
+
app.post('/api/smartscan/scans/batch', smartScanRoutes.createBatchScans);
|
|
137
|
+
app.post('/api/smartscan/cached-results', smartScanRoutes.getCachedResults);
|
|
138
|
+
app.post('/api/smartscan/cache/clear', smartScanRoutes.clearCache);
|
|
139
|
+
|
|
140
|
+
const cleanup = () => {
|
|
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
|
+
});
|
|
153
|
+
|
|
154
|
+
const staticPath = path.join(__dirname, 'dist');
|
|
155
|
+
app.use(express.static(staticPath));
|
|
156
|
+
|
|
157
|
+
app.get('*', (req, res) => {
|
|
158
|
+
res.sendFile(path.join(staticPath, 'index.html'));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const notifyClients = async () => {
|
|
162
|
+
const requests = queryRequests(db, { limit: 100 });
|
|
163
|
+
const { serializeBigInt } = await import('./server/utils/serialization.js');
|
|
164
|
+
const message = JSON.stringify({ type: 'update', data: serializeBigInt(requests) });
|
|
165
|
+
clients.forEach((client) => {
|
|
166
|
+
if (client.readyState === 1) {
|
|
167
|
+
client.send(message);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const timestampState = { lastTs: 0 };
|
|
173
|
+
setInterval(() => {
|
|
174
|
+
const lastCheck = db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets').get();
|
|
175
|
+
if (lastCheck && lastCheck.max_ts > timestampState.lastTs) {
|
|
176
|
+
timestampState.lastTs = lastCheck.max_ts;
|
|
177
|
+
notifyClients();
|
|
178
|
+
}
|
|
179
|
+
}, 500);
|
|
180
|
+
|
|
181
|
+
return { server, cleanup };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function runUIServer() {
|
|
185
|
+
const port = parseInt(process.env.UI_PORT) || 9853;
|
|
186
|
+
const { server, cleanup } = createUIServer();
|
|
187
|
+
|
|
188
|
+
server.listen(port, '0.0.0.0', () => {
|
|
189
|
+
console.log(`UI server listening on http://localhost:${port}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
server.on('close', () => {
|
|
193
|
+
cleanup();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
198
|
+
runUIServer().catch(console.error);
|
|
199
|
+
}
|
package/ui/src/App.jsx
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import CompositeSetup from './CompositeSetup';
|
|
3
|
+
import CompositeLogs from './CompositeLogs';
|
|
4
|
+
import McpPlayground from './components/McpPlayground';
|
|
5
|
+
import SmartScan from './SmartScan';
|
|
6
|
+
import TabNavigation from './TabNavigation';
|
|
7
|
+
import IntroTour from './IntroTour';
|
|
8
|
+
import HelpButton from './components/App/HelpButton';
|
|
9
|
+
import TrafficTab from './components/App/TrafficTab';
|
|
10
|
+
import { colors } from './theme';
|
|
11
|
+
import { tourSteps } from './config/tourSteps.jsx';
|
|
12
|
+
import { fadeIn } from './utils/animations';
|
|
13
|
+
import { useAppState } from './components/App/useAppState';
|
|
14
|
+
|
|
15
|
+
function App() {
|
|
16
|
+
const {
|
|
17
|
+
activeTab,
|
|
18
|
+
setActiveTab,
|
|
19
|
+
requests,
|
|
20
|
+
selected,
|
|
21
|
+
setSelected,
|
|
22
|
+
filters,
|
|
23
|
+
setFilters,
|
|
24
|
+
stats,
|
|
25
|
+
firstRequestTime,
|
|
26
|
+
showTour,
|
|
27
|
+
setShowTour,
|
|
28
|
+
prevTabRef,
|
|
29
|
+
loadRequests,
|
|
30
|
+
} = useAppState();
|
|
31
|
+
const [tourKey, setTourKey] = useState(0);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (prevTabRef.current !== activeTab) {
|
|
35
|
+
const tabContent = document.querySelector('[data-tab-content]');
|
|
36
|
+
if (tabContent) {
|
|
37
|
+
fadeIn(tabContent, { duration: 300 });
|
|
38
|
+
}
|
|
39
|
+
prevTabRef.current = activeTab;
|
|
40
|
+
}
|
|
41
|
+
}, [activeTab, prevTabRef]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
style={{
|
|
46
|
+
display: 'flex',
|
|
47
|
+
height: '100vh',
|
|
48
|
+
flexDirection: 'column',
|
|
49
|
+
background: colors.bgPrimary,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{showTour && (
|
|
53
|
+
<IntroTour
|
|
54
|
+
key={tourKey}
|
|
55
|
+
steps={tourSteps}
|
|
56
|
+
onComplete={() => setShowTour(false)}
|
|
57
|
+
onSkip={() => setShowTour(false)}
|
|
58
|
+
onStepChange={(stepIndex) => {
|
|
59
|
+
const step = tourSteps[stepIndex];
|
|
60
|
+
if (step) {
|
|
61
|
+
if (
|
|
62
|
+
step.target === '[data-tour="setup-tab"]' ||
|
|
63
|
+
step.target === '[data-tour="detected-editors"]' ||
|
|
64
|
+
step.target === '[data-tour="select-file"]' ||
|
|
65
|
+
step.target === '[data-tour="start-button"]'
|
|
66
|
+
) {
|
|
67
|
+
if (activeTab !== 'setup') {
|
|
68
|
+
setActiveTab('setup');
|
|
69
|
+
}
|
|
70
|
+
} else if (step.target === '[data-tour="traffic-tab"]') {
|
|
71
|
+
if (activeTab !== 'traffic') {
|
|
72
|
+
setActiveTab('traffic');
|
|
73
|
+
}
|
|
74
|
+
} else if (step.target === '[data-tour="smart-scan-tab"]') {
|
|
75
|
+
if (activeTab !== 'smart-scan') {
|
|
76
|
+
setActiveTab('smart-scan');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
<div style={{ position: 'relative' }} data-tour="tabs">
|
|
84
|
+
<TabNavigation activeTab={activeTab} onTabChange={setActiveTab} />
|
|
85
|
+
</div>
|
|
86
|
+
<HelpButton
|
|
87
|
+
onClick={() => {
|
|
88
|
+
if (showTour) {
|
|
89
|
+
setShowTour(false);
|
|
90
|
+
setTourKey((prev) => prev + 1);
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
setShowTour(true);
|
|
93
|
+
}, 100);
|
|
94
|
+
} else {
|
|
95
|
+
setTourKey((prev) => prev + 1);
|
|
96
|
+
setShowTour(true);
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
{activeTab === 'traffic' && (
|
|
102
|
+
<TrafficTab
|
|
103
|
+
requests={requests}
|
|
104
|
+
selected={selected}
|
|
105
|
+
onSelect={setSelected}
|
|
106
|
+
filters={filters}
|
|
107
|
+
onFilterChange={setFilters}
|
|
108
|
+
stats={stats}
|
|
109
|
+
firstRequestTime={firstRequestTime}
|
|
110
|
+
onClear={() => {
|
|
111
|
+
setSelected(null);
|
|
112
|
+
loadRequests();
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{activeTab === 'logs' && (
|
|
118
|
+
<div
|
|
119
|
+
data-tab-content
|
|
120
|
+
style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}
|
|
121
|
+
>
|
|
122
|
+
<CompositeLogs />
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{activeTab === 'setup' && (
|
|
127
|
+
<div
|
|
128
|
+
data-tab-content
|
|
129
|
+
style={{ flex: 1, overflow: 'hidden', width: '100%', height: '100%' }}
|
|
130
|
+
>
|
|
131
|
+
<CompositeSetup />
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{activeTab === 'playground' && (
|
|
136
|
+
<div
|
|
137
|
+
data-tab-content
|
|
138
|
+
style={{ flex: 1, overflow: 'hidden', width: '100%', height: '100%' }}
|
|
139
|
+
>
|
|
140
|
+
<McpPlayground />
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{activeTab === 'smart-scan' && (
|
|
145
|
+
<div data-tab-content style={{ flex: 1, overflow: 'auto', width: '100%', height: '100%' }}>
|
|
146
|
+
<SmartScan />
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default App;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { colors } from './theme';
|
|
3
|
+
import LogsToolbar from './components/LogsToolbar';
|
|
4
|
+
import LogsDisplay from './components/LogsDisplay';
|
|
5
|
+
|
|
6
|
+
function CompositeLogs() {
|
|
7
|
+
const [logs, setLogs] = useState([]);
|
|
8
|
+
const [autoScroll, setAutoScroll] = useState(true);
|
|
9
|
+
const [filter, setFilter] = useState('');
|
|
10
|
+
const [logType, setLogType] = useState('all'); // all, stdout, stderr, error, exit
|
|
11
|
+
const logEndRef = useRef(null);
|
|
12
|
+
const wsRef = useRef(null);
|
|
13
|
+
|
|
14
|
+
const scrollToTop = () => {
|
|
15
|
+
if (autoScroll && logEndRef.current) {
|
|
16
|
+
logEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
scrollToTop();
|
|
22
|
+
}, [logs, autoScroll]);
|
|
23
|
+
|
|
24
|
+
const loadLogs = async () => {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch('/api/composite/logs?limit=5000');
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
setLogs(data);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Failed to load logs:', error);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
loadLogs();
|
|
36
|
+
|
|
37
|
+
const wsUrl = import.meta.env.DEV
|
|
38
|
+
? 'ws://localhost:9853'
|
|
39
|
+
: `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}`;
|
|
40
|
+
const ws = new WebSocket(wsUrl);
|
|
41
|
+
wsRef.current = ws;
|
|
42
|
+
|
|
43
|
+
ws.onopen = () => {
|
|
44
|
+
console.log('WebSocket connected for logs');
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ws.onerror = (error) => {
|
|
48
|
+
console.error('WebSocket error:', error);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
ws.onmessage = (e) => {
|
|
52
|
+
try {
|
|
53
|
+
const msg = JSON.parse(e.data);
|
|
54
|
+
if (msg.type === 'log') {
|
|
55
|
+
setLogs((prev) => {
|
|
56
|
+
// Add new log at the beginning (latest first)
|
|
57
|
+
const newLogs = [msg.data, ...prev];
|
|
58
|
+
// Keep only last 5000 in memory
|
|
59
|
+
return newLogs.slice(0, 5000);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Failed to parse WebSocket message:', error);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
ws.onclose = () => {
|
|
68
|
+
console.log('WebSocket closed, attempting to reconnect...');
|
|
69
|
+
// Attempt to reconnect after a delay
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
if (wsRef.current?.readyState === WebSocket.CLOSED) {
|
|
72
|
+
// Reconnect logic would go here, but for now just log
|
|
73
|
+
console.log('WebSocket reconnection needed');
|
|
74
|
+
}
|
|
75
|
+
}, 3000);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
80
|
+
ws.close();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const clearLogs = async () => {
|
|
86
|
+
try {
|
|
87
|
+
await fetch('/api/composite/logs/clear', { method: 'POST' });
|
|
88
|
+
setLogs([]);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Failed to clear logs:', error);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getLogColor = (type) => {
|
|
95
|
+
switch (type) {
|
|
96
|
+
case 'stderr':
|
|
97
|
+
case 'error':
|
|
98
|
+
return colors.error;
|
|
99
|
+
case 'stdout':
|
|
100
|
+
return colors.textPrimary;
|
|
101
|
+
case 'exit':
|
|
102
|
+
return colors.accentOrange;
|
|
103
|
+
default:
|
|
104
|
+
return colors.textSecondary;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const filteredLogs = logs.filter((log) => {
|
|
109
|
+
if (logType !== 'all' && log.type !== logType) return false;
|
|
110
|
+
if (filter && !log.line.toLowerCase().includes(filter.toLowerCase())) return false;
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const handleExportLogs = async () => {
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetch('/api/composite/logs/export');
|
|
117
|
+
const blob = await response.blob();
|
|
118
|
+
const url = window.URL.createObjectURL(blob);
|
|
119
|
+
const a = document.createElement('a');
|
|
120
|
+
a.href = url;
|
|
121
|
+
a.download = `mcp-shark-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
|
|
122
|
+
document.body.appendChild(a);
|
|
123
|
+
a.click();
|
|
124
|
+
window.URL.revokeObjectURL(url);
|
|
125
|
+
document.body.removeChild(a);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Failed to export logs:', error);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
style={{
|
|
134
|
+
display: 'flex',
|
|
135
|
+
flexDirection: 'column',
|
|
136
|
+
height: '100%',
|
|
137
|
+
width: '100%',
|
|
138
|
+
background: colors.bgPrimary,
|
|
139
|
+
overflow: 'hidden',
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
<LogsToolbar
|
|
143
|
+
filter={filter}
|
|
144
|
+
setFilter={setFilter}
|
|
145
|
+
logType={logType}
|
|
146
|
+
setLogType={setLogType}
|
|
147
|
+
autoScroll={autoScroll}
|
|
148
|
+
setAutoScroll={setAutoScroll}
|
|
149
|
+
onClearLogs={clearLogs}
|
|
150
|
+
onExportLogs={handleExportLogs}
|
|
151
|
+
filteredCount={filteredLogs.length}
|
|
152
|
+
totalCount={logs.length}
|
|
153
|
+
/>
|
|
154
|
+
<LogsDisplay
|
|
155
|
+
logs={logs}
|
|
156
|
+
filteredLogs={filteredLogs}
|
|
157
|
+
logEndRef={logEndRef}
|
|
158
|
+
getLogColor={getLogColor}
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default CompositeLogs;
|