@mcp-shark/mcp-shark 1.4.2 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -645
- package/bin/mcp-shark.js +30 -36
- package/mcp-server/index.js +115 -0
- package/mcp-server/lib/auditor/audit.js +22 -38
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +4 -12
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
- package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
- package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
- package/mcp-server/lib/server/internal/run.js +4 -16
- package/mcp-server/lib/server/internal/server.js +6 -7
- package/mcp-server/lib/server/internal/session.js +2 -15
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/shared/logger.js +90 -0
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +45 -47
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +7 -6
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +86 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -4
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
- package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
- package/ui/src/components/McpPlayground.jsx +5 -2
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +32 -101
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -260
|
@@ -43,6 +43,7 @@ export default function ToolCallPanel({
|
|
|
43
43
|
>
|
|
44
44
|
<div>
|
|
45
45
|
<label
|
|
46
|
+
htmlFor="tool-args-textarea"
|
|
46
47
|
style={{
|
|
47
48
|
display: 'block',
|
|
48
49
|
fontSize: '12px',
|
|
@@ -54,6 +55,7 @@ export default function ToolCallPanel({
|
|
|
54
55
|
Arguments (JSON)
|
|
55
56
|
</label>
|
|
56
57
|
<textarea
|
|
58
|
+
id="tool-args-textarea"
|
|
57
59
|
value={toolArgs}
|
|
58
60
|
onChange={(e) => onToolArgsChange(e.target.value)}
|
|
59
61
|
style={{
|
|
@@ -71,6 +73,7 @@ export default function ToolCallPanel({
|
|
|
71
73
|
/>
|
|
72
74
|
</div>
|
|
73
75
|
<button
|
|
76
|
+
type="button"
|
|
74
77
|
onClick={onCallTool}
|
|
75
78
|
disabled={loading}
|
|
76
79
|
style={{
|
|
@@ -91,6 +94,7 @@ export default function ToolCallPanel({
|
|
|
91
94
|
{toolResult && (
|
|
92
95
|
<div>
|
|
93
96
|
<label
|
|
97
|
+
htmlFor="tool-result-pre"
|
|
94
98
|
style={{
|
|
95
99
|
display: 'block',
|
|
96
100
|
fontSize: '12px',
|
|
@@ -102,6 +106,7 @@ export default function ToolCallPanel({
|
|
|
102
106
|
Result
|
|
103
107
|
</label>
|
|
104
108
|
<pre
|
|
109
|
+
id="tool-result-pre"
|
|
105
110
|
style={{
|
|
106
111
|
padding: '12px',
|
|
107
112
|
background: colors.bgSecondary,
|
|
@@ -2,8 +2,10 @@ import { colors } from '../../../theme';
|
|
|
2
2
|
|
|
3
3
|
export default function ToolItem({ tool, isSelected, onClick }) {
|
|
4
4
|
return (
|
|
5
|
-
<
|
|
5
|
+
<button
|
|
6
|
+
type="button"
|
|
6
7
|
onClick={onClick}
|
|
8
|
+
aria-label={`Select tool ${tool.name}`}
|
|
7
9
|
style={{
|
|
8
10
|
padding: '16px 20px',
|
|
9
11
|
margin: '4px 8px',
|
|
@@ -14,6 +16,8 @@ export default function ToolItem({ tool, isSelected, onClick }) {
|
|
|
14
16
|
boxShadow: isSelected ? `0 2px 4px ${colors.shadowSm}` : 'none',
|
|
15
17
|
transition: 'all 0.2s ease',
|
|
16
18
|
position: 'relative',
|
|
19
|
+
width: '100%',
|
|
20
|
+
textAlign: 'left',
|
|
17
21
|
}}
|
|
18
22
|
onMouseEnter={(e) => {
|
|
19
23
|
if (!isSelected) {
|
|
@@ -68,6 +72,6 @@ export default function ToolItem({ tool, isSelected, onClick }) {
|
|
|
68
72
|
)}
|
|
69
73
|
</div>
|
|
70
74
|
</div>
|
|
71
|
-
</
|
|
75
|
+
</button>
|
|
72
76
|
);
|
|
73
77
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { colors } from '../../../theme';
|
|
2
|
-
import LoadingState from '../common/LoadingState';
|
|
3
|
-
import ErrorState from '../common/ErrorState';
|
|
4
2
|
import EmptyState from '../common/EmptyState';
|
|
3
|
+
import ErrorState from '../common/ErrorState';
|
|
4
|
+
import LoadingState from '../common/LoadingState';
|
|
5
5
|
import ToolItem from './ToolItem';
|
|
6
6
|
|
|
7
7
|
export default function ToolsList({
|
|
@@ -26,7 +26,7 @@ export default function ToolsList({
|
|
|
26
26
|
<LoadingState message="Waiting for MCP server to start..." />
|
|
27
27
|
) : toolsLoading || !toolsLoaded ? (
|
|
28
28
|
<LoadingState message="Loading tools..." />
|
|
29
|
-
) : error
|
|
29
|
+
) : error?.includes('tools:') ? (
|
|
30
30
|
<ErrorState message={`Error loading tools: ${error.replace('tools: ', '')}`} />
|
|
31
31
|
) : tools.length === 0 ? (
|
|
32
32
|
<EmptyState message="No tools available." />
|
|
@@ -34,7 +34,7 @@ export default function ToolsList({
|
|
|
34
34
|
<div style={{ padding: '8px 0' }}>
|
|
35
35
|
{tools.map((tool, idx) => (
|
|
36
36
|
<ToolItem
|
|
37
|
-
key={idx}
|
|
37
|
+
key={tool.name || `tool-${idx}`}
|
|
38
38
|
tool={tool}
|
|
39
39
|
isSelected={selectedTool?.name === tool.name}
|
|
40
40
|
onClick={() => onSelectTool(tool)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { colors, fonts } from '../../theme';
|
|
2
|
-
import ToolsList from './ToolsSection/ToolsList';
|
|
3
2
|
import ToolCallPanel from './ToolsSection/ToolCallPanel';
|
|
3
|
+
import ToolsList from './ToolsSection/ToolsList';
|
|
4
4
|
|
|
5
5
|
export default function ToolsSection({
|
|
6
6
|
tools,
|
|
@@ -33,6 +33,7 @@ export default function ToolsSection({
|
|
|
33
33
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
|
34
34
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
35
35
|
<button
|
|
36
|
+
type="button"
|
|
36
37
|
onClick={onRefresh}
|
|
37
38
|
disabled={loading || toolsLoading}
|
|
38
39
|
style={{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export function useMcpDataLoader(makeMcpRequest, selectedServer, setError) {
|
|
4
4
|
const [tools, setTools] = useState([]);
|
|
@@ -11,7 +11,7 @@ export function useMcpDataLoader(makeMcpRequest, selectedServer, setError) {
|
|
|
11
11
|
const [promptsLoaded, setPromptsLoaded] = useState(false);
|
|
12
12
|
const [resourcesLoaded, setResourcesLoaded] = useState(false);
|
|
13
13
|
|
|
14
|
-
const loadTools = async () => {
|
|
14
|
+
const loadTools = useCallback(async () => {
|
|
15
15
|
if (!selectedServer) {
|
|
16
16
|
setError('tools: No server selected');
|
|
17
17
|
setToolsLoaded(true);
|
|
@@ -32,9 +32,9 @@ export function useMcpDataLoader(makeMcpRequest, selectedServer, setError) {
|
|
|
32
32
|
} finally {
|
|
33
33
|
setToolsLoading(false);
|
|
34
34
|
}
|
|
35
|
-
};
|
|
35
|
+
}, [selectedServer, makeMcpRequest, setError]);
|
|
36
36
|
|
|
37
|
-
const loadPrompts = async () => {
|
|
37
|
+
const loadPrompts = useCallback(async () => {
|
|
38
38
|
if (!selectedServer) {
|
|
39
39
|
setError('prompts: No server selected');
|
|
40
40
|
setPromptsLoaded(true);
|
|
@@ -55,9 +55,9 @@ export function useMcpDataLoader(makeMcpRequest, selectedServer, setError) {
|
|
|
55
55
|
} finally {
|
|
56
56
|
setPromptsLoading(false);
|
|
57
57
|
}
|
|
58
|
-
};
|
|
58
|
+
}, [selectedServer, makeMcpRequest, setError]);
|
|
59
59
|
|
|
60
|
-
const loadResources = async () => {
|
|
60
|
+
const loadResources = useCallback(async () => {
|
|
61
61
|
if (!selectedServer) {
|
|
62
62
|
setError('resources: No server selected');
|
|
63
63
|
setResourcesLoaded(true);
|
|
@@ -78,16 +78,16 @@ export function useMcpDataLoader(makeMcpRequest, selectedServer, setError) {
|
|
|
78
78
|
} finally {
|
|
79
79
|
setResourcesLoading(false);
|
|
80
80
|
}
|
|
81
|
-
};
|
|
81
|
+
}, [selectedServer, makeMcpRequest, setError]);
|
|
82
82
|
|
|
83
|
-
const resetData = () => {
|
|
83
|
+
const resetData = useCallback(() => {
|
|
84
84
|
setToolsLoaded(false);
|
|
85
85
|
setPromptsLoaded(false);
|
|
86
86
|
setResourcesLoaded(false);
|
|
87
87
|
setTools([]);
|
|
88
88
|
setPrompts([]);
|
|
89
89
|
setResources([]);
|
|
90
|
-
};
|
|
90
|
+
}, []);
|
|
91
91
|
|
|
92
92
|
return {
|
|
93
93
|
tools,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export function useMcpRequest(selectedServer) {
|
|
4
4
|
const [loading, setLoading] = useState(false);
|
|
5
5
|
const [error, setError] = useState(null);
|
|
6
6
|
const [sessionId, setSessionId] = useState(null);
|
|
7
|
+
const sessionIdRef = useRef(sessionId);
|
|
8
|
+
|
|
9
|
+
// Keep ref in sync with state
|
|
10
|
+
sessionIdRef.current = sessionId;
|
|
7
11
|
|
|
8
12
|
const makeMcpRequest = useCallback(
|
|
9
13
|
async (method, params = {}) => {
|
|
@@ -16,8 +20,9 @@ export function useMcpRequest(selectedServer) {
|
|
|
16
20
|
|
|
17
21
|
try {
|
|
18
22
|
const headers = { 'Content-Type': 'application/json' };
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
const currentSessionId = sessionIdRef.current;
|
|
24
|
+
if (currentSessionId) {
|
|
25
|
+
headers['Mcp-Session-Id'] = currentSessionId;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
const response = await fetch('/api/playground/proxy', {
|
|
@@ -32,7 +37,7 @@ export function useMcpRequest(selectedServer) {
|
|
|
32
37
|
response.headers.get('Mcp-Session-Id') ||
|
|
33
38
|
response.headers.get('mcp-session-id') ||
|
|
34
39
|
data._sessionId;
|
|
35
|
-
if (responseSessionId && responseSessionId !==
|
|
40
|
+
if (responseSessionId && responseSessionId !== currentSessionId) {
|
|
36
41
|
setSessionId(responseSessionId);
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -48,7 +53,7 @@ export function useMcpRequest(selectedServer) {
|
|
|
48
53
|
setLoading(false);
|
|
49
54
|
}
|
|
50
55
|
},
|
|
51
|
-
[selectedServer
|
|
56
|
+
[selectedServer]
|
|
52
57
|
);
|
|
53
58
|
|
|
54
59
|
const resetSession = useCallback(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export function useMcpServerStatus() {
|
|
4
4
|
const [serverStatus, setServerStatus] = useState(null);
|
|
@@ -6,52 +6,72 @@ export function useMcpServerStatus() {
|
|
|
6
6
|
const [availableServers, setAvailableServers] = useState([]);
|
|
7
7
|
const [selectedServer, setSelectedServer] = useState(null);
|
|
8
8
|
|
|
9
|
-
const checkServerStatus = async () => {
|
|
9
|
+
const checkServerStatus = useCallback(async () => {
|
|
10
10
|
try {
|
|
11
11
|
const res = await fetch('/api/composite/status');
|
|
12
12
|
if (!res.ok) {
|
|
13
13
|
throw new Error('Server not available');
|
|
14
14
|
}
|
|
15
15
|
const data = await res.json();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
setServerStatus((prevStatus) => {
|
|
17
|
+
const wasRunning = prevStatus?.running;
|
|
18
|
+
if (!data.running) {
|
|
19
|
+
setShowLoadingModal((prevModal) => {
|
|
20
|
+
if (!prevModal || wasRunning) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return prevModal;
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
setShowLoadingModal((prevModal) => {
|
|
27
|
+
if (prevModal) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return prevModal;
|
|
31
|
+
});
|
|
22
32
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} catch (err) {
|
|
33
|
+
return data;
|
|
34
|
+
});
|
|
35
|
+
} catch (_err) {
|
|
27
36
|
setServerStatus({ running: false });
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
setShowLoadingModal((prevModal) => {
|
|
38
|
+
if (!prevModal) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return prevModal;
|
|
42
|
+
});
|
|
31
43
|
}
|
|
32
|
-
};
|
|
44
|
+
}, []);
|
|
33
45
|
|
|
34
|
-
const loadAvailableServers = async () => {
|
|
46
|
+
const loadAvailableServers = useCallback(async () => {
|
|
35
47
|
try {
|
|
36
48
|
const res = await fetch('/api/composite/servers');
|
|
37
49
|
if (res.ok) {
|
|
38
50
|
const data = await res.json();
|
|
39
51
|
setAvailableServers(data.servers || []);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
setSelectedServer((current) => {
|
|
53
|
+
if (!current && data.servers && data.servers.length > 0) {
|
|
54
|
+
return data.servers[0];
|
|
55
|
+
}
|
|
56
|
+
return current;
|
|
57
|
+
});
|
|
43
58
|
}
|
|
44
59
|
} catch (err) {
|
|
45
60
|
console.error('Failed to load servers:', err);
|
|
46
61
|
}
|
|
47
|
-
};
|
|
62
|
+
}, []);
|
|
48
63
|
|
|
64
|
+
// Load servers once on mount
|
|
49
65
|
useEffect(() => {
|
|
50
|
-
checkServerStatus();
|
|
51
66
|
loadAvailableServers();
|
|
67
|
+
}, [loadAvailableServers]);
|
|
68
|
+
|
|
69
|
+
// Poll server status every 2 seconds
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
checkServerStatus();
|
|
52
72
|
const interval = setInterval(checkServerStatus, 2000);
|
|
53
73
|
return () => clearInterval(interval);
|
|
54
|
-
}, []);
|
|
74
|
+
}, [checkServerStatus]);
|
|
55
75
|
|
|
56
76
|
useEffect(() => {
|
|
57
77
|
if (availableServers.length > 0 && !selectedServer) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useMcpDataLoader } from './hooks/useMcpDataLoader';
|
|
2
3
|
import { useMcpRequest } from './hooks/useMcpRequest';
|
|
3
4
|
import { useMcpServerStatus } from './hooks/useMcpServerStatus';
|
|
4
|
-
import { useMcpDataLoader } from './hooks/useMcpDataLoader';
|
|
5
5
|
|
|
6
6
|
export function useMcpPlayground() {
|
|
7
7
|
const [activeSection, setActiveSection] = useState('tools');
|
|
@@ -35,7 +35,10 @@ export function useMcpPlayground() {
|
|
|
35
35
|
resetData,
|
|
36
36
|
} = useMcpDataLoader(makeMcpRequest, selectedServer, setError);
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const activeSectionRef = useRef(activeSection);
|
|
39
|
+
activeSectionRef.current = activeSection;
|
|
40
|
+
|
|
41
|
+
// Reset and reload data when server changes (not when section changes)
|
|
39
42
|
useEffect(() => {
|
|
40
43
|
if (!selectedServer || !serverStatus?.running) {
|
|
41
44
|
return;
|
|
@@ -54,69 +57,84 @@ export function useMcpPlayground() {
|
|
|
54
57
|
setError(null);
|
|
55
58
|
|
|
56
59
|
const timer = setTimeout(() => {
|
|
57
|
-
|
|
60
|
+
const currentSection = activeSectionRef.current;
|
|
61
|
+
if (currentSection === 'tools') {
|
|
58
62
|
loadTools();
|
|
59
|
-
} else if (
|
|
63
|
+
} else if (currentSection === 'prompts') {
|
|
60
64
|
loadPrompts();
|
|
61
|
-
} else if (
|
|
65
|
+
} else if (currentSection === 'resources') {
|
|
62
66
|
loadResources();
|
|
63
67
|
}
|
|
64
68
|
}, 100);
|
|
65
69
|
|
|
66
70
|
return () => clearTimeout(timer);
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
}, [
|
|
72
|
+
selectedServer,
|
|
73
|
+
serverStatus?.running,
|
|
74
|
+
resetData,
|
|
75
|
+
loadTools,
|
|
76
|
+
loadPrompts,
|
|
77
|
+
loadResources,
|
|
78
|
+
resetSession,
|
|
79
|
+
setError,
|
|
80
|
+
]);
|
|
69
81
|
|
|
82
|
+
// Load data when section changes (only if not already loaded)
|
|
70
83
|
useEffect(() => {
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
) {
|
|
84
|
+
if (!serverStatus?.running || !selectedServer) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Only load if we haven't loaded this section yet and we're not currently loading
|
|
89
|
+
if (activeSection === 'tools' && !toolsLoaded && !toolsLoading) {
|
|
77
90
|
const timer = setTimeout(() => {
|
|
78
91
|
loadTools();
|
|
79
|
-
},
|
|
92
|
+
}, 100);
|
|
80
93
|
return () => clearTimeout(timer);
|
|
81
94
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (!serverStatus?.running || !selectedServer) return;
|
|
87
|
-
|
|
88
|
-
const timer = setTimeout(() => {
|
|
89
|
-
if (activeSection === 'tools' && !toolsLoaded && !toolsLoading) {
|
|
90
|
-
loadTools();
|
|
91
|
-
} else if (activeSection === 'prompts' && !promptsLoaded && !promptsLoading) {
|
|
95
|
+
if (activeSection === 'prompts' && !promptsLoaded && !promptsLoading) {
|
|
96
|
+
const timer = setTimeout(() => {
|
|
92
97
|
loadPrompts();
|
|
93
|
-
}
|
|
98
|
+
}, 100);
|
|
99
|
+
return () => clearTimeout(timer);
|
|
100
|
+
}
|
|
101
|
+
if (activeSection === 'resources' && !resourcesLoaded && !resourcesLoading) {
|
|
102
|
+
const timer = setTimeout(() => {
|
|
94
103
|
loadResources();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return () => clearTimeout(timer);
|
|
99
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
}, 100);
|
|
105
|
+
return () => clearTimeout(timer);
|
|
106
|
+
}
|
|
100
107
|
}, [
|
|
101
108
|
activeSection,
|
|
102
109
|
serverStatus?.running,
|
|
103
110
|
selectedServer,
|
|
104
111
|
toolsLoaded,
|
|
105
|
-
promptsLoaded,
|
|
106
|
-
resourcesLoaded,
|
|
107
112
|
toolsLoading,
|
|
113
|
+
promptsLoaded,
|
|
108
114
|
promptsLoading,
|
|
115
|
+
resourcesLoaded,
|
|
109
116
|
resourcesLoading,
|
|
117
|
+
loadTools,
|
|
118
|
+
loadPrompts,
|
|
119
|
+
loadResources,
|
|
110
120
|
]);
|
|
111
121
|
|
|
112
122
|
const handleCallTool = async () => {
|
|
113
|
-
if (!selectedTool)
|
|
123
|
+
if (!selectedTool) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
114
126
|
|
|
115
127
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
const parseArgs = (argsString) => {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(argsString);
|
|
131
|
+
} catch (_e) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const args = parseArgs(toolArgs);
|
|
137
|
+
if (args === null) {
|
|
120
138
|
setError('Invalid JSON in arguments');
|
|
121
139
|
return;
|
|
122
140
|
}
|
|
@@ -133,13 +151,21 @@ export function useMcpPlayground() {
|
|
|
133
151
|
};
|
|
134
152
|
|
|
135
153
|
const handleGetPrompt = async () => {
|
|
136
|
-
if (!selectedPrompt)
|
|
154
|
+
if (!selectedPrompt) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
137
157
|
|
|
138
158
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
const parseArgs = (argsString) => {
|
|
160
|
+
try {
|
|
161
|
+
return JSON.parse(argsString);
|
|
162
|
+
} catch (_e) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const args = parseArgs(promptArgs);
|
|
168
|
+
if (args === null) {
|
|
143
169
|
setError('Invalid JSON in arguments');
|
|
144
170
|
return;
|
|
145
171
|
}
|
|
@@ -156,7 +182,9 @@ export function useMcpPlayground() {
|
|
|
156
182
|
};
|
|
157
183
|
|
|
158
184
|
const handleReadResource = async () => {
|
|
159
|
-
if (!selectedResource)
|
|
185
|
+
if (!selectedResource) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
160
188
|
|
|
161
189
|
try {
|
|
162
190
|
const result = await makeMcpRequest('resources/read', {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { colors, fonts } from '../theme';
|
|
2
|
-
import { useMcpPlayground } from './McpPlayground/useMcpPlayground';
|
|
3
2
|
import LoadingModal from './McpPlayground/LoadingModal';
|
|
4
|
-
import ToolsSection from './McpPlayground/ToolsSection';
|
|
5
3
|
import PromptsSection from './McpPlayground/PromptsSection';
|
|
6
4
|
import ResourcesSection from './McpPlayground/ResourcesSection';
|
|
5
|
+
import ToolsSection from './McpPlayground/ToolsSection';
|
|
6
|
+
import { useMcpPlayground } from './McpPlayground/useMcpPlayground';
|
|
7
7
|
|
|
8
8
|
function McpPlayground() {
|
|
9
9
|
const {
|
|
@@ -100,6 +100,7 @@ function McpPlayground() {
|
|
|
100
100
|
}}
|
|
101
101
|
>
|
|
102
102
|
<label
|
|
103
|
+
htmlFor="mcp-server-select"
|
|
103
104
|
style={{
|
|
104
105
|
fontSize: '13px',
|
|
105
106
|
fontFamily: fonts.body,
|
|
@@ -119,6 +120,7 @@ function McpPlayground() {
|
|
|
119
120
|
{availableServers.map((server) => (
|
|
120
121
|
<button
|
|
121
122
|
key={server}
|
|
123
|
+
type="button"
|
|
122
124
|
onClick={() => setSelectedServer(server)}
|
|
123
125
|
style={{
|
|
124
126
|
padding: '10px 18px',
|
|
@@ -169,6 +171,7 @@ function McpPlayground() {
|
|
|
169
171
|
{['tools', 'prompts', 'resources'].map((section) => (
|
|
170
172
|
<button
|
|
171
173
|
key={section}
|
|
174
|
+
type="button"
|
|
172
175
|
onClick={() => setActiveSection(section)}
|
|
173
176
|
style={{
|
|
174
177
|
padding: '10px 18px',
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { colors, fonts } from '../theme';
|
|
2
1
|
import { IconX } from '@tabler/icons-react';
|
|
2
|
+
import { colors, fonts } from '../theme';
|
|
3
3
|
|
|
4
4
|
function PacketDetailHeader({ request, onClose, matchingPair }) {
|
|
5
5
|
const formatBytes = (bytes) => {
|
|
6
|
-
if (bytes < 1024)
|
|
7
|
-
|
|
6
|
+
if (bytes < 1024) {
|
|
7
|
+
return `${bytes} B`;
|
|
8
|
+
}
|
|
9
|
+
if (bytes < 1024 * 1024) {
|
|
10
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
11
|
+
}
|
|
8
12
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
9
13
|
};
|
|
10
14
|
|
|
@@ -59,6 +63,7 @@ function PacketDetailHeader({ request, onClose, matchingPair }) {
|
|
|
59
63
|
</span>
|
|
60
64
|
</div>
|
|
61
65
|
<button
|
|
66
|
+
type="button"
|
|
62
67
|
onClick={onClose}
|
|
63
68
|
style={{
|
|
64
69
|
background: 'none',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { colors, fonts } from '../../theme';
|
|
2
1
|
import { IconDownload } from '@tabler/icons-react';
|
|
3
2
|
import anime from 'animejs';
|
|
3
|
+
import { colors, fonts } from '../../theme';
|
|
4
4
|
|
|
5
5
|
export default function ExportControls({ stats, onExport }) {
|
|
6
6
|
return (
|
|
@@ -50,6 +50,7 @@ export default function ExportControls({ stats, onExport }) {
|
|
|
50
50
|
}}
|
|
51
51
|
>
|
|
52
52
|
<button
|
|
53
|
+
type="button"
|
|
53
54
|
onClick={() => onExport('json')}
|
|
54
55
|
style={{
|
|
55
56
|
padding: '8px 14px',
|
|
@@ -17,7 +17,10 @@ const ChevronDown = ({ size = 14, color = 'currentColor', rotated = false }) =>
|
|
|
17
17
|
transform: rotated ? 'rotate(-90deg)' : 'rotate(0deg)',
|
|
18
18
|
transition: 'transform 0.2s ease',
|
|
19
19
|
}}
|
|
20
|
+
role="img"
|
|
21
|
+
aria-label="Chevron down icon"
|
|
20
22
|
>
|
|
23
|
+
<title>Chevron down icon</title>
|
|
21
24
|
<polyline points="6 9 12 15 18 9" />
|
|
22
25
|
</svg>
|
|
23
26
|
);
|
|
@@ -35,8 +38,15 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
|
|
|
35
38
|
marginBottom: '20px',
|
|
36
39
|
}}
|
|
37
40
|
>
|
|
38
|
-
<
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
39
43
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
44
|
+
onKeyDown={(e) => {
|
|
45
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
setIsExpanded(!isExpanded);
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
40
50
|
style={{
|
|
41
51
|
padding: '16px 20px',
|
|
42
52
|
background: isExpanded ? colors.bgCard : colors.bgSecondary,
|
|
@@ -47,6 +57,9 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
|
|
|
47
57
|
alignItems: 'center',
|
|
48
58
|
justifyContent: 'space-between',
|
|
49
59
|
transition: 'background-color 0.15s ease',
|
|
60
|
+
width: '100%',
|
|
61
|
+
border: 'none',
|
|
62
|
+
textAlign: 'left',
|
|
50
63
|
}}
|
|
51
64
|
onMouseEnter={(e) => {
|
|
52
65
|
e.currentTarget.style.background = colors.bgHover;
|
|
@@ -71,7 +84,7 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
|
|
|
71
84
|
<ChevronDown size={14} color={titleColor} rotated={!isExpanded} />
|
|
72
85
|
{title}
|
|
73
86
|
</div>
|
|
74
|
-
</
|
|
87
|
+
</button>
|
|
75
88
|
{isExpanded && <div style={{ padding: '20px' }}>{children}</div>}
|
|
76
89
|
</div>
|
|
77
90
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { colors, fonts, withOpacity } from '../../theme';
|
|
2
2
|
import {
|
|
3
|
-
formatRelativeTime,
|
|
4
3
|
formatDateTime,
|
|
5
|
-
|
|
4
|
+
formatRelativeTime,
|
|
6
5
|
getEndpoint,
|
|
6
|
+
getSourceDest,
|
|
7
7
|
} from '../../utils/requestUtils.js';
|
|
8
8
|
|
|
9
9
|
export default function OrphanedResponseRow({ response, selected, firstRequestTime, onSelect }) {
|
|
@@ -14,6 +14,14 @@ export default function OrphanedResponseRow({ response, selected, firstRequestTi
|
|
|
14
14
|
return (
|
|
15
15
|
<tr
|
|
16
16
|
onClick={() => onSelect(response)}
|
|
17
|
+
onKeyDown={(e) => {
|
|
18
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
onSelect(response);
|
|
21
|
+
}
|
|
22
|
+
}}
|
|
23
|
+
tabIndex={0}
|
|
24
|
+
aria-label={`Select orphaned response ${response.frame_number}`}
|
|
17
25
|
style={{
|
|
18
26
|
cursor: 'pointer',
|
|
19
27
|
background: isSelected ? colors.bgSelected : colors.bgUnpaired,
|