@mcp-shark/mcp-shark 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -645
- package/bin/mcp-shark.js +30 -36
- package/mcp-server/index.js +115 -0
- package/mcp-server/lib/auditor/audit.js +34 -42
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +21 -40
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +5 -7
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-list.js +5 -4
- package/mcp-server/lib/server/internal/handlers/resources-read.js +5 -4
- package/mcp-server/lib/server/internal/handlers/tools-call.js +8 -10
- package/mcp-server/lib/server/internal/handlers/tools-list.js +6 -5
- package/mcp-server/lib/server/internal/run.js +5 -17
- package/mcp-server/lib/server/internal/server.js +14 -15
- package/mcp-server/lib/server/internal/session.js +8 -13
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +91 -62
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +140 -112
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +87 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -10
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +52 -23
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +13 -11
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +66 -34
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +13 -11
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +52 -23
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +13 -11
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +70 -0
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +90 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +118 -159
- package/ui/src/components/McpPlayground.jsx +105 -23
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +37 -105
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -244
|
@@ -1,7 +1,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,19 +26,21 @@ 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." />
|
|
33
33
|
) : (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
<div style={{ padding: '8px 0' }}>
|
|
35
|
+
{tools.map((tool, idx) => (
|
|
36
|
+
<ToolItem
|
|
37
|
+
key={tool.name || `tool-${idx}`}
|
|
38
|
+
tool={tool}
|
|
39
|
+
isSelected={selectedTool?.name === tool.name}
|
|
40
|
+
onClick={() => onSelectTool(tool)}
|
|
41
|
+
/>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
42
44
|
)}
|
|
43
45
|
</div>
|
|
44
46
|
);
|
|
@@ -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={{
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useMcpDataLoader(makeMcpRequest, selectedServer, setError) {
|
|
4
|
+
const [tools, setTools] = useState([]);
|
|
5
|
+
const [prompts, setPrompts] = useState([]);
|
|
6
|
+
const [resources, setResources] = useState([]);
|
|
7
|
+
const [toolsLoading, setToolsLoading] = useState(false);
|
|
8
|
+
const [promptsLoading, setPromptsLoading] = useState(false);
|
|
9
|
+
const [resourcesLoading, setResourcesLoading] = useState(false);
|
|
10
|
+
const [toolsLoaded, setToolsLoaded] = useState(false);
|
|
11
|
+
const [promptsLoaded, setPromptsLoaded] = useState(false);
|
|
12
|
+
const [resourcesLoaded, setResourcesLoaded] = useState(false);
|
|
13
|
+
|
|
14
|
+
const loadTools = useCallback(async () => {
|
|
15
|
+
if (!selectedServer) {
|
|
16
|
+
setError('tools: No server selected');
|
|
17
|
+
setToolsLoaded(true);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setToolsLoading(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
const result = await makeMcpRequest('tools/list');
|
|
25
|
+
setTools(result?.tools || []);
|
|
26
|
+
setToolsLoaded(true);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const errorMsg = err.message || 'Failed to load tools';
|
|
29
|
+
setError(`tools: ${errorMsg}`);
|
|
30
|
+
setToolsLoaded(true);
|
|
31
|
+
console.error('Failed to load tools:', err);
|
|
32
|
+
} finally {
|
|
33
|
+
setToolsLoading(false);
|
|
34
|
+
}
|
|
35
|
+
}, [selectedServer, makeMcpRequest, setError]);
|
|
36
|
+
|
|
37
|
+
const loadPrompts = useCallback(async () => {
|
|
38
|
+
if (!selectedServer) {
|
|
39
|
+
setError('prompts: No server selected');
|
|
40
|
+
setPromptsLoaded(true);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setPromptsLoading(true);
|
|
45
|
+
setError(null);
|
|
46
|
+
try {
|
|
47
|
+
const result = await makeMcpRequest('prompts/list');
|
|
48
|
+
setPrompts(result?.prompts || []);
|
|
49
|
+
setPromptsLoaded(true);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const errorMsg = err.message || 'Failed to load prompts';
|
|
52
|
+
setError(`prompts: ${errorMsg}`);
|
|
53
|
+
setPromptsLoaded(true);
|
|
54
|
+
console.error('Failed to load prompts:', err);
|
|
55
|
+
} finally {
|
|
56
|
+
setPromptsLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, [selectedServer, makeMcpRequest, setError]);
|
|
59
|
+
|
|
60
|
+
const loadResources = useCallback(async () => {
|
|
61
|
+
if (!selectedServer) {
|
|
62
|
+
setError('resources: No server selected');
|
|
63
|
+
setResourcesLoaded(true);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setResourcesLoading(true);
|
|
68
|
+
setError(null);
|
|
69
|
+
try {
|
|
70
|
+
const result = await makeMcpRequest('resources/list');
|
|
71
|
+
setResources(result?.resources || []);
|
|
72
|
+
setResourcesLoaded(true);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const errorMsg = err.message || 'Failed to load resources';
|
|
75
|
+
setError(`resources: ${errorMsg}`);
|
|
76
|
+
setResourcesLoaded(true);
|
|
77
|
+
console.error('Failed to load resources:', err);
|
|
78
|
+
} finally {
|
|
79
|
+
setResourcesLoading(false);
|
|
80
|
+
}
|
|
81
|
+
}, [selectedServer, makeMcpRequest, setError]);
|
|
82
|
+
|
|
83
|
+
const resetData = useCallback(() => {
|
|
84
|
+
setToolsLoaded(false);
|
|
85
|
+
setPromptsLoaded(false);
|
|
86
|
+
setResourcesLoaded(false);
|
|
87
|
+
setTools([]);
|
|
88
|
+
setPrompts([]);
|
|
89
|
+
setResources([]);
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
tools,
|
|
94
|
+
prompts,
|
|
95
|
+
resources,
|
|
96
|
+
toolsLoading,
|
|
97
|
+
promptsLoading,
|
|
98
|
+
resourcesLoading,
|
|
99
|
+
toolsLoaded,
|
|
100
|
+
promptsLoaded,
|
|
101
|
+
resourcesLoaded,
|
|
102
|
+
loadTools,
|
|
103
|
+
loadPrompts,
|
|
104
|
+
loadResources,
|
|
105
|
+
resetData,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useMcpRequest(selectedServer) {
|
|
4
|
+
const [loading, setLoading] = useState(false);
|
|
5
|
+
const [error, setError] = useState(null);
|
|
6
|
+
const [sessionId, setSessionId] = useState(null);
|
|
7
|
+
const sessionIdRef = useRef(sessionId);
|
|
8
|
+
|
|
9
|
+
// Keep ref in sync with state
|
|
10
|
+
sessionIdRef.current = sessionId;
|
|
11
|
+
|
|
12
|
+
const makeMcpRequest = useCallback(
|
|
13
|
+
async (method, params = {}) => {
|
|
14
|
+
if (!selectedServer) {
|
|
15
|
+
throw new Error('No server selected');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setError(null);
|
|
19
|
+
setLoading(true);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
23
|
+
const currentSessionId = sessionIdRef.current;
|
|
24
|
+
if (currentSessionId) {
|
|
25
|
+
headers['Mcp-Session-Id'] = currentSessionId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const response = await fetch('/api/playground/proxy', {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers,
|
|
31
|
+
body: JSON.stringify({ method, params, serverName: selectedServer }),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
|
|
36
|
+
const responseSessionId =
|
|
37
|
+
response.headers.get('Mcp-Session-Id') ||
|
|
38
|
+
response.headers.get('mcp-session-id') ||
|
|
39
|
+
data._sessionId;
|
|
40
|
+
if (responseSessionId && responseSessionId !== currentSessionId) {
|
|
41
|
+
setSessionId(responseSessionId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(data.error?.message || data.message || 'Request failed');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return data.result || data;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
setError(err.message);
|
|
51
|
+
throw err;
|
|
52
|
+
} finally {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[selectedServer]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const resetSession = useCallback(() => {
|
|
60
|
+
setSessionId(null);
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
makeMcpRequest,
|
|
65
|
+
loading,
|
|
66
|
+
error,
|
|
67
|
+
setError,
|
|
68
|
+
resetSession,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useMcpServerStatus() {
|
|
4
|
+
const [serverStatus, setServerStatus] = useState(null);
|
|
5
|
+
const [showLoadingModal, setShowLoadingModal] = useState(false);
|
|
6
|
+
const [availableServers, setAvailableServers] = useState([]);
|
|
7
|
+
const [selectedServer, setSelectedServer] = useState(null);
|
|
8
|
+
|
|
9
|
+
const checkServerStatus = useCallback(async () => {
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch('/api/composite/status');
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
throw new Error('Server not available');
|
|
14
|
+
}
|
|
15
|
+
const data = await res.json();
|
|
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
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
});
|
|
35
|
+
} catch (_err) {
|
|
36
|
+
setServerStatus({ running: false });
|
|
37
|
+
setShowLoadingModal((prevModal) => {
|
|
38
|
+
if (!prevModal) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return prevModal;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const loadAvailableServers = useCallback(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch('/api/composite/servers');
|
|
49
|
+
if (res.ok) {
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
setAvailableServers(data.servers || []);
|
|
52
|
+
setSelectedServer((current) => {
|
|
53
|
+
if (!current && data.servers && data.servers.length > 0) {
|
|
54
|
+
return data.servers[0];
|
|
55
|
+
}
|
|
56
|
+
return current;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('Failed to load servers:', err);
|
|
61
|
+
}
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
// Load servers once on mount
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
loadAvailableServers();
|
|
67
|
+
}, [loadAvailableServers]);
|
|
68
|
+
|
|
69
|
+
// Poll server status every 2 seconds
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
checkServerStatus();
|
|
72
|
+
const interval = setInterval(checkServerStatus, 2000);
|
|
73
|
+
return () => clearInterval(interval);
|
|
74
|
+
}, [checkServerStatus]);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (availableServers.length > 0 && !selectedServer) {
|
|
78
|
+
setSelectedServer(availableServers[0]);
|
|
79
|
+
}
|
|
80
|
+
}, [availableServers, selectedServer]);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
serverStatus,
|
|
84
|
+
showLoadingModal,
|
|
85
|
+
availableServers,
|
|
86
|
+
selectedServer,
|
|
87
|
+
setSelectedServer,
|
|
88
|
+
checkServerStatus,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useMcpDataLoader } from './hooks/useMcpDataLoader';
|
|
3
|
+
import { useMcpRequest } from './hooks/useMcpRequest';
|
|
4
|
+
import { useMcpServerStatus } from './hooks/useMcpServerStatus';
|
|
2
5
|
|
|
3
6
|
export function useMcpPlayground() {
|
|
4
7
|
const [activeSection, setActiveSection] = useState('tools');
|
|
5
|
-
const [tools, setTools] = useState([]);
|
|
6
|
-
const [prompts, setPrompts] = useState([]);
|
|
7
|
-
const [resources, setResources] = useState([]);
|
|
8
|
-
const [loading, setLoading] = useState(false);
|
|
9
|
-
const [error, setError] = useState(null);
|
|
10
8
|
const [selectedTool, setSelectedTool] = useState(null);
|
|
11
9
|
const [toolArgs, setToolArgs] = useState('{}');
|
|
12
10
|
const [toolResult, setToolResult] = useState(null);
|
|
@@ -15,180 +13,128 @@ export function useMcpPlayground() {
|
|
|
15
13
|
const [promptResult, setPromptResult] = useState(null);
|
|
16
14
|
const [selectedResource, setSelectedResource] = useState(null);
|
|
17
15
|
const [resourceResult, setResourceResult] = useState(null);
|
|
18
|
-
const [serverStatus, setServerStatus] = useState(null);
|
|
19
|
-
const [sessionId, setSessionId] = useState(null);
|
|
20
|
-
const [showLoadingModal, setShowLoadingModal] = useState(false);
|
|
21
|
-
const [toolsLoading, setToolsLoading] = useState(false);
|
|
22
|
-
const [promptsLoading, setPromptsLoading] = useState(false);
|
|
23
|
-
const [resourcesLoading, setResourcesLoading] = useState(false);
|
|
24
|
-
const [toolsLoaded, setToolsLoaded] = useState(false);
|
|
25
|
-
const [promptsLoaded, setPromptsLoaded] = useState(false);
|
|
26
|
-
const [resourcesLoaded, setResourcesLoaded] = useState(false);
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}, []);
|
|
17
|
+
const { serverStatus, showLoadingModal, availableServers, selectedServer, setSelectedServer } =
|
|
18
|
+
useMcpServerStatus();
|
|
19
|
+
|
|
20
|
+
const { makeMcpRequest, loading, error, setError, resetSession } = useMcpRequest(selectedServer);
|
|
33
21
|
|
|
22
|
+
const {
|
|
23
|
+
tools,
|
|
24
|
+
prompts,
|
|
25
|
+
resources,
|
|
26
|
+
toolsLoading,
|
|
27
|
+
promptsLoading,
|
|
28
|
+
resourcesLoading,
|
|
29
|
+
toolsLoaded,
|
|
30
|
+
promptsLoaded,
|
|
31
|
+
resourcesLoaded,
|
|
32
|
+
loadTools,
|
|
33
|
+
loadPrompts,
|
|
34
|
+
loadResources,
|
|
35
|
+
resetData,
|
|
36
|
+
} = useMcpDataLoader(makeMcpRequest, selectedServer, setError);
|
|
37
|
+
|
|
38
|
+
const activeSectionRef = useRef(activeSection);
|
|
39
|
+
activeSectionRef.current = activeSection;
|
|
40
|
+
|
|
41
|
+
// Reset and reload data when server changes (not when section changes)
|
|
34
42
|
useEffect(() => {
|
|
35
|
-
if (serverStatus?.running
|
|
36
|
-
|
|
37
|
-
loadTools();
|
|
38
|
-
}, 2000);
|
|
39
|
-
return () => clearTimeout(timer);
|
|
43
|
+
if (!selectedServer || !serverStatus?.running) {
|
|
44
|
+
return;
|
|
40
45
|
}
|
|
41
|
-
}, [serverStatus?.running]);
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
resetData();
|
|
48
|
+
setSelectedTool(null);
|
|
49
|
+
setSelectedPrompt(null);
|
|
50
|
+
setSelectedResource(null);
|
|
51
|
+
setToolResult(null);
|
|
52
|
+
setPromptResult(null);
|
|
53
|
+
setResourceResult(null);
|
|
54
|
+
setToolArgs('{}');
|
|
55
|
+
setPromptArgs('{}');
|
|
56
|
+
resetSession();
|
|
57
|
+
setError(null);
|
|
45
58
|
|
|
46
59
|
const timer = setTimeout(() => {
|
|
47
|
-
|
|
60
|
+
const currentSection = activeSectionRef.current;
|
|
61
|
+
if (currentSection === 'tools') {
|
|
48
62
|
loadTools();
|
|
49
|
-
} else if (
|
|
63
|
+
} else if (currentSection === 'prompts') {
|
|
50
64
|
loadPrompts();
|
|
51
|
-
} else if (
|
|
65
|
+
} else if (currentSection === 'resources') {
|
|
52
66
|
loadResources();
|
|
53
67
|
}
|
|
54
68
|
}, 100);
|
|
55
69
|
|
|
56
70
|
return () => clearTimeout(timer);
|
|
57
71
|
}, [
|
|
58
|
-
|
|
72
|
+
selectedServer,
|
|
59
73
|
serverStatus?.running,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
resetData,
|
|
75
|
+
loadTools,
|
|
76
|
+
loadPrompts,
|
|
77
|
+
loadResources,
|
|
78
|
+
resetSession,
|
|
79
|
+
setError,
|
|
66
80
|
]);
|
|
67
81
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
throw new Error('Server not available');
|
|
73
|
-
}
|
|
74
|
-
const data = await res.json();
|
|
75
|
-
const wasRunning = serverStatus?.running;
|
|
76
|
-
setServerStatus(data);
|
|
77
|
-
|
|
78
|
-
if (!data.running) {
|
|
79
|
-
if (!showLoadingModal || wasRunning) {
|
|
80
|
-
setShowLoadingModal(true);
|
|
81
|
-
}
|
|
82
|
-
} else if (data.running && showLoadingModal) {
|
|
83
|
-
setShowLoadingModal(false);
|
|
84
|
-
}
|
|
85
|
-
} catch (err) {
|
|
86
|
-
// Silently handle connection errors - server is not running
|
|
87
|
-
setServerStatus({ running: false });
|
|
88
|
-
if (!showLoadingModal) {
|
|
89
|
-
setShowLoadingModal(true);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const makeMcpRequest = async (method, params = {}) => {
|
|
95
|
-
setError(null);
|
|
96
|
-
setLoading(true);
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
100
|
-
if (sessionId) {
|
|
101
|
-
headers['Mcp-Session-Id'] = sessionId;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const response = await fetch('/api/playground/proxy', {
|
|
105
|
-
method: 'POST',
|
|
106
|
-
headers,
|
|
107
|
-
body: JSON.stringify({ method, params }),
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const data = await response.json();
|
|
111
|
-
|
|
112
|
-
const responseSessionId =
|
|
113
|
-
response.headers.get('Mcp-Session-Id') ||
|
|
114
|
-
response.headers.get('mcp-session-id') ||
|
|
115
|
-
data._sessionId;
|
|
116
|
-
if (responseSessionId && responseSessionId !== sessionId) {
|
|
117
|
-
setSessionId(responseSessionId);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
throw new Error(data.error?.message || data.message || 'Request failed');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return data.result || data;
|
|
125
|
-
} catch (err) {
|
|
126
|
-
setError(err.message);
|
|
127
|
-
throw err;
|
|
128
|
-
} finally {
|
|
129
|
-
setLoading(false);
|
|
82
|
+
// Load data when section changes (only if not already loaded)
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!serverStatus?.running || !selectedServer) {
|
|
85
|
+
return;
|
|
130
86
|
}
|
|
131
|
-
};
|
|
132
87
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
setToolsLoaded(true);
|
|
140
|
-
} catch (err) {
|
|
141
|
-
const errorMsg = err.message || 'Failed to load tools';
|
|
142
|
-
setError(`tools: ${errorMsg}`);
|
|
143
|
-
setToolsLoaded(true);
|
|
144
|
-
console.error('Failed to load tools:', err);
|
|
145
|
-
} finally {
|
|
146
|
-
setToolsLoading(false);
|
|
88
|
+
// Only load if we haven't loaded this section yet and we're not currently loading
|
|
89
|
+
if (activeSection === 'tools' && !toolsLoaded && !toolsLoading) {
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
loadTools();
|
|
92
|
+
}, 100);
|
|
93
|
+
return () => clearTimeout(timer);
|
|
147
94
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
const result = await makeMcpRequest('prompts/list');
|
|
155
|
-
setPrompts(result?.prompts || []);
|
|
156
|
-
setPromptsLoaded(true);
|
|
157
|
-
} catch (err) {
|
|
158
|
-
const errorMsg = err.message || 'Failed to load prompts';
|
|
159
|
-
setError(`prompts: ${errorMsg}`);
|
|
160
|
-
setPromptsLoaded(true);
|
|
161
|
-
console.error('Failed to load prompts:', err);
|
|
162
|
-
} finally {
|
|
163
|
-
setPromptsLoading(false);
|
|
95
|
+
if (activeSection === 'prompts' && !promptsLoaded && !promptsLoading) {
|
|
96
|
+
const timer = setTimeout(() => {
|
|
97
|
+
loadPrompts();
|
|
98
|
+
}, 100);
|
|
99
|
+
return () => clearTimeout(timer);
|
|
164
100
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const result = await makeMcpRequest('resources/list');
|
|
172
|
-
setResources(result?.resources || []);
|
|
173
|
-
setResourcesLoaded(true);
|
|
174
|
-
} catch (err) {
|
|
175
|
-
const errorMsg = err.message || 'Failed to load resources';
|
|
176
|
-
setError(`resources: ${errorMsg}`);
|
|
177
|
-
setResourcesLoaded(true);
|
|
178
|
-
console.error('Failed to load resources:', err);
|
|
179
|
-
} finally {
|
|
180
|
-
setResourcesLoading(false);
|
|
101
|
+
if (activeSection === 'resources' && !resourcesLoaded && !resourcesLoading) {
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
loadResources();
|
|
104
|
+
}, 100);
|
|
105
|
+
return () => clearTimeout(timer);
|
|
181
106
|
}
|
|
182
|
-
}
|
|
107
|
+
}, [
|
|
108
|
+
activeSection,
|
|
109
|
+
serverStatus?.running,
|
|
110
|
+
selectedServer,
|
|
111
|
+
toolsLoaded,
|
|
112
|
+
toolsLoading,
|
|
113
|
+
promptsLoaded,
|
|
114
|
+
promptsLoading,
|
|
115
|
+
resourcesLoaded,
|
|
116
|
+
resourcesLoading,
|
|
117
|
+
loadTools,
|
|
118
|
+
loadPrompts,
|
|
119
|
+
loadResources,
|
|
120
|
+
]);
|
|
183
121
|
|
|
184
122
|
const handleCallTool = async () => {
|
|
185
|
-
if (!selectedTool)
|
|
123
|
+
if (!selectedTool) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
186
126
|
|
|
187
127
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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) {
|
|
192
138
|
setError('Invalid JSON in arguments');
|
|
193
139
|
return;
|
|
194
140
|
}
|
|
@@ -205,13 +151,21 @@ export function useMcpPlayground() {
|
|
|
205
151
|
};
|
|
206
152
|
|
|
207
153
|
const handleGetPrompt = async () => {
|
|
208
|
-
if (!selectedPrompt)
|
|
154
|
+
if (!selectedPrompt) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
209
157
|
|
|
210
158
|
try {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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) {
|
|
215
169
|
setError('Invalid JSON in arguments');
|
|
216
170
|
return;
|
|
217
171
|
}
|
|
@@ -228,7 +182,9 @@ export function useMcpPlayground() {
|
|
|
228
182
|
};
|
|
229
183
|
|
|
230
184
|
const handleReadResource = async () => {
|
|
231
|
-
if (!selectedResource)
|
|
185
|
+
if (!selectedResource) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
232
188
|
|
|
233
189
|
try {
|
|
234
190
|
const result = await makeMcpRequest('resources/read', {
|
|
@@ -276,5 +232,8 @@ export function useMcpPlayground() {
|
|
|
276
232
|
handleCallTool,
|
|
277
233
|
handleGetPrompt,
|
|
278
234
|
handleReadResource,
|
|
235
|
+
availableServers,
|
|
236
|
+
selectedServer,
|
|
237
|
+
setSelectedServer,
|
|
279
238
|
};
|
|
280
239
|
}
|