@mcp-shark/mcp-shark 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -645
- package/bin/mcp-shark.js +30 -36
- package/mcp-server/index.js +115 -0
- package/mcp-server/lib/auditor/audit.js +34 -42
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +21 -40
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +5 -7
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-read.js +5 -4
- package/mcp-server/lib/server/internal/handlers/tools-call.js +8 -10
- package/mcp-server/lib/server/internal/handlers/tools-list.js +6 -5
- package/mcp-server/lib/server/internal/run.js +5 -17
- package/mcp-server/lib/server/internal/server.js +14 -15
- package/mcp-server/lib/server/internal/session.js +8 -13
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +91 -62
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +140 -112
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +87 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -10
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +52 -23
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +13 -11
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +66 -34
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +13 -11
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +52 -23
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +13 -11
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +70 -0
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +90 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +118 -159
- package/ui/src/components/McpPlayground.jsx +105 -23
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +37 -105
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -244
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { colors, fonts } from '../theme';
|
|
2
1
|
import { IconList, IconNetwork } from '@tabler/icons-react';
|
|
2
|
+
import { colors, fonts } from '../theme';
|
|
3
3
|
|
|
4
4
|
function ViewModeTabs({ viewMode, onViewModeChange }) {
|
|
5
5
|
return (
|
|
@@ -15,6 +15,7 @@ function ViewModeTabs({ viewMode, onViewModeChange }) {
|
|
|
15
15
|
}}
|
|
16
16
|
>
|
|
17
17
|
<button
|
|
18
|
+
type="button"
|
|
18
19
|
onClick={() => onViewModeChange('general')}
|
|
19
20
|
style={{
|
|
20
21
|
padding: '10px 18px',
|
|
@@ -49,6 +50,7 @@ function ViewModeTabs({ viewMode, onViewModeChange }) {
|
|
|
49
50
|
General List
|
|
50
51
|
</button>
|
|
51
52
|
<button
|
|
53
|
+
type="button"
|
|
52
54
|
onClick={() => onViewModeChange('groupedByMcp')}
|
|
53
55
|
style={{
|
|
54
56
|
padding: '10px 18px',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
1
|
import anime from 'animejs';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* React hook for animating elements on mount/unmount
|
|
@@ -18,7 +18,7 @@ export const useAnimation = (animationFn, deps = []) => {
|
|
|
18
18
|
anime.remove(elementRef.current);
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
|
-
}, deps);
|
|
21
|
+
}, [animationFn, ...deps]);
|
|
22
22
|
|
|
23
23
|
return elementRef;
|
|
24
24
|
};
|
|
@@ -28,6 +28,7 @@ export const useAnimation = (animationFn, deps = []) => {
|
|
|
28
28
|
*/
|
|
29
29
|
export const useMountAnimation = (options = {}) => {
|
|
30
30
|
const elementRef = useRef(null);
|
|
31
|
+
const { duration, easing } = options;
|
|
31
32
|
|
|
32
33
|
useEffect(() => {
|
|
33
34
|
if (elementRef.current) {
|
|
@@ -35,12 +36,12 @@ export const useMountAnimation = (options = {}) => {
|
|
|
35
36
|
targets: elementRef.current,
|
|
36
37
|
opacity: [0, 1],
|
|
37
38
|
translateY: [20, 0],
|
|
38
|
-
duration:
|
|
39
|
-
easing:
|
|
39
|
+
duration: duration || 400,
|
|
40
|
+
easing: easing || 'easeOutExpo',
|
|
40
41
|
...options,
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
}, []);
|
|
44
|
+
}, [duration, easing, options]);
|
|
44
45
|
|
|
45
46
|
return elementRef;
|
|
46
47
|
};
|
|
@@ -50,6 +51,7 @@ export const useMountAnimation = (options = {}) => {
|
|
|
50
51
|
*/
|
|
51
52
|
export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) => {
|
|
52
53
|
const elementRef = useRef(null);
|
|
54
|
+
const { duration, easing } = options;
|
|
53
55
|
|
|
54
56
|
useEffect(() => {
|
|
55
57
|
if (shouldUnmount && elementRef.current) {
|
|
@@ -57,13 +59,13 @@ export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) =>
|
|
|
57
59
|
targets: elementRef.current,
|
|
58
60
|
opacity: [1, 0],
|
|
59
61
|
translateY: [0, -20],
|
|
60
|
-
duration:
|
|
61
|
-
easing:
|
|
62
|
+
duration: duration || 300,
|
|
63
|
+
easing: easing || 'easeInExpo',
|
|
62
64
|
complete: onComplete,
|
|
63
65
|
...options,
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
|
-
}, [shouldUnmount, onComplete]);
|
|
68
|
+
}, [shouldUnmount, onComplete, duration, easing, options]);
|
|
67
69
|
|
|
68
70
|
return elementRef;
|
|
69
71
|
};
|
|
@@ -73,6 +75,7 @@ export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) =>
|
|
|
73
75
|
*/
|
|
74
76
|
export const useStaggerAnimation = (items, options = {}) => {
|
|
75
77
|
const containerRef = useRef(null);
|
|
78
|
+
const { duration, delay, easing } = options;
|
|
76
79
|
|
|
77
80
|
useEffect(() => {
|
|
78
81
|
if (containerRef.current && items.length > 0) {
|
|
@@ -80,13 +83,13 @@ export const useStaggerAnimation = (items, options = {}) => {
|
|
|
80
83
|
targets: containerRef.current.children,
|
|
81
84
|
opacity: [0, 1],
|
|
82
85
|
translateY: [20, 0],
|
|
83
|
-
duration:
|
|
84
|
-
delay: anime.stagger(
|
|
85
|
-
easing:
|
|
86
|
+
duration: duration || 400,
|
|
87
|
+
delay: anime.stagger(delay || 50),
|
|
88
|
+
easing: easing || 'easeOutExpo',
|
|
86
89
|
...options,
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
|
-
}, [items.length]);
|
|
92
|
+
}, [items.length, duration, delay, easing, options]);
|
|
90
93
|
|
|
91
94
|
return containerRef;
|
|
92
95
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export function useConfigManagement() {
|
|
4
4
|
const [detectedPaths, setDetectedPaths] = useState([]);
|
|
@@ -12,7 +12,7 @@ export function useConfigManagement() {
|
|
|
12
12
|
const [backupContent, setBackupContent] = useState(null);
|
|
13
13
|
const [loadingBackup, setLoadingBackup] = useState(false);
|
|
14
14
|
|
|
15
|
-
const detectConfigPaths = async () => {
|
|
15
|
+
const detectConfigPaths = useCallback(async () => {
|
|
16
16
|
setDetecting(true);
|
|
17
17
|
try {
|
|
18
18
|
const res = await fetch('/api/config/detect');
|
|
@@ -23,9 +23,9 @@ export function useConfigManagement() {
|
|
|
23
23
|
} finally {
|
|
24
24
|
setDetecting(false);
|
|
25
25
|
}
|
|
26
|
-
};
|
|
26
|
+
}, []);
|
|
27
27
|
|
|
28
|
-
const loadBackups = async () => {
|
|
28
|
+
const loadBackups = useCallback(async () => {
|
|
29
29
|
setLoadingBackups(true);
|
|
30
30
|
try {
|
|
31
31
|
const res = await fetch('/api/config/backups');
|
|
@@ -36,7 +36,7 @@ export function useConfigManagement() {
|
|
|
36
36
|
} finally {
|
|
37
37
|
setLoadingBackups(false);
|
|
38
38
|
}
|
|
39
|
-
};
|
|
39
|
+
}, []);
|
|
40
40
|
|
|
41
41
|
const handleViewConfig = async (filePath) => {
|
|
42
42
|
setLoadingConfig(true);
|
|
@@ -49,7 +49,7 @@ export function useConfigManagement() {
|
|
|
49
49
|
} else {
|
|
50
50
|
setConfigContent(null);
|
|
51
51
|
}
|
|
52
|
-
} catch (
|
|
52
|
+
} catch (_err) {
|
|
53
53
|
setConfigContent(null);
|
|
54
54
|
} finally {
|
|
55
55
|
setLoadingConfig(false);
|
|
@@ -69,7 +69,7 @@ export function useConfigManagement() {
|
|
|
69
69
|
} else {
|
|
70
70
|
setBackupContent(null);
|
|
71
71
|
}
|
|
72
|
-
} catch (
|
|
72
|
+
} catch (_err) {
|
|
73
73
|
setBackupContent(null);
|
|
74
74
|
} finally {
|
|
75
75
|
setLoadingBackup(false);
|
|
@@ -98,7 +98,7 @@ export function useConfigManagement() {
|
|
|
98
98
|
useEffect(() => {
|
|
99
99
|
detectConfigPaths();
|
|
100
100
|
loadBackups();
|
|
101
|
-
}, []);
|
|
101
|
+
}, [detectConfigPaths, loadBackups]);
|
|
102
102
|
|
|
103
103
|
return {
|
|
104
104
|
detectedPaths,
|
package/ui/src/index.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import url(
|
|
1
|
+
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;500&display=swap");
|
|
2
2
|
|
|
3
3
|
* {
|
|
4
4
|
margin: 0;
|
|
@@ -7,12 +7,7 @@
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
body {
|
|
10
|
-
font-family:
|
|
11
|
-
'Roboto',
|
|
12
|
-
-apple-system,
|
|
13
|
-
BlinkMacSystemFont,
|
|
14
|
-
'Segoe UI',
|
|
15
|
-
sans-serif;
|
|
10
|
+
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
16
11
|
background: #ffffff;
|
|
17
12
|
color: #202124;
|
|
18
13
|
overflow: hidden;
|
|
@@ -23,7 +18,7 @@ body {
|
|
|
23
18
|
code,
|
|
24
19
|
pre,
|
|
25
20
|
.monospace {
|
|
26
|
-
font-family:
|
|
21
|
+
font-family: "Roboto Mono", "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
#root {
|
package/ui/src/theme.js
CHANGED
|
@@ -53,9 +53,9 @@ export function withOpacity(color, opacity) {
|
|
|
53
53
|
// Remove # if present
|
|
54
54
|
const hex = color.replace('#', '');
|
|
55
55
|
// Convert to RGB
|
|
56
|
-
const r = parseInt(hex.substring(0, 2), 16);
|
|
57
|
-
const g = parseInt(hex.substring(2, 4), 16);
|
|
58
|
-
const b = parseInt(hex.substring(4, 6), 16);
|
|
56
|
+
const r = Number.parseInt(hex.substring(0, 2), 16);
|
|
57
|
+
const g = Number.parseInt(hex.substring(2, 4), 16);
|
|
58
|
+
const b = Number.parseInt(hex.substring(4, 6), 16);
|
|
59
59
|
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
60
60
|
}
|
|
61
61
|
|
package/ui/src/utils/hexUtils.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
export function generateHexDump(text) {
|
|
2
|
-
if (!text)
|
|
2
|
+
if (!text) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
3
5
|
const bytes = new TextEncoder().encode(text);
|
|
4
6
|
const lines = [];
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
const chunkSize = 16;
|
|
8
|
+
const totalChunks = Math.ceil(bytes.length / chunkSize);
|
|
9
|
+
|
|
10
|
+
for (const chunkIndex of Array.from({ length: totalChunks }, (_, idx) => idx)) {
|
|
11
|
+
const startOffset = chunkIndex * chunkSize;
|
|
12
|
+
const chunk = bytes.slice(startOffset, startOffset + chunkSize);
|
|
7
13
|
const hex = Array.from(chunk)
|
|
8
14
|
.map((b) => b.toString(16).padStart(2, '0'))
|
|
9
15
|
.join(' ');
|
|
10
16
|
const ascii = Array.from(chunk)
|
|
11
17
|
.map((b) => (b >= 32 && b < 127 ? String.fromCharCode(b) : '.'))
|
|
12
18
|
.join('');
|
|
13
|
-
const offset =
|
|
19
|
+
const offset = startOffset.toString(16).padStart(8, '0');
|
|
14
20
|
lines.push({ offset, hex, ascii });
|
|
15
21
|
}
|
|
16
22
|
return lines;
|
|
@@ -20,5 +26,5 @@ export function createFullRequestText(headers, bodyRaw) {
|
|
|
20
26
|
const headersText = Object.entries(headers)
|
|
21
27
|
.map(([key, value]) => `${key}: ${value}`)
|
|
22
28
|
.join('\r\n');
|
|
23
|
-
return headersText + (bodyRaw ?
|
|
29
|
+
return headersText + (bodyRaw ? `\r\n\r\n${bodyRaw}` : '');
|
|
24
30
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { pairRequestsWithResponses } from './requestUtils.js';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
IconTool,
|
|
2
|
+
IconBell,
|
|
5
3
|
IconDatabase,
|
|
6
4
|
IconMessage,
|
|
7
|
-
IconBell,
|
|
8
|
-
IconUser,
|
|
9
5
|
IconPackage,
|
|
6
|
+
IconRefresh,
|
|
7
|
+
IconTool,
|
|
8
|
+
IconUser,
|
|
10
9
|
} from '@tabler/icons-react';
|
|
10
|
+
import { pairRequestsWithResponses } from './requestUtils.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* MCP Method Categories based on the protocol specification
|
|
@@ -27,7 +27,9 @@ export const MCP_METHOD_CATEGORIES = {
|
|
|
27
27
|
* Categorize an MCP method into its protocol category
|
|
28
28
|
*/
|
|
29
29
|
export function categorizeMcpMethod(method) {
|
|
30
|
-
if (!method)
|
|
30
|
+
if (!method) {
|
|
31
|
+
return MCP_METHOD_CATEGORIES.OTHER;
|
|
32
|
+
}
|
|
31
33
|
|
|
32
34
|
// Lifecycle methods
|
|
33
35
|
if (method === 'initialize' || method === 'notifications/initialized') {
|
|
@@ -112,7 +114,9 @@ export function groupByMcpSessionAndCategory(requests) {
|
|
|
112
114
|
|
|
113
115
|
pairs.forEach((pair) => {
|
|
114
116
|
const request = pair.request || pair.response;
|
|
115
|
-
if (!request)
|
|
117
|
+
if (!request) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
116
120
|
|
|
117
121
|
const sessionId = request.session_id || '__NO_SESSION__';
|
|
118
122
|
const method = getJsonRpcMethod(request);
|
|
@@ -140,7 +144,7 @@ export function groupByMcpSessionAndCategory(requests) {
|
|
|
140
144
|
});
|
|
141
145
|
|
|
142
146
|
return Array.from(sessionGroups.entries())
|
|
143
|
-
.map(([
|
|
147
|
+
.map(([_sessionId, session]) => ({
|
|
144
148
|
sessionId: session.sessionId,
|
|
145
149
|
firstTimestamp: session.firstTimestamp,
|
|
146
150
|
categories: Array.from(session.categories.entries())
|
|
@@ -182,7 +186,9 @@ export function groupByMcpCategory(requests) {
|
|
|
182
186
|
|
|
183
187
|
pairs.forEach((pair) => {
|
|
184
188
|
const request = pair.request || pair.response;
|
|
185
|
-
if (!request)
|
|
189
|
+
if (!request) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
186
192
|
|
|
187
193
|
const method = getJsonRpcMethod(request);
|
|
188
194
|
const category = categorizeMcpMethod(method || '');
|
|
@@ -226,7 +232,9 @@ export function groupByMcpCategory(requests) {
|
|
|
226
232
|
* Based on the protocol specification
|
|
227
233
|
*/
|
|
228
234
|
export function getMethodDescription(method) {
|
|
229
|
-
if (!method)
|
|
235
|
+
if (!method) {
|
|
236
|
+
return 'Unknown operation';
|
|
237
|
+
}
|
|
230
238
|
|
|
231
239
|
const descriptions = {
|
|
232
240
|
// Lifecycle
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getJsonRpcMethod } from './requestUtils.js';
|
|
2
|
+
|
|
3
|
+
export function pairRequestsWithResponses(requests) {
|
|
4
|
+
const pairs = [];
|
|
5
|
+
const processed = new Set();
|
|
6
|
+
|
|
7
|
+
// Helper function to check if two requests match (same session, JSON-RPC method, and optionally jsonrpc_id)
|
|
8
|
+
const matches = (req, resp) => {
|
|
9
|
+
// Session ID must match (or both null for initiation)
|
|
10
|
+
const sessionMatch = req.session_id === resp.session_id;
|
|
11
|
+
if (!sessionMatch) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// JSON-RPC Method must match
|
|
16
|
+
const reqMethod = getJsonRpcMethod(req);
|
|
17
|
+
const respMethod = getJsonRpcMethod(resp);
|
|
18
|
+
|
|
19
|
+
// Both must have a method, and they must match
|
|
20
|
+
if (!reqMethod || !respMethod) {
|
|
21
|
+
// If either doesn't have a method, we can't match by method
|
|
22
|
+
// Fall back to JSON-RPC ID matching only
|
|
23
|
+
if (req.jsonrpc_id && resp.jsonrpc_id) {
|
|
24
|
+
return req.jsonrpc_id === resp.jsonrpc_id;
|
|
25
|
+
}
|
|
26
|
+
// If no method and no ID, we can't match reliably
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const methodMatch = reqMethod === respMethod;
|
|
31
|
+
if (!methodMatch) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If JSON-RPC ID exists, it must match (for more precise pairing)
|
|
36
|
+
if (req.jsonrpc_id && resp.jsonrpc_id) {
|
|
37
|
+
return req.jsonrpc_id === resp.jsonrpc_id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If no JSON-RPC ID, match by session and method only
|
|
41
|
+
return true;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
requests.forEach((request) => {
|
|
45
|
+
if (processed.has(request.frame_number)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (request.direction === 'request') {
|
|
50
|
+
// Find matching response - must match session, endpoint, and optionally jsonrpc_id
|
|
51
|
+
const response = requests.find(
|
|
52
|
+
(r) =>
|
|
53
|
+
r.direction === 'response' &&
|
|
54
|
+
!processed.has(r.frame_number) &&
|
|
55
|
+
matches(request, r) &&
|
|
56
|
+
r.frame_number > request.frame_number
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (response) {
|
|
60
|
+
pairs.push({ request, response, frame_number: request.frame_number });
|
|
61
|
+
processed.add(request.frame_number);
|
|
62
|
+
processed.add(response.frame_number);
|
|
63
|
+
} else {
|
|
64
|
+
// Request without response
|
|
65
|
+
pairs.push({ request, response: null, frame_number: request.frame_number });
|
|
66
|
+
processed.add(request.frame_number);
|
|
67
|
+
}
|
|
68
|
+
} else if (request.direction === 'response') {
|
|
69
|
+
// Find matching request - must match session, endpoint, and optionally jsonrpc_id
|
|
70
|
+
const matchingRequest = requests.find(
|
|
71
|
+
(r) =>
|
|
72
|
+
r.direction === 'request' &&
|
|
73
|
+
!processed.has(r.frame_number) &&
|
|
74
|
+
matches(r, request) &&
|
|
75
|
+
r.frame_number < request.frame_number
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (!matchingRequest) {
|
|
79
|
+
// Response without request (orphaned)
|
|
80
|
+
pairs.push({ request: null, response: request, frame_number: request.frame_number });
|
|
81
|
+
processed.add(request.frame_number);
|
|
82
|
+
}
|
|
83
|
+
// If matching request exists, it will be handled when we iterate over it
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Sort by frame number (descending - latest first)
|
|
88
|
+
return pairs.sort((a, b) => b.frame_number - a.frame_number);
|
|
89
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
const LLM_SERVER = 'LLM Server';
|
|
1
2
|
export function extractServerName(request) {
|
|
2
3
|
if (request.body_json) {
|
|
3
4
|
try {
|
|
4
5
|
const body =
|
|
5
6
|
typeof request.body_json === 'string' ? JSON.parse(request.body_json) : request.body_json;
|
|
6
|
-
if (body.params
|
|
7
|
+
if (body.params?.name) {
|
|
7
8
|
const fullName = body.params.name;
|
|
8
9
|
return fullName.includes('.') ? fullName.split('.')[0] : fullName;
|
|
9
10
|
}
|
|
10
|
-
} catch (
|
|
11
|
+
} catch (_e) {
|
|
11
12
|
// Failed to parse JSON, try body_raw
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -16,11 +17,11 @@ export function extractServerName(request) {
|
|
|
16
17
|
try {
|
|
17
18
|
const body =
|
|
18
19
|
typeof request.body_raw === 'string' ? JSON.parse(request.body_raw) : request.body_raw;
|
|
19
|
-
if (body.params
|
|
20
|
+
if (body.params?.name) {
|
|
20
21
|
const fullName = body.params.name;
|
|
21
22
|
return fullName.includes('.') ? fullName.split('.')[0] : fullName;
|
|
22
23
|
}
|
|
23
|
-
} catch (
|
|
24
|
+
} catch (_e) {
|
|
24
25
|
// Failed to parse
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -33,13 +34,17 @@ export function extractServerName(request) {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export function formatRelativeTime(timestampISO, firstTime) {
|
|
36
|
-
if (!firstTime)
|
|
37
|
+
if (!firstTime) {
|
|
38
|
+
return '0.000000';
|
|
39
|
+
}
|
|
37
40
|
const diff = new Date(timestampISO) - new Date(firstTime);
|
|
38
41
|
return (diff / 1000).toFixed(6);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export function formatDateTime(timestampISO) {
|
|
42
|
-
if (!timestampISO)
|
|
45
|
+
if (!timestampISO) {
|
|
46
|
+
return '-';
|
|
47
|
+
}
|
|
43
48
|
try {
|
|
44
49
|
const date = new Date(timestampISO);
|
|
45
50
|
return date.toLocaleString('en-US', {
|
|
@@ -51,7 +56,7 @@ export function formatDateTime(timestampISO) {
|
|
|
51
56
|
second: '2-digit',
|
|
52
57
|
hour12: false,
|
|
53
58
|
});
|
|
54
|
-
} catch (
|
|
59
|
+
} catch (_e) {
|
|
55
60
|
return timestampISO;
|
|
56
61
|
}
|
|
57
62
|
}
|
|
@@ -59,13 +64,13 @@ export function formatDateTime(timestampISO) {
|
|
|
59
64
|
export function getSourceDest(request) {
|
|
60
65
|
if (request.direction === 'request') {
|
|
61
66
|
return {
|
|
62
|
-
source:
|
|
63
|
-
dest: request.
|
|
67
|
+
source: LLM_SERVER,
|
|
68
|
+
dest: request.remote_address || 'Unknown MCP Client',
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
return {
|
|
67
|
-
source: request.
|
|
68
|
-
dest:
|
|
72
|
+
source: request.remote_address || 'Unknown MCP Server',
|
|
73
|
+
dest: LLM_SERVER,
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
|
|
@@ -78,7 +83,7 @@ export function getEndpoint(request) {
|
|
|
78
83
|
if (body && typeof body === 'object' && body.method) {
|
|
79
84
|
return body.method;
|
|
80
85
|
}
|
|
81
|
-
} catch (
|
|
86
|
+
} catch (_e) {
|
|
82
87
|
// Failed to parse JSON, try body_raw
|
|
83
88
|
}
|
|
84
89
|
}
|
|
@@ -89,7 +94,7 @@ export function getEndpoint(request) {
|
|
|
89
94
|
if (body && typeof body === 'object' && body.method) {
|
|
90
95
|
return body.method;
|
|
91
96
|
}
|
|
92
|
-
} catch (
|
|
97
|
+
} catch (_e) {
|
|
93
98
|
// Failed to parse
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -100,7 +105,7 @@ export function getEndpoint(request) {
|
|
|
100
105
|
try {
|
|
101
106
|
const url = new URL(request.url);
|
|
102
107
|
return url.pathname + (url.search || '');
|
|
103
|
-
} catch (
|
|
108
|
+
} catch (_e) {
|
|
104
109
|
const url = request.url;
|
|
105
110
|
const match = url.match(/^https?:\/\/[^\/]+(\/.*)$/);
|
|
106
111
|
return match ? match[1] : url;
|
|
@@ -126,15 +131,19 @@ export function getInfo(request) {
|
|
|
126
131
|
// If we have both HTTP method and endpoint, show both
|
|
127
132
|
if (httpMethod && url) {
|
|
128
133
|
return `${httpMethod} ${endpoint}`;
|
|
129
|
-
}
|
|
134
|
+
}
|
|
135
|
+
if (httpMethod) {
|
|
130
136
|
return `${httpMethod} ${endpoint}`;
|
|
131
137
|
}
|
|
132
138
|
return endpoint;
|
|
133
|
-
}
|
|
139
|
+
}
|
|
140
|
+
if (httpMethod && url) {
|
|
134
141
|
return `${httpMethod} ${url}`;
|
|
135
|
-
}
|
|
142
|
+
}
|
|
143
|
+
if (httpMethod) {
|
|
136
144
|
return httpMethod;
|
|
137
|
-
}
|
|
145
|
+
}
|
|
146
|
+
if (url) {
|
|
138
147
|
return url;
|
|
139
148
|
}
|
|
140
149
|
return 'Request';
|
|
@@ -148,9 +157,11 @@ export function getInfo(request) {
|
|
|
148
157
|
|
|
149
158
|
if (status && rpcMethod) {
|
|
150
159
|
return `${status} ${rpcMethod}`;
|
|
151
|
-
}
|
|
160
|
+
}
|
|
161
|
+
if (status) {
|
|
152
162
|
return `Status: ${status}`;
|
|
153
|
-
}
|
|
163
|
+
}
|
|
164
|
+
if (rpcMethod) {
|
|
154
165
|
return rpcMethod;
|
|
155
166
|
}
|
|
156
167
|
return 'Response';
|
|
@@ -184,7 +195,7 @@ export function getJsonRpcMethod(req) {
|
|
|
184
195
|
if (body && typeof body === 'object' && body.method) {
|
|
185
196
|
return body.method;
|
|
186
197
|
}
|
|
187
|
-
} catch (
|
|
198
|
+
} catch (_e) {
|
|
188
199
|
// Failed to parse
|
|
189
200
|
}
|
|
190
201
|
}
|
|
@@ -194,7 +205,7 @@ export function getJsonRpcMethod(req) {
|
|
|
194
205
|
if (body && typeof body === 'object' && body.method) {
|
|
195
206
|
return body.method;
|
|
196
207
|
}
|
|
197
|
-
} catch (
|
|
208
|
+
} catch (_e) {
|
|
198
209
|
// Failed to parse
|
|
199
210
|
}
|
|
200
211
|
}
|
|
@@ -203,10 +214,10 @@ export function getJsonRpcMethod(req) {
|
|
|
203
214
|
// For responses, try to extract from body if available
|
|
204
215
|
if (req.direction === 'response' && req.body_json) {
|
|
205
216
|
try {
|
|
206
|
-
const
|
|
217
|
+
const _body = typeof req.body_json === 'string' ? JSON.parse(req.body_json) : req.body_json;
|
|
207
218
|
// Responses don't have a method field, but we can check if it's an error response
|
|
208
219
|
// For now, we'll rely on jsonrpc_method field
|
|
209
|
-
} catch (
|
|
220
|
+
} catch (_e) {
|
|
210
221
|
// Failed to parse
|
|
211
222
|
}
|
|
212
223
|
}
|
|
@@ -214,84 +225,5 @@ export function getJsonRpcMethod(req) {
|
|
|
214
225
|
return null;
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
export
|
|
218
|
-
|
|
219
|
-
const processed = new Set();
|
|
220
|
-
|
|
221
|
-
// Helper function to check if two requests match (same session, JSON-RPC method, and optionally jsonrpc_id)
|
|
222
|
-
const matches = (req, resp) => {
|
|
223
|
-
// Session ID must match (or both null for initiation)
|
|
224
|
-
const sessionMatch = req.session_id === resp.session_id;
|
|
225
|
-
if (!sessionMatch) return false;
|
|
226
|
-
|
|
227
|
-
// JSON-RPC Method must match
|
|
228
|
-
const reqMethod = getJsonRpcMethod(req);
|
|
229
|
-
const respMethod = getJsonRpcMethod(resp);
|
|
230
|
-
|
|
231
|
-
// Both must have a method, and they must match
|
|
232
|
-
if (!reqMethod || !respMethod) {
|
|
233
|
-
// If either doesn't have a method, we can't match by method
|
|
234
|
-
// Fall back to JSON-RPC ID matching only
|
|
235
|
-
if (req.jsonrpc_id && resp.jsonrpc_id) {
|
|
236
|
-
return req.jsonrpc_id === resp.jsonrpc_id;
|
|
237
|
-
}
|
|
238
|
-
// If no method and no ID, we can't match reliably
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const methodMatch = reqMethod === respMethod;
|
|
243
|
-
if (!methodMatch) return false;
|
|
244
|
-
|
|
245
|
-
// If JSON-RPC ID exists, it must match (for more precise pairing)
|
|
246
|
-
if (req.jsonrpc_id && resp.jsonrpc_id) {
|
|
247
|
-
return req.jsonrpc_id === resp.jsonrpc_id;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// If no JSON-RPC ID, match by session and method only
|
|
251
|
-
return true;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
requests.forEach((request) => {
|
|
255
|
-
if (processed.has(request.frame_number)) return;
|
|
256
|
-
|
|
257
|
-
if (request.direction === 'request') {
|
|
258
|
-
// Find matching response - must match session, endpoint, and optionally jsonrpc_id
|
|
259
|
-
const response = requests.find(
|
|
260
|
-
(r) =>
|
|
261
|
-
r.direction === 'response' &&
|
|
262
|
-
!processed.has(r.frame_number) &&
|
|
263
|
-
matches(request, r) &&
|
|
264
|
-
r.frame_number > request.frame_number
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
if (response) {
|
|
268
|
-
pairs.push({ request, response, frame_number: request.frame_number });
|
|
269
|
-
processed.add(request.frame_number);
|
|
270
|
-
processed.add(response.frame_number);
|
|
271
|
-
} else {
|
|
272
|
-
// Request without response
|
|
273
|
-
pairs.push({ request, response: null, frame_number: request.frame_number });
|
|
274
|
-
processed.add(request.frame_number);
|
|
275
|
-
}
|
|
276
|
-
} else if (request.direction === 'response') {
|
|
277
|
-
// Find matching request - must match session, endpoint, and optionally jsonrpc_id
|
|
278
|
-
const matchingRequest = requests.find(
|
|
279
|
-
(r) =>
|
|
280
|
-
r.direction === 'request' &&
|
|
281
|
-
!processed.has(r.frame_number) &&
|
|
282
|
-
matches(r, request) &&
|
|
283
|
-
r.frame_number < request.frame_number
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
if (!matchingRequest) {
|
|
287
|
-
// Response without request (orphaned)
|
|
288
|
-
pairs.push({ request: null, response: request, frame_number: request.frame_number });
|
|
289
|
-
processed.add(request.frame_number);
|
|
290
|
-
}
|
|
291
|
-
// If matching request exists, it will be handled when we iterate over it
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// Sort by frame number (descending - latest first)
|
|
296
|
-
return pairs.sort((a, b) => b.frame_number - a.frame_number);
|
|
297
|
-
}
|
|
228
|
+
// Re-export pairRequestsWithResponses from requestPairing.js
|
|
229
|
+
export { pairRequestsWithResponses } from './requestPairing.js';
|