@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,4 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
function appendFilterParams(queryParams, filters) {
|
|
4
|
+
if (filters.search) {
|
|
5
|
+
queryParams.append('search', filters.search);
|
|
6
|
+
}
|
|
7
|
+
if (filters.serverName) {
|
|
8
|
+
queryParams.append('serverName', filters.serverName);
|
|
9
|
+
}
|
|
10
|
+
if (filters.sessionId) {
|
|
11
|
+
queryParams.append('sessionId', filters.sessionId);
|
|
12
|
+
}
|
|
13
|
+
if (filters.method) {
|
|
14
|
+
queryParams.append('method', filters.method);
|
|
15
|
+
}
|
|
16
|
+
if (filters.jsonrpcMethod) {
|
|
17
|
+
queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
|
|
18
|
+
}
|
|
19
|
+
if (filters.statusCode) {
|
|
20
|
+
queryParams.append('statusCode', filters.statusCode);
|
|
21
|
+
}
|
|
22
|
+
if (filters.jsonrpcId) {
|
|
23
|
+
queryParams.append('jsonrpcId', filters.jsonrpcId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
2
26
|
|
|
3
27
|
export function useAppState() {
|
|
4
28
|
const [activeTab, setActiveTab] = useState('traffic');
|
|
@@ -11,17 +35,12 @@ export function useAppState() {
|
|
|
11
35
|
const [tourDismissed, setTourDismissed] = useState(true);
|
|
12
36
|
const wsRef = useRef(null);
|
|
13
37
|
const prevTabRef = useRef(activeTab);
|
|
38
|
+
const filtersRef = useRef(filters);
|
|
14
39
|
|
|
15
40
|
const loadStatistics = async () => {
|
|
16
41
|
try {
|
|
17
42
|
const queryParams = new URLSearchParams();
|
|
18
|
-
|
|
19
|
-
if (filters.serverName) queryParams.append('serverName', filters.serverName);
|
|
20
|
-
if (filters.sessionId) queryParams.append('sessionId', filters.sessionId);
|
|
21
|
-
if (filters.method) queryParams.append('method', filters.method);
|
|
22
|
-
if (filters.jsonrpcMethod) queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
|
|
23
|
-
if (filters.statusCode) queryParams.append('statusCode', filters.statusCode);
|
|
24
|
-
if (filters.jsonrpcId) queryParams.append('jsonrpcId', filters.jsonrpcId);
|
|
43
|
+
appendFilterParams(queryParams, filters);
|
|
25
44
|
|
|
26
45
|
const statsResponse = await fetch(`/api/statistics?${queryParams}`);
|
|
27
46
|
const statsData = await statsResponse.json();
|
|
@@ -34,13 +53,7 @@ export function useAppState() {
|
|
|
34
53
|
const loadRequests = async () => {
|
|
35
54
|
try {
|
|
36
55
|
const queryParams = new URLSearchParams();
|
|
37
|
-
|
|
38
|
-
if (filters.serverName) queryParams.append('serverName', filters.serverName);
|
|
39
|
-
if (filters.sessionId) queryParams.append('sessionId', filters.sessionId);
|
|
40
|
-
if (filters.method) queryParams.append('method', filters.method);
|
|
41
|
-
if (filters.jsonrpcMethod) queryParams.append('jsonrpcMethod', filters.jsonrpcMethod);
|
|
42
|
-
if (filters.statusCode) queryParams.append('statusCode', filters.statusCode);
|
|
43
|
-
if (filters.jsonrpcId) queryParams.append('jsonrpcId', filters.jsonrpcId);
|
|
56
|
+
appendFilterParams(queryParams, filters);
|
|
44
57
|
queryParams.append('limit', '5000');
|
|
45
58
|
|
|
46
59
|
const response = await fetch(`/api/requests?${queryParams}`);
|
|
@@ -81,8 +94,37 @@ export function useAppState() {
|
|
|
81
94
|
}
|
|
82
95
|
};
|
|
83
96
|
|
|
97
|
+
const initData = async () => {
|
|
98
|
+
try {
|
|
99
|
+
const queryParams = new URLSearchParams();
|
|
100
|
+
appendFilterParams(queryParams, filters);
|
|
101
|
+
queryParams.append('limit', '5000');
|
|
102
|
+
|
|
103
|
+
const response = await fetch(`/api/requests?${queryParams}`);
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
setRequests(data);
|
|
106
|
+
|
|
107
|
+
if (data.length > 0) {
|
|
108
|
+
const oldest = data[data.length - 1]?.timestamp_iso;
|
|
109
|
+
if (oldest) {
|
|
110
|
+
setFirstRequestTime(oldest);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Also load statistics
|
|
115
|
+
const statsQueryParams = new URLSearchParams();
|
|
116
|
+
appendFilterParams(statsQueryParams, filters);
|
|
117
|
+
|
|
118
|
+
const statsResponse = await fetch(`/api/statistics?${statsQueryParams}`);
|
|
119
|
+
const statsData = await statsResponse.json();
|
|
120
|
+
setStats(statsData);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Failed to load requests:', error);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
84
126
|
checkTourState();
|
|
85
|
-
|
|
127
|
+
initData();
|
|
86
128
|
|
|
87
129
|
const wsUrl = import.meta.env.DEV
|
|
88
130
|
? 'ws://localhost:9853'
|
|
@@ -92,7 +134,7 @@ export function useAppState() {
|
|
|
92
134
|
const ws = new WebSocket(wsUrl);
|
|
93
135
|
wsRef.current = ws;
|
|
94
136
|
|
|
95
|
-
ws.onmessage = (e) => {
|
|
137
|
+
ws.onmessage = async (e) => {
|
|
96
138
|
const msg = JSON.parse(e.data);
|
|
97
139
|
if (msg.type === 'update') {
|
|
98
140
|
setRequests(msg.data);
|
|
@@ -103,7 +145,16 @@ export function useAppState() {
|
|
|
103
145
|
}
|
|
104
146
|
}
|
|
105
147
|
// Update statistics when new data arrives via WebSocket
|
|
106
|
-
|
|
148
|
+
try {
|
|
149
|
+
const queryParams = new URLSearchParams();
|
|
150
|
+
appendFilterParams(queryParams, filters);
|
|
151
|
+
|
|
152
|
+
const statsResponse = await fetch(`/api/statistics?${queryParams}`);
|
|
153
|
+
const statsData = await statsResponse.json();
|
|
154
|
+
setStats(statsData);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error('Failed to load statistics:', error);
|
|
157
|
+
}
|
|
107
158
|
}
|
|
108
159
|
};
|
|
109
160
|
|
|
@@ -114,7 +165,7 @@ export function useAppState() {
|
|
|
114
165
|
ws.onclose = () => {
|
|
115
166
|
// Connection closed - will attempt to reconnect on next mount if needed
|
|
116
167
|
};
|
|
117
|
-
} catch (
|
|
168
|
+
} catch (_err) {
|
|
118
169
|
// Silently handle WebSocket creation errors
|
|
119
170
|
}
|
|
120
171
|
|
|
@@ -123,10 +174,44 @@ export function useAppState() {
|
|
|
123
174
|
wsRef.current.close();
|
|
124
175
|
}
|
|
125
176
|
};
|
|
126
|
-
}, []);
|
|
177
|
+
}, [filters]);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const fetchData = async () => {
|
|
181
|
+
try {
|
|
182
|
+
const queryParams = new URLSearchParams();
|
|
183
|
+
appendFilterParams(queryParams, filters);
|
|
184
|
+
queryParams.append('limit', '5000');
|
|
185
|
+
|
|
186
|
+
const response = await fetch(`/api/requests?${queryParams}`);
|
|
187
|
+
const data = await response.json();
|
|
188
|
+
setRequests(data);
|
|
189
|
+
|
|
190
|
+
if (data.length > 0) {
|
|
191
|
+
const oldest = data[data.length - 1]?.timestamp_iso;
|
|
192
|
+
if (oldest) {
|
|
193
|
+
setFirstRequestTime(oldest);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Also load statistics
|
|
198
|
+
const statsQueryParams = new URLSearchParams();
|
|
199
|
+
appendFilterParams(statsQueryParams, filters);
|
|
200
|
+
|
|
201
|
+
const statsResponse = await fetch(`/api/statistics?${statsQueryParams}`);
|
|
202
|
+
const statsData = await statsResponse.json();
|
|
203
|
+
setStats(statsData);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error('Failed to load requests:', error);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
fetchData();
|
|
210
|
+
}, [filters]);
|
|
127
211
|
|
|
212
|
+
// Keep filters ref up to date
|
|
128
213
|
useEffect(() => {
|
|
129
|
-
|
|
214
|
+
filtersRef.current = filters;
|
|
130
215
|
}, [filters]);
|
|
131
216
|
|
|
132
217
|
// Periodically update statistics when on traffic tab
|
|
@@ -136,12 +221,21 @@ export function useAppState() {
|
|
|
136
221
|
}
|
|
137
222
|
|
|
138
223
|
// Update statistics every 2 seconds
|
|
139
|
-
const interval = setInterval(() => {
|
|
140
|
-
|
|
224
|
+
const interval = setInterval(async () => {
|
|
225
|
+
try {
|
|
226
|
+
const queryParams = new URLSearchParams();
|
|
227
|
+
appendFilterParams(queryParams, filtersRef.current);
|
|
228
|
+
|
|
229
|
+
const statsResponse = await fetch(`/api/statistics?${queryParams}`);
|
|
230
|
+
const statsData = await statsResponse.json();
|
|
231
|
+
setStats(statsData);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Failed to load statistics:', error);
|
|
234
|
+
}
|
|
141
235
|
}, 2000);
|
|
142
236
|
|
|
143
237
|
return () => clearInterval(interval);
|
|
144
|
-
}, [activeTab
|
|
238
|
+
}, [activeTab]);
|
|
145
239
|
|
|
146
240
|
return {
|
|
147
241
|
activeTab,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { IconEye, IconRefresh, IconRestore, IconTrash } from '@tabler/icons-react';
|
|
1
2
|
import { colors, fonts, withOpacity } from '../theme';
|
|
2
|
-
import { IconRefresh, IconEye, IconTrash, IconRestore } from '@tabler/icons-react';
|
|
3
3
|
|
|
4
4
|
function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onDelete }) {
|
|
5
5
|
if (backups.length === 0) {
|
|
@@ -36,6 +36,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
|
|
|
36
36
|
Backup Files
|
|
37
37
|
</h3>
|
|
38
38
|
<button
|
|
39
|
+
type="button"
|
|
39
40
|
onClick={onRefresh}
|
|
40
41
|
disabled={loadingBackups}
|
|
41
42
|
style={{
|
|
@@ -68,7 +69,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
|
|
|
68
69
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
69
70
|
{backups.map((backup, idx) => (
|
|
70
71
|
<div
|
|
71
|
-
key={idx}
|
|
72
|
+
key={backup.backupPath || `backup-${idx}`}
|
|
72
73
|
style={{
|
|
73
74
|
padding: '12px',
|
|
74
75
|
background: colors.bgPrimary,
|
|
@@ -97,6 +98,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
|
|
|
97
98
|
</div>
|
|
98
99
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
99
100
|
<button
|
|
101
|
+
type="button"
|
|
100
102
|
onClick={() => onView(backup.backupPath)}
|
|
101
103
|
style={{
|
|
102
104
|
padding: '6px 12px',
|
|
@@ -122,6 +124,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
|
|
|
122
124
|
View
|
|
123
125
|
</button>
|
|
124
126
|
<button
|
|
127
|
+
type="button"
|
|
125
128
|
onClick={() => {
|
|
126
129
|
if (confirm('Are you sure you want to delete this backup?')) {
|
|
127
130
|
onDelete(backup.backupPath);
|
|
@@ -155,6 +158,7 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
|
|
|
155
158
|
Delete
|
|
156
159
|
</button>
|
|
157
160
|
<button
|
|
161
|
+
type="button"
|
|
158
162
|
onClick={() => onRestore(backup.backupPath, backup.originalPath)}
|
|
159
163
|
style={{
|
|
160
164
|
padding: '6px 12px',
|
|
@@ -12,7 +12,10 @@ const ChevronDown = ({ size = 12, color = 'currentColor' }) => (
|
|
|
12
12
|
strokeLinecap="round"
|
|
13
13
|
strokeLinejoin="round"
|
|
14
14
|
style={{ display: 'inline-block', verticalAlign: 'middle', marginRight: '4px' }}
|
|
15
|
+
role="img"
|
|
16
|
+
aria-label="Chevron icon"
|
|
15
17
|
>
|
|
18
|
+
<title>Chevron icon</title>
|
|
16
19
|
<polyline points="6 9 12 15 18 9" />
|
|
17
20
|
</svg>
|
|
18
21
|
);
|
|
@@ -27,7 +30,8 @@ function CollapsibleSection({
|
|
|
27
30
|
|
|
28
31
|
return (
|
|
29
32
|
<div style={{ marginBottom: '16px' }}>
|
|
30
|
-
<
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
31
35
|
style={{
|
|
32
36
|
color: titleColor,
|
|
33
37
|
fontWeight: '600',
|
|
@@ -41,8 +45,18 @@ function CollapsibleSection({
|
|
|
41
45
|
gap: '6px',
|
|
42
46
|
padding: '4px 0',
|
|
43
47
|
transition: 'color 0.15s ease',
|
|
48
|
+
background: 'transparent',
|
|
49
|
+
border: 'none',
|
|
50
|
+
textAlign: 'left',
|
|
51
|
+
width: '100%',
|
|
44
52
|
}}
|
|
45
53
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
54
|
+
onKeyDown={(e) => {
|
|
55
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
setIsExpanded(!isExpanded);
|
|
58
|
+
}
|
|
59
|
+
}}
|
|
46
60
|
onMouseEnter={(e) => {
|
|
47
61
|
e.currentTarget.style.color =
|
|
48
62
|
titleColor === colors.accentBlue ? colors.accentBlueHover : titleColor;
|
|
@@ -61,7 +75,7 @@ function CollapsibleSection({
|
|
|
61
75
|
<ChevronDown size={12} color={titleColor} />
|
|
62
76
|
</span>
|
|
63
77
|
{title}
|
|
64
|
-
</
|
|
78
|
+
</button>
|
|
65
79
|
{isExpanded && (
|
|
66
80
|
<div
|
|
67
81
|
style={{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { colors
|
|
1
|
+
import { colors } from '../theme';
|
|
2
2
|
|
|
3
3
|
function ConfigViewerModal({
|
|
4
4
|
viewingConfig,
|
|
@@ -21,7 +21,9 @@ function ConfigViewerModal({
|
|
|
21
21
|
const title = isViewingBackup ? 'Backup File' : 'MCP Configuration File';
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<
|
|
24
|
+
<dialog
|
|
25
|
+
open
|
|
26
|
+
aria-modal="true"
|
|
25
27
|
style={{
|
|
26
28
|
position: 'fixed',
|
|
27
29
|
top: 0,
|
|
@@ -34,10 +36,20 @@ function ConfigViewerModal({
|
|
|
34
36
|
justifyContent: 'center',
|
|
35
37
|
zIndex: 1000,
|
|
36
38
|
padding: '20px',
|
|
39
|
+
border: 'none',
|
|
40
|
+
margin: 0,
|
|
41
|
+
width: '100%',
|
|
42
|
+
height: '100%',
|
|
37
43
|
}}
|
|
38
44
|
onClick={onClose}
|
|
45
|
+
onKeyDown={(e) => {
|
|
46
|
+
if (e.key === 'Escape') {
|
|
47
|
+
onClose();
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
39
50
|
>
|
|
40
51
|
<div
|
|
52
|
+
role="document"
|
|
41
53
|
style={{
|
|
42
54
|
background: colors.bgPrimary,
|
|
43
55
|
border: `1px solid ${colors.borderLight}`,
|
|
@@ -50,6 +62,7 @@ function ConfigViewerModal({
|
|
|
50
62
|
overflow: 'hidden',
|
|
51
63
|
}}
|
|
52
64
|
onClick={(e) => e.stopPropagation()}
|
|
65
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
53
66
|
>
|
|
54
67
|
<div
|
|
55
68
|
style={{
|
|
@@ -83,6 +96,7 @@ function ConfigViewerModal({
|
|
|
83
96
|
)}
|
|
84
97
|
</div>
|
|
85
98
|
<button
|
|
99
|
+
type="button"
|
|
86
100
|
onClick={onClose}
|
|
87
101
|
style={{
|
|
88
102
|
background: 'transparent',
|
|
@@ -134,7 +148,7 @@ function ConfigViewerModal({
|
|
|
134
148
|
)}
|
|
135
149
|
</div>
|
|
136
150
|
</div>
|
|
137
|
-
</
|
|
151
|
+
</dialog>
|
|
138
152
|
);
|
|
139
153
|
}
|
|
140
154
|
|
|
@@ -10,10 +10,14 @@ function ConfirmationModal({
|
|
|
10
10
|
cancelText = 'Cancel',
|
|
11
11
|
danger = false,
|
|
12
12
|
}) {
|
|
13
|
-
if (!isOpen)
|
|
13
|
+
if (!isOpen) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
|
-
<
|
|
18
|
+
<dialog
|
|
19
|
+
open
|
|
20
|
+
aria-modal="true"
|
|
17
21
|
style={{
|
|
18
22
|
position: 'fixed',
|
|
19
23
|
top: 0,
|
|
@@ -25,10 +29,20 @@ function ConfirmationModal({
|
|
|
25
29
|
alignItems: 'center',
|
|
26
30
|
justifyContent: 'center',
|
|
27
31
|
zIndex: 1000,
|
|
32
|
+
border: 'none',
|
|
33
|
+
margin: 0,
|
|
34
|
+
width: '100%',
|
|
35
|
+
height: '100%',
|
|
28
36
|
}}
|
|
29
37
|
onClick={onClose}
|
|
38
|
+
onKeyDown={(e) => {
|
|
39
|
+
if (e.key === 'Escape') {
|
|
40
|
+
onClose();
|
|
41
|
+
}
|
|
42
|
+
}}
|
|
30
43
|
>
|
|
31
44
|
<div
|
|
45
|
+
role="document"
|
|
32
46
|
style={{
|
|
33
47
|
background: colors.bgCard,
|
|
34
48
|
borderRadius: '12px',
|
|
@@ -39,6 +53,7 @@ function ConfirmationModal({
|
|
|
39
53
|
fontFamily: fonts.body,
|
|
40
54
|
}}
|
|
41
55
|
onClick={(e) => e.stopPropagation()}
|
|
56
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
42
57
|
>
|
|
43
58
|
<h3
|
|
44
59
|
style={{
|
|
@@ -68,6 +83,7 @@ function ConfirmationModal({
|
|
|
68
83
|
}}
|
|
69
84
|
>
|
|
70
85
|
<button
|
|
86
|
+
type="button"
|
|
71
87
|
onClick={onClose}
|
|
72
88
|
style={{
|
|
73
89
|
padding: '10px 20px',
|
|
@@ -91,6 +107,7 @@ function ConfirmationModal({
|
|
|
91
107
|
{cancelText}
|
|
92
108
|
</button>
|
|
93
109
|
<button
|
|
110
|
+
type="button"
|
|
94
111
|
onClick={() => {
|
|
95
112
|
onConfirm();
|
|
96
113
|
onClose();
|
|
@@ -122,7 +139,7 @@ function ConfirmationModal({
|
|
|
122
139
|
</button>
|
|
123
140
|
</div>
|
|
124
141
|
</div>
|
|
125
|
-
</
|
|
142
|
+
</dialog>
|
|
126
143
|
);
|
|
127
144
|
}
|
|
128
145
|
|
|
@@ -2,7 +2,9 @@ import { colors, fonts } from '../../theme';
|
|
|
2
2
|
import CollapsibleSection from '../CollapsibleSection';
|
|
3
3
|
|
|
4
4
|
export default function BodySection({ body, title, titleColor }) {
|
|
5
|
-
if (!body)
|
|
5
|
+
if (!body) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
return (
|
|
8
10
|
<CollapsibleSection title={title || 'Body'} titleColor={titleColor}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { IconChevronDown } from '@tabler/icons-react';
|
|
1
2
|
import { useState } from 'react';
|
|
2
3
|
import { colors, fonts } from '../../theme';
|
|
3
|
-
import { IconChevronDown } from '@tabler/icons-react';
|
|
4
4
|
|
|
5
5
|
export default function CollapsibleRequestResponse({
|
|
6
6
|
title,
|
|
@@ -20,8 +20,16 @@ export default function CollapsibleRequestResponse({
|
|
|
20
20
|
marginBottom: '20px',
|
|
21
21
|
}}
|
|
22
22
|
>
|
|
23
|
-
<
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
24
25
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
26
|
+
onKeyDown={(e) => {
|
|
27
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
setIsExpanded(!isExpanded);
|
|
30
|
+
}
|
|
31
|
+
}}
|
|
32
|
+
aria-label={`Toggle ${title} section`}
|
|
25
33
|
style={{
|
|
26
34
|
padding: '16px 20px',
|
|
27
35
|
background: isExpanded ? colors.bgCard : colors.bgSecondary,
|
|
@@ -32,6 +40,9 @@ export default function CollapsibleRequestResponse({
|
|
|
32
40
|
alignItems: 'center',
|
|
33
41
|
justifyContent: 'space-between',
|
|
34
42
|
transition: 'background-color 0.15s ease',
|
|
43
|
+
width: '100%',
|
|
44
|
+
border: 'none',
|
|
45
|
+
textAlign: 'left',
|
|
35
46
|
}}
|
|
36
47
|
onMouseEnter={(e) => {
|
|
37
48
|
e.currentTarget.style.background = colors.bgHover;
|
|
@@ -63,7 +74,7 @@ export default function CollapsibleRequestResponse({
|
|
|
63
74
|
/>
|
|
64
75
|
{title}
|
|
65
76
|
</div>
|
|
66
|
-
</
|
|
77
|
+
</button>
|
|
67
78
|
{isExpanded && <div style={{ padding: '20px' }}>{children}</div>}
|
|
68
79
|
</div>
|
|
69
80
|
);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
-
import CollapsibleSection from '../CollapsibleSection';
|
|
3
2
|
import { getInfo } from '../../utils/requestUtils';
|
|
3
|
+
import CollapsibleSection from '../CollapsibleSection';
|
|
4
4
|
|
|
5
5
|
export default function InfoSection({ data, titleColor }) {
|
|
6
|
-
if (!data)
|
|
6
|
+
if (!data) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
7
9
|
|
|
8
10
|
const info = getInfo(data);
|
|
9
11
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { colors } from '../../theme';
|
|
2
|
+
import CollapsibleSection from '../CollapsibleSection';
|
|
3
|
+
import BodySection from './BodySection';
|
|
2
4
|
import CollapsibleRequestResponse from './CollapsibleRequestResponse';
|
|
3
|
-
import NetworkInfoSection from './NetworkInfoSection';
|
|
4
|
-
import ProtocolInfoSection from './ProtocolInfoSection';
|
|
5
5
|
import HeadersSection from './HeadersSection';
|
|
6
|
-
import BodySection from './BodySection';
|
|
7
6
|
import InfoSection from './InfoSection';
|
|
8
|
-
import
|
|
7
|
+
import NetworkInfoSection from './NetworkInfoSection';
|
|
8
|
+
import ProtocolInfoSection from './ProtocolInfoSection';
|
|
9
9
|
|
|
10
10
|
export default function RequestDetailsSection({ request, requestHeaders, requestBody }) {
|
|
11
|
-
if (!request)
|
|
11
|
+
if (!request) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
12
14
|
|
|
13
15
|
return (
|
|
14
16
|
<CollapsibleRequestResponse
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
+
import CollapsibleSection from '../CollapsibleSection';
|
|
3
|
+
import BodySection from './BodySection';
|
|
2
4
|
import CollapsibleRequestResponse from './CollapsibleRequestResponse';
|
|
3
|
-
import NetworkInfoSection from './NetworkInfoSection';
|
|
4
|
-
import ProtocolInfoSection from './ProtocolInfoSection';
|
|
5
5
|
import HeadersSection from './HeadersSection';
|
|
6
|
-
import BodySection from './BodySection';
|
|
7
6
|
import InfoSection from './InfoSection';
|
|
8
|
-
import
|
|
7
|
+
import NetworkInfoSection from './NetworkInfoSection';
|
|
8
|
+
import ProtocolInfoSection from './ProtocolInfoSection';
|
|
9
9
|
|
|
10
10
|
export default function ResponseDetailsSection({ response, responseHeaders, responseBody }) {
|
|
11
|
-
if (!response)
|
|
11
|
+
if (!response) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
12
14
|
|
|
13
15
|
return (
|
|
14
16
|
<CollapsibleRequestResponse
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { IconClock, IconCode, IconEye, IconRefresh } from '@tabler/icons-react';
|
|
1
2
|
import { colors, fonts } from '../theme';
|
|
2
|
-
import { IconRefresh, IconEye, IconCode, IconClock } from '@tabler/icons-react';
|
|
3
3
|
|
|
4
4
|
function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onView }) {
|
|
5
5
|
if (detectedPaths.length === 0) {
|
|
@@ -27,6 +27,7 @@ function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onVie
|
|
|
27
27
|
Detected Configuration Files:
|
|
28
28
|
</div>
|
|
29
29
|
<button
|
|
30
|
+
type="button"
|
|
30
31
|
onClick={onDetect}
|
|
31
32
|
disabled={detecting}
|
|
32
33
|
title="Refresh detection"
|
|
@@ -67,7 +68,8 @@ function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onVie
|
|
|
67
68
|
>
|
|
68
69
|
{detectedPaths.map((item, idx) => (
|
|
69
70
|
<button
|
|
70
|
-
key={idx}
|
|
71
|
+
key={`${item.editor}-${item.path}-${idx}`}
|
|
72
|
+
type="button"
|
|
71
73
|
data-tour={idx === 0 ? 'first-detected-editor' : undefined}
|
|
72
74
|
onClick={() => onSelect(item.path)}
|
|
73
75
|
onDoubleClick={() => {
|
|
@@ -137,6 +139,7 @@ function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onVie
|
|
|
137
139
|
Found
|
|
138
140
|
</span>
|
|
139
141
|
<button
|
|
142
|
+
type="button"
|
|
140
143
|
onClick={(e) => {
|
|
141
144
|
e.stopPropagation();
|
|
142
145
|
onView(item.path);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { colors, fonts } from '../theme';
|
|
2
1
|
import { IconFileUpload } from '@tabler/icons-react';
|
|
2
|
+
import { colors, fonts } from '../theme';
|
|
3
3
|
|
|
4
4
|
function FileInput({
|
|
5
5
|
filePath,
|
|
@@ -70,6 +70,7 @@ function FileInput({
|
|
|
70
70
|
{fileContent && !filePath && (
|
|
71
71
|
<div>
|
|
72
72
|
<label
|
|
73
|
+
htmlFor="update-path-input"
|
|
73
74
|
style={{
|
|
74
75
|
display: 'block',
|
|
75
76
|
fontSize: '12px',
|
|
@@ -80,6 +81,7 @@ function FileInput({
|
|
|
80
81
|
Optional: File Path to Update
|
|
81
82
|
</label>
|
|
82
83
|
<input
|
|
84
|
+
id="update-path-input"
|
|
83
85
|
type="text"
|
|
84
86
|
placeholder="Enter file path to update (e.g., ~/.cursor/mcp.json)"
|
|
85
87
|
value={updatePath}
|
|
@@ -11,7 +11,10 @@ const ChevronDown = ({ size = 12, color = 'currentColor' }) => (
|
|
|
11
11
|
strokeLinecap="round"
|
|
12
12
|
strokeLinejoin="round"
|
|
13
13
|
style={{ display: 'inline-block', verticalAlign: 'middle' }}
|
|
14
|
+
role="img"
|
|
15
|
+
aria-label="Chevron down icon"
|
|
14
16
|
>
|
|
17
|
+
<title>Chevron down icon</title>
|
|
15
18
|
<polyline points="6 9 12 15 18 9" />
|
|
16
19
|
</svg>
|
|
17
20
|
);
|
|
@@ -27,7 +30,10 @@ const ChevronRight = ({ size = 12, color = 'currentColor' }) => (
|
|
|
27
30
|
strokeLinecap="round"
|
|
28
31
|
strokeLinejoin="round"
|
|
29
32
|
style={{ display: 'inline-block', verticalAlign: 'middle' }}
|
|
33
|
+
role="img"
|
|
34
|
+
aria-label="Chevron right icon"
|
|
30
35
|
>
|
|
36
|
+
<title>Chevron right icon</title>
|
|
31
37
|
<polyline points="9 18 15 12 9 6" />
|
|
32
38
|
</svg>
|
|
33
39
|
);
|
|
@@ -36,6 +42,14 @@ export default function GroupHeader({ children, onClick, isExpanded, indent = 0
|
|
|
36
42
|
return (
|
|
37
43
|
<tr
|
|
38
44
|
onClick={onClick}
|
|
45
|
+
onKeyDown={(e) => {
|
|
46
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
onClick();
|
|
49
|
+
}
|
|
50
|
+
}}
|
|
51
|
+
tabIndex={0}
|
|
52
|
+
aria-label={`Toggle group ${children}`}
|
|
39
53
|
style={{
|
|
40
54
|
cursor: 'pointer',
|
|
41
55
|
background: colors.bgSecondary,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { colors
|
|
3
|
-
import
|
|
2
|
+
import { colors } from '../theme';
|
|
3
|
+
import { getCategoryIconComponent } from '../utils/mcpGroupingUtils';
|
|
4
4
|
import GroupHeader from './GroupHeader';
|
|
5
|
-
import
|
|
6
|
-
import { getJsonRpcMethod } from '../utils/requestUtils';
|
|
5
|
+
import RequestRow from './RequestRow';
|
|
7
6
|
|
|
8
7
|
function GroupedByMcpView({
|
|
9
8
|
groupedData,
|
|
@@ -15,12 +14,6 @@ function GroupedByMcpView({
|
|
|
15
14
|
onToggleSession,
|
|
16
15
|
onToggleCategory,
|
|
17
16
|
}) {
|
|
18
|
-
const getJsonRpcMethodFromPair = (pair) => {
|
|
19
|
-
const request = pair.request || pair.response;
|
|
20
|
-
if (!request) return null;
|
|
21
|
-
return getJsonRpcMethod(request);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
17
|
return (
|
|
25
18
|
<tbody>
|
|
26
19
|
{groupedData.map((sessionGroup) => {
|