@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,12 +1,12 @@
|
|
|
1
|
-
import { serializeBigInt } from '../utils/serialization.js';
|
|
2
1
|
import { queryConversations } from 'mcp-shark-common/db/query.js';
|
|
2
|
+
import { serializeBigInt } from '../utils/serialization.js';
|
|
3
3
|
|
|
4
4
|
export function createConversationsRoutes(db) {
|
|
5
5
|
const router = {};
|
|
6
6
|
|
|
7
7
|
router.getConversations = (req, res) => {
|
|
8
|
-
const limit = parseInt(req.query.limit) || 1000;
|
|
9
|
-
const offset = parseInt(req.query.offset) || 0;
|
|
8
|
+
const limit = Number.parseInt(req.query.limit) || 1000;
|
|
9
|
+
const offset = Number.parseInt(req.query.offset) || 0;
|
|
10
10
|
const filters = {
|
|
11
11
|
sessionId: req.query.sessionId || null,
|
|
12
12
|
method: req.query.method || null,
|
package/ui/server/routes/help.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readHelpState, writeHelpState } from 'mcp-shark-common/configs/index.js
|
|
|
3
3
|
export function createHelpRoutes() {
|
|
4
4
|
const router = {};
|
|
5
5
|
|
|
6
|
-
router.getState = (
|
|
6
|
+
router.getState = (_req, res) => {
|
|
7
7
|
const state = readHelpState();
|
|
8
8
|
res.json({
|
|
9
9
|
dismissed: state.dismissed || false,
|
|
@@ -25,7 +25,7 @@ export function createHelpRoutes() {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
router.reset = (
|
|
28
|
+
router.reset = (_req, res) => {
|
|
29
29
|
const state = {
|
|
30
30
|
dismissed: false,
|
|
31
31
|
tourCompleted: false,
|
package/ui/server/routes/logs.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
export function createLogsRoutes(mcpSharkLogs,
|
|
1
|
+
export function createLogsRoutes(mcpSharkLogs, _broadcastLogUpdate) {
|
|
2
2
|
const router = {};
|
|
3
3
|
|
|
4
4
|
router.getLogs = (req, res) => {
|
|
5
|
-
const limit = parseInt(req.query.limit) || 1000;
|
|
6
|
-
const offset = parseInt(req.query.offset) || 0;
|
|
5
|
+
const limit = Number.parseInt(req.query.limit) || 1000;
|
|
6
|
+
const offset = Number.parseInt(req.query.offset) || 0;
|
|
7
7
|
const logs = [...mcpSharkLogs].reverse().slice(offset, offset + limit);
|
|
8
8
|
res.json(logs);
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
router.clearLogs = (
|
|
11
|
+
router.clearLogs = (_req, res) => {
|
|
12
12
|
mcpSharkLogs.length = 0;
|
|
13
13
|
res.json({ success: true, message: 'Logs cleared' });
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
router.exportLogs = (
|
|
16
|
+
router.exportLogs = (_req, res) => {
|
|
17
17
|
try {
|
|
18
18
|
const logsText = mcpSharkLogs
|
|
19
19
|
.map((log) => `[${log.timestamp}] [${log.type.toUpperCase()}] ${log.line}`)
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
2
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import logger from '../utils/logger.js';
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
+
const MCP_SERVER_BASE_URL = 'http://localhost:9851/mcp';
|
|
5
6
|
|
|
6
|
-
// Store client connections per session
|
|
7
|
+
// Store client connections per server and session
|
|
7
8
|
const clientSessions = new Map();
|
|
8
9
|
|
|
10
|
+
function getSessionKey(serverName, sessionId) {
|
|
11
|
+
return `${serverName}:${sessionId}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
export function createPlaygroundRoutes() {
|
|
10
15
|
const router = {};
|
|
11
16
|
|
|
12
|
-
// Get or create client for a session
|
|
13
|
-
async function getClient(sessionId) {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// Get or create client for a session and server
|
|
18
|
+
async function getClient(serverName, sessionId) {
|
|
19
|
+
const sessionKey = getSessionKey(serverName, sessionId);
|
|
20
|
+
if (clientSessions.has(sessionKey)) {
|
|
21
|
+
return clientSessions.get(sessionKey);
|
|
16
22
|
}
|
|
17
23
|
|
|
24
|
+
if (!serverName) {
|
|
25
|
+
throw new Error('Server name is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const mcpServerUrl = `${MCP_SERVER_BASE_URL}/${encodeURIComponent(serverName)}`;
|
|
29
|
+
|
|
18
30
|
const client = new Client(
|
|
19
31
|
{ name: 'mcp-shark-playground', version: '1.0.0' },
|
|
20
32
|
{
|
|
@@ -26,7 +38,7 @@ export function createPlaygroundRoutes() {
|
|
|
26
38
|
}
|
|
27
39
|
);
|
|
28
40
|
|
|
29
|
-
const transport = new StreamableHTTPClientTransport(new URL(
|
|
41
|
+
const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl));
|
|
30
42
|
await client.connect(transport);
|
|
31
43
|
|
|
32
44
|
const clientWrapper = {
|
|
@@ -38,13 +50,13 @@ export function createPlaygroundRoutes() {
|
|
|
38
50
|
},
|
|
39
51
|
};
|
|
40
52
|
|
|
41
|
-
clientSessions.set(
|
|
53
|
+
clientSessions.set(sessionKey, clientWrapper);
|
|
42
54
|
return clientWrapper;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
router.proxyRequest = async (req, res) => {
|
|
46
58
|
try {
|
|
47
|
-
const { method, params } = req.body;
|
|
59
|
+
const { method, params, serverName } = req.body;
|
|
48
60
|
|
|
49
61
|
if (!method) {
|
|
50
62
|
return res.status(400).json({
|
|
@@ -53,64 +65,68 @@ export function createPlaygroundRoutes() {
|
|
|
53
65
|
});
|
|
54
66
|
}
|
|
55
67
|
|
|
68
|
+
if (!serverName) {
|
|
69
|
+
return res.status(400).json({
|
|
70
|
+
error: 'Invalid request',
|
|
71
|
+
message: 'serverName field is required',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
// Get or create session ID
|
|
57
76
|
const sessionId =
|
|
58
77
|
req.headers['mcp-session-id'] ||
|
|
59
78
|
req.headers['x-mcp-session-id'] ||
|
|
60
79
|
`playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
61
80
|
|
|
62
|
-
const { client } = await getClient(sessionId);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
const { client } = await getClient(serverName, sessionId);
|
|
82
|
+
|
|
83
|
+
const executeMethod = async () => {
|
|
84
|
+
switch (method) {
|
|
85
|
+
case 'tools/list':
|
|
86
|
+
return await client.listTools();
|
|
87
|
+
case 'tools/call':
|
|
88
|
+
if (!params?.name) {
|
|
89
|
+
return res.status(400).json({
|
|
90
|
+
error: 'Invalid request',
|
|
91
|
+
message: 'Tool name is required',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return await client.callTool({
|
|
95
|
+
name: params.name,
|
|
96
|
+
arguments: params.arguments || {},
|
|
74
97
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
error: 'Invalid request',
|
|
88
|
-
message: 'Prompt name is required',
|
|
98
|
+
case 'prompts/list':
|
|
99
|
+
return await client.listPrompts();
|
|
100
|
+
case 'prompts/get':
|
|
101
|
+
if (!params?.name) {
|
|
102
|
+
return res.status(400).json({
|
|
103
|
+
error: 'Invalid request',
|
|
104
|
+
message: 'Prompt name is required',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return await client.getPrompt({
|
|
108
|
+
name: params.name,
|
|
109
|
+
arguments: params.arguments || {},
|
|
89
110
|
});
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
case 'resources/list':
|
|
112
|
+
return await client.listResources();
|
|
113
|
+
case 'resources/read':
|
|
114
|
+
if (!params?.uri) {
|
|
115
|
+
return res.status(400).json({
|
|
116
|
+
error: 'Invalid request',
|
|
117
|
+
message: 'Resource URI is required',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return await client.readResource({ uri: params.uri });
|
|
121
|
+
default:
|
|
101
122
|
return res.status(400).json({
|
|
102
|
-
error: '
|
|
103
|
-
message:
|
|
123
|
+
error: 'Unsupported method',
|
|
124
|
+
message: `Method ${method} is not supported`,
|
|
104
125
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return res.status(400).json({
|
|
110
|
-
error: 'Unsupported method',
|
|
111
|
-
message: `Method ${method} is not supported`,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = await executeMethod();
|
|
114
130
|
|
|
115
131
|
// Return session ID in response
|
|
116
132
|
res.setHeader('Mcp-Session-Id', sessionId);
|
|
@@ -119,7 +135,7 @@ export function createPlaygroundRoutes() {
|
|
|
119
135
|
_sessionId: sessionId,
|
|
120
136
|
});
|
|
121
137
|
} catch (error) {
|
|
122
|
-
|
|
138
|
+
logger.error({ error: error.message }, 'Error in playground proxy');
|
|
123
139
|
|
|
124
140
|
// Check if it's a connection error
|
|
125
141
|
if (error.message?.includes('ECONNREFUSED') || error.message?.includes('connect')) {
|
|
@@ -140,10 +156,23 @@ export function createPlaygroundRoutes() {
|
|
|
140
156
|
// Cleanup endpoint to close client connections
|
|
141
157
|
router.cleanup = async (req, res) => {
|
|
142
158
|
const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'];
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
const { serverName } = req.body || {};
|
|
160
|
+
|
|
161
|
+
if (serverName && sessionId) {
|
|
162
|
+
const sessionKey = getSessionKey(serverName, sessionId);
|
|
163
|
+
if (clientSessions.has(sessionKey)) {
|
|
164
|
+
const clientWrapper = clientSessions.get(sessionKey);
|
|
165
|
+
await clientWrapper.close();
|
|
166
|
+
clientSessions.delete(sessionKey);
|
|
167
|
+
}
|
|
168
|
+
} else if (sessionId) {
|
|
169
|
+
// Cleanup all sessions for this sessionId across all servers
|
|
170
|
+
for (const [key, clientWrapper] of clientSessions.entries()) {
|
|
171
|
+
if (key.endsWith(`:${sessionId}`)) {
|
|
172
|
+
await clientWrapper.close();
|
|
173
|
+
clientSessions.delete(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
147
176
|
}
|
|
148
177
|
res.json({ success: true });
|
|
149
178
|
};
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { serializeBigInt } from '../utils/serialization.js';
|
|
2
1
|
import { queryRequests } from 'mcp-shark-common/db/query.js';
|
|
2
|
+
import logger from '../utils/logger.js';
|
|
3
|
+
import { serializeBigInt } from '../utils/serialization.js';
|
|
4
|
+
|
|
5
|
+
const sanitizeSearch = (value) => {
|
|
6
|
+
if (value !== undefined && value !== null) {
|
|
7
|
+
const trimmed = String(value).trim();
|
|
8
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
};
|
|
3
12
|
|
|
4
13
|
export function createRequestsRoutes(db) {
|
|
5
14
|
const router = {};
|
|
6
15
|
|
|
7
16
|
router.getRequests = (req, res) => {
|
|
8
17
|
try {
|
|
9
|
-
const limit = parseInt(req.query.limit) || 1000;
|
|
10
|
-
const offset = parseInt(req.query.offset) || 0;
|
|
18
|
+
const limit = Number.parseInt(req.query.limit) || 1000;
|
|
19
|
+
const offset = Number.parseInt(req.query.offset) || 0;
|
|
11
20
|
|
|
12
21
|
// Sanitize search parameter - convert empty strings to null
|
|
13
|
-
|
|
14
|
-
if (search !== undefined && search !== null) {
|
|
15
|
-
search = String(search).trim();
|
|
16
|
-
search = search.length > 0 ? search : null;
|
|
17
|
-
} else {
|
|
18
|
-
search = null;
|
|
19
|
-
}
|
|
22
|
+
const search = sanitizeSearch(req.query.search);
|
|
20
23
|
|
|
21
24
|
// Build filters object, ensuring all values are properly typed
|
|
22
25
|
const filters = {
|
|
@@ -24,7 +27,7 @@ export function createRequestsRoutes(db) {
|
|
|
24
27
|
direction: (req.query.direction && String(req.query.direction).trim()) || null,
|
|
25
28
|
method: (req.query.method && String(req.query.method).trim()) || null,
|
|
26
29
|
jsonrpcMethod: (req.query.jsonrpcMethod && String(req.query.jsonrpcMethod).trim()) || null,
|
|
27
|
-
statusCode: req.query.statusCode ? parseInt(req.query.statusCode) : null,
|
|
30
|
+
statusCode: req.query.statusCode ? Number.parseInt(req.query.statusCode) : null,
|
|
28
31
|
jsonrpcId: (req.query.jsonrpcId && String(req.query.jsonrpcId).trim()) || null,
|
|
29
32
|
search: search,
|
|
30
33
|
serverName: (req.query.serverName && String(req.query.serverName).trim()) || null,
|
|
@@ -44,21 +47,21 @@ export function createRequestsRoutes(db) {
|
|
|
44
47
|
const requests = queryRequests(db, filters);
|
|
45
48
|
res.json(serializeBigInt(requests));
|
|
46
49
|
} catch (error) {
|
|
47
|
-
|
|
50
|
+
logger.error({ error: error.message }, 'Error in getRequests');
|
|
48
51
|
res.status(500).json({ error: 'Failed to query requests', details: error.message });
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
router.getRequest = (req, res) => {
|
|
53
56
|
const stmt = db.prepare('SELECT * FROM packets WHERE frame_number = ?');
|
|
54
|
-
const request = stmt.get(parseInt(req.params.frameNumber));
|
|
57
|
+
const request = stmt.get(Number.parseInt(req.params.frameNumber));
|
|
55
58
|
if (!request) {
|
|
56
59
|
return res.status(404).json({ error: 'Request not found' });
|
|
57
60
|
}
|
|
58
61
|
res.json(serializeBigInt(request));
|
|
59
62
|
};
|
|
60
63
|
|
|
61
|
-
router.clearRequests = (
|
|
64
|
+
router.clearRequests = (_req, res) => {
|
|
62
65
|
try {
|
|
63
66
|
// Disable foreign key constraints temporarily to avoid constraint violations
|
|
64
67
|
db.exec('PRAGMA foreign_keys = OFF');
|
|
@@ -87,19 +90,25 @@ export function createRequestsRoutes(db) {
|
|
|
87
90
|
];
|
|
88
91
|
|
|
89
92
|
// Delete from each table that exists
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
const clearResults = trafficTables.reduce(
|
|
94
|
+
(acc, table) => {
|
|
95
|
+
if (existingTables.includes(table)) {
|
|
96
|
+
try {
|
|
97
|
+
db.exec(`DELETE FROM ${table}`);
|
|
98
|
+
return {
|
|
99
|
+
count: acc.count + 1,
|
|
100
|
+
tables: [...acc.tables, table],
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
logger.warn({ table, error: err.message }, 'Error clearing table');
|
|
104
|
+
}
|
|
100
105
|
}
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
return acc;
|
|
107
|
+
},
|
|
108
|
+
{ count: 0, tables: [] }
|
|
109
|
+
);
|
|
110
|
+
const clearedCount = clearResults.count;
|
|
111
|
+
const clearedTables = clearResults.tables;
|
|
103
112
|
|
|
104
113
|
// Re-enable foreign key constraints
|
|
105
114
|
db.exec('PRAGMA foreign_keys = ON');
|
|
@@ -112,10 +121,10 @@ export function createRequestsRoutes(db) {
|
|
|
112
121
|
// Make sure to re-enable foreign keys even if there's an error
|
|
113
122
|
try {
|
|
114
123
|
db.exec('PRAGMA foreign_keys = ON');
|
|
115
|
-
} catch (
|
|
124
|
+
} catch (_e) {
|
|
116
125
|
// Ignore
|
|
117
126
|
}
|
|
118
|
-
|
|
127
|
+
logger.error({ error: error.message }, 'Error clearing requests');
|
|
119
128
|
res.status(500).json({ error: 'Failed to clear traffic', details: error.message });
|
|
120
129
|
}
|
|
121
130
|
};
|
|
@@ -123,20 +132,14 @@ export function createRequestsRoutes(db) {
|
|
|
123
132
|
router.exportRequests = (req, res) => {
|
|
124
133
|
try {
|
|
125
134
|
// Sanitize search parameter - convert empty strings to null
|
|
126
|
-
|
|
127
|
-
if (search !== undefined && search !== null) {
|
|
128
|
-
search = String(search).trim();
|
|
129
|
-
search = search.length > 0 ? search : null;
|
|
130
|
-
} else {
|
|
131
|
-
search = null;
|
|
132
|
-
}
|
|
135
|
+
const search = sanitizeSearch(req.query.search);
|
|
133
136
|
|
|
134
137
|
const filters = {
|
|
135
138
|
sessionId: req.query.sessionId || null,
|
|
136
139
|
direction: req.query.direction || null,
|
|
137
140
|
method: req.query.method || null,
|
|
138
141
|
jsonrpcMethod: req.query.jsonrpcMethod || null,
|
|
139
|
-
statusCode: req.query.statusCode ? parseInt(req.query.statusCode) : null,
|
|
142
|
+
statusCode: req.query.statusCode ? Number.parseInt(req.query.statusCode) : null,
|
|
140
143
|
jsonrpcId: req.query.jsonrpcId || null,
|
|
141
144
|
search: search,
|
|
142
145
|
serverName: req.query.serverName || null,
|
|
@@ -149,78 +152,79 @@ export function createRequestsRoutes(db) {
|
|
|
149
152
|
const requests = queryRequests(db, filters);
|
|
150
153
|
const format = req.query.format || 'json';
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
content = JSON.stringify(serializeBigInt(requests), null, 2);
|
|
221
|
-
contentType
|
|
222
|
-
|
|
223
|
-
|
|
155
|
+
const formatExport = (requests, format) => {
|
|
156
|
+
if (format === 'csv') {
|
|
157
|
+
const headers = [
|
|
158
|
+
'Frame',
|
|
159
|
+
'Time',
|
|
160
|
+
'Source',
|
|
161
|
+
'Destination',
|
|
162
|
+
'Protocol',
|
|
163
|
+
'Length',
|
|
164
|
+
'Method',
|
|
165
|
+
'Status',
|
|
166
|
+
'JSON-RPC Method',
|
|
167
|
+
'Session ID',
|
|
168
|
+
'Server Name',
|
|
169
|
+
];
|
|
170
|
+
const rows = requests.map((req) => [
|
|
171
|
+
req.frame_number || '',
|
|
172
|
+
req.timestamp_iso || '',
|
|
173
|
+
req.request?.host || '',
|
|
174
|
+
req.request?.host || '',
|
|
175
|
+
'HTTP',
|
|
176
|
+
req.length || '',
|
|
177
|
+
req.request?.method || '',
|
|
178
|
+
req.response?.status_code || '',
|
|
179
|
+
req.jsonrpc_method || '',
|
|
180
|
+
req.session_id || '',
|
|
181
|
+
req.server_name || '',
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
const content = [
|
|
185
|
+
headers.join(','),
|
|
186
|
+
...rows.map((row) =>
|
|
187
|
+
row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')
|
|
188
|
+
),
|
|
189
|
+
].join('\n');
|
|
190
|
+
return { content, contentType: 'text/csv', extension: 'csv' };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (format === 'txt') {
|
|
194
|
+
const content = requests
|
|
195
|
+
.map((req, idx) => {
|
|
196
|
+
const lines = [
|
|
197
|
+
`=== Request/Response #${idx + 1} (Frame ${req.frame_number || 'N/A'}) ===`,
|
|
198
|
+
`Time: ${req.timestamp_iso || 'N/A'}`,
|
|
199
|
+
`Session ID: ${req.session_id || 'N/A'}`,
|
|
200
|
+
`Server: ${req.server_name || 'N/A'}`,
|
|
201
|
+
`Direction: ${req.direction || 'N/A'}`,
|
|
202
|
+
`Method: ${req.request?.method || 'N/A'}`,
|
|
203
|
+
`Status: ${req.response?.status_code || 'N/A'}`,
|
|
204
|
+
`JSON-RPC Method: ${req.jsonrpc_method || 'N/A'}`,
|
|
205
|
+
`JSON-RPC ID: ${req.jsonrpc_id || 'N/A'}`,
|
|
206
|
+
`Length: ${req.length || 0} bytes`,
|
|
207
|
+
'',
|
|
208
|
+
'Request:',
|
|
209
|
+
JSON.stringify(req.request || {}, null, 2),
|
|
210
|
+
'',
|
|
211
|
+
'Response:',
|
|
212
|
+
JSON.stringify(req.response || {}, null, 2),
|
|
213
|
+
'',
|
|
214
|
+
'---',
|
|
215
|
+
'',
|
|
216
|
+
];
|
|
217
|
+
return lines.join('\n');
|
|
218
|
+
})
|
|
219
|
+
.join('\n');
|
|
220
|
+
return { content, contentType: 'text/plain', extension: 'txt' };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const content = JSON.stringify(serializeBigInt(requests), null, 2);
|
|
224
|
+
return { content, contentType: 'application/json', extension: 'json' };
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const { content, contentType, extension } = formatExport(requests, format);
|
|
224
228
|
|
|
225
229
|
const filename = `mcp-shark-traffic-${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`;
|
|
226
230
|
res.setHeader('Content-Type', contentType);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { getSessionRequests, getSessions } from 'mcp-shark-common/db/query.js';
|
|
1
2
|
import { serializeBigInt } from '../utils/serialization.js';
|
|
2
|
-
import { getSessions, getSessionRequests } from 'mcp-shark-common/db/query.js';
|
|
3
3
|
|
|
4
4
|
export function createSessionsRoutes(db) {
|
|
5
5
|
const router = {};
|
|
6
6
|
|
|
7
7
|
router.getSessions = (req, res) => {
|
|
8
|
-
const limit = parseInt(req.query.limit) || 1000;
|
|
9
|
-
const offset = parseInt(req.query.offset) || 0;
|
|
8
|
+
const limit = Number.parseInt(req.query.limit) || 1000;
|
|
9
|
+
const offset = Number.parseInt(req.query.offset) || 0;
|
|
10
10
|
const filters = {
|
|
11
11
|
startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
|
|
12
12
|
endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
|
|
@@ -18,7 +18,7 @@ export function createSessionsRoutes(db) {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
router.getSessionRequests = (req, res) => {
|
|
21
|
-
const limit = parseInt(req.query.limit) || 10000;
|
|
21
|
+
const limit = Number.parseInt(req.query.limit) || 10000;
|
|
22
22
|
const requests = getSessionRequests(db, req.params.sessionId, limit);
|
|
23
23
|
res.json(serializeBigInt(requests));
|
|
24
24
|
};
|