@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
package/bin/mcp-shark.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { dirname, join, resolve } from 'node:path';
|
|
6
4
|
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import open from 'open';
|
|
9
|
+
import logger from '../shared/logger.js';
|
|
9
10
|
|
|
10
11
|
const SERVER_URL = 'http://localhost:9853';
|
|
11
12
|
const BROWSER_OPEN_DELAY = 1000;
|
|
@@ -34,7 +35,7 @@ function displayWelcomeBanner() {
|
|
|
34
35
|
Version: ${version} | Homepage: https://mcpshark.sh
|
|
35
36
|
`;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
logger.log(banner);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const uiDir = join(rootDir, 'ui');
|
|
@@ -70,25 +71,25 @@ function runCommand(command, args, options) {
|
|
|
70
71
|
* Install dependencies in the root directory
|
|
71
72
|
*/
|
|
72
73
|
async function installDependencies() {
|
|
73
|
-
|
|
74
|
+
logger.info('Installing dependencies...');
|
|
74
75
|
try {
|
|
75
76
|
await runCommand('npm', ['install'], { cwd: rootDir });
|
|
76
|
-
|
|
77
|
+
logger.info('Dependencies installed successfully!\n');
|
|
77
78
|
} catch (error) {
|
|
78
|
-
|
|
79
|
+
logger.error({ error: error.message }, 'Failed to install dependencies');
|
|
79
80
|
process.exit(1);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
/**
|
|
84
|
-
*
|
|
85
|
+
* Validate that UI dist directory exists
|
|
85
86
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
function validateUIBuilt() {
|
|
88
|
+
if (!existsSync(distDir)) {
|
|
89
|
+
logger.error(
|
|
90
|
+
'Error: UI build not found. The package should include pre-built UI files.\n' +
|
|
91
|
+
'If you are developing, run: npm run build:ui'
|
|
92
|
+
);
|
|
92
93
|
process.exit(1);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
@@ -105,9 +106,9 @@ async function openBrowser() {
|
|
|
105
106
|
* Start the UI server
|
|
106
107
|
*/
|
|
107
108
|
async function startServer(shouldOpenBrowser = false) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
logger.info('Starting MCP Shark UI server...');
|
|
110
|
+
logger.info(`Open ${SERVER_URL} in your browser`);
|
|
111
|
+
logger.info('Press Ctrl+C to stop\n');
|
|
111
112
|
|
|
112
113
|
const serverProcess = spawn('node', ['server.js'], {
|
|
113
114
|
cwd: uiDir,
|
|
@@ -119,12 +120,12 @@ async function startServer(shouldOpenBrowser = false) {
|
|
|
119
120
|
await openBrowser();
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
const shutdownState = { isShuttingDown: false };
|
|
123
124
|
|
|
124
125
|
serverProcess.on('close', (code) => {
|
|
125
|
-
if (!isShuttingDown) {
|
|
126
|
+
if (!shutdownState.isShuttingDown) {
|
|
126
127
|
if (code !== 0 && code !== null) {
|
|
127
|
-
|
|
128
|
+
logger.error({ code }, 'Server exited with code');
|
|
128
129
|
process.exit(code);
|
|
129
130
|
}
|
|
130
131
|
} else {
|
|
@@ -134,17 +135,19 @@ async function startServer(shouldOpenBrowser = false) {
|
|
|
134
135
|
|
|
135
136
|
// Handle process termination
|
|
136
137
|
const shutdown = async (signal) => {
|
|
137
|
-
if (isShuttingDown)
|
|
138
|
-
|
|
138
|
+
if (shutdownState.isShuttingDown) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
shutdownState.isShuttingDown = true;
|
|
139
142
|
|
|
140
|
-
|
|
143
|
+
logger.info('Shutting down...');
|
|
141
144
|
|
|
142
145
|
// Send signal to child process
|
|
143
146
|
serverProcess.kill(signal);
|
|
144
147
|
|
|
145
148
|
// Wait for child process to exit, with timeout
|
|
146
149
|
const timeout = setTimeout(() => {
|
|
147
|
-
|
|
150
|
+
logger.info('Forcefully terminating server process...');
|
|
148
151
|
serverProcess.kill('SIGKILL');
|
|
149
152
|
process.exit(1);
|
|
150
153
|
}, 5000);
|
|
@@ -164,7 +167,7 @@ async function startServer(shouldOpenBrowser = false) {
|
|
|
164
167
|
*/
|
|
165
168
|
function validateDirectories() {
|
|
166
169
|
if (!existsSync(uiDir)) {
|
|
167
|
-
|
|
170
|
+
logger.error('Error: UI directory not found. Please ensure you are in the correct directory.');
|
|
168
171
|
process.exit(1);
|
|
169
172
|
}
|
|
170
173
|
}
|
|
@@ -178,15 +181,6 @@ async function ensureDependencies() {
|
|
|
178
181
|
}
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
/**
|
|
182
|
-
* Ensure UI is built
|
|
183
|
-
*/
|
|
184
|
-
async function ensureUIBuilt() {
|
|
185
|
-
if (!existsSync(distDir)) {
|
|
186
|
-
await buildUI();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
184
|
/**
|
|
191
185
|
* Main execution function
|
|
192
186
|
*/
|
|
@@ -206,14 +200,14 @@ async function main() {
|
|
|
206
200
|
// Ensure dependencies are installed
|
|
207
201
|
await ensureDependencies();
|
|
208
202
|
|
|
209
|
-
//
|
|
210
|
-
|
|
203
|
+
// Validate UI is built (pre-built files should be included in package)
|
|
204
|
+
validateUIBuilt();
|
|
211
205
|
|
|
212
206
|
// Start the server
|
|
213
207
|
await startServer(options.open);
|
|
214
208
|
}
|
|
215
209
|
|
|
216
210
|
main().catch((error) => {
|
|
217
|
-
|
|
211
|
+
logger.error({ error: error.message }, 'Unexpected error');
|
|
218
212
|
process.exit(1);
|
|
219
213
|
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
|
|
3
|
+
import serverLogger from '../shared/logger.js';
|
|
4
|
+
import { isError } from './lib/common/error.js';
|
|
5
|
+
import { runAllExternalServers } from './lib/server/external/all.js';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getDatabaseFile,
|
|
9
|
+
getMcpConfigPath,
|
|
10
|
+
prepareAppDataSpaces,
|
|
11
|
+
} from 'mcp-shark-common/configs/index.js';
|
|
12
|
+
import { initDb } from 'mcp-shark-common/db/init.js';
|
|
13
|
+
import { getLogger } from 'mcp-shark-common/db/logger.js';
|
|
14
|
+
import { withAuditRequestResponseHandler } from './lib/auditor/audit.js';
|
|
15
|
+
import { getInternalServer } from './lib/server/internal/run.js';
|
|
16
|
+
import { createInternalServerFactory } from './lib/server/internal/server.js';
|
|
17
|
+
|
|
18
|
+
function initAuditLogger(serverLogger) {
|
|
19
|
+
serverLogger.info({ path: getDatabaseFile() }, 'Initializing audit logger at');
|
|
20
|
+
return getLogger(initDb(getDatabaseFile()));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Start the MCP Shark server
|
|
25
|
+
* @param {Object} options - Configuration options
|
|
26
|
+
* @param {string} [options.configPath] - Path to MCP config file (defaults to getMcpConfigPath())
|
|
27
|
+
* @param {number} [options.port=9851] - Port to listen on
|
|
28
|
+
* @param {Function} [options.onError] - Error callback
|
|
29
|
+
* @param {Function} [options.onReady] - Ready callback
|
|
30
|
+
* @returns {Promise<{app: Express, server: http.Server, stop: Function}>} Server instance
|
|
31
|
+
*/
|
|
32
|
+
export async function startMcpSharkServer(options = {}) {
|
|
33
|
+
const { configPath = getMcpConfigPath(), port = 9851, onError, onReady } = options;
|
|
34
|
+
|
|
35
|
+
prepareAppDataSpaces();
|
|
36
|
+
|
|
37
|
+
serverLogger.info('[MCP-Shark] Starting MCP server...');
|
|
38
|
+
serverLogger.info(`[MCP-Shark] Config path: ${configPath}`);
|
|
39
|
+
serverLogger.info(`[MCP-Shark] Database path: ${getDatabaseFile()}`);
|
|
40
|
+
serverLogger.info(`[MCP-Shark] Working directory: ${process.cwd()}`);
|
|
41
|
+
serverLogger.info(`[MCP-Shark] PATH: ${process.env.PATH}`);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const auditLogger = initAuditLogger(serverLogger);
|
|
45
|
+
const externalServersResult = await runAllExternalServers(serverLogger, configPath);
|
|
46
|
+
|
|
47
|
+
if (isError(externalServersResult)) {
|
|
48
|
+
const error = new Error(JSON.stringify(externalServersResult));
|
|
49
|
+
if (onError) onError(error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { kv, servers: externalServers } = externalServersResult;
|
|
54
|
+
|
|
55
|
+
const internalServerFactory = createInternalServerFactory(serverLogger, kv);
|
|
56
|
+
const app = getInternalServer(
|
|
57
|
+
internalServerFactory,
|
|
58
|
+
auditLogger,
|
|
59
|
+
withAuditRequestResponseHandler
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const httpServer = createServer(app);
|
|
63
|
+
|
|
64
|
+
const server = await new Promise((resolve, reject) => {
|
|
65
|
+
httpServer.on('error', (err) => {
|
|
66
|
+
if (onError) onError(err);
|
|
67
|
+
reject(err);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
httpServer.listen(port, '0.0.0.0', () => {
|
|
71
|
+
serverLogger.info(`MCP proxy HTTP server listening on http://localhost:${port}/mcp`);
|
|
72
|
+
if (onReady) onReady();
|
|
73
|
+
resolve(httpServer);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const stop = async () => {
|
|
78
|
+
// Close all external server clients
|
|
79
|
+
for (const serverInfo of externalServers) {
|
|
80
|
+
if (serverInfo?.client && !isError(serverInfo)) {
|
|
81
|
+
try {
|
|
82
|
+
await serverInfo.client.close();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
serverLogger.warn({ error: err.message }, 'Error closing external server client');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Close all connections forcefully
|
|
90
|
+
if (server.closeAllConnections) {
|
|
91
|
+
server.closeAllConnections();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Close the server
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
server.close(() => {
|
|
97
|
+
serverLogger.info('MCP Shark server stopped');
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
app,
|
|
105
|
+
server,
|
|
106
|
+
stop,
|
|
107
|
+
auditLogger,
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const errorMsg = 'Error starting MCP server';
|
|
111
|
+
serverLogger.error({ error: error.message, stack: error.stack }, `[MCP-Shark] ${errorMsg}`);
|
|
112
|
+
if (onError) onError(error);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -4,6 +4,17 @@ import { getSessionFromRequest } from '../server/internal/handlers/common.js';
|
|
|
4
4
|
|
|
5
5
|
/* ---------- helpers ---------- */
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Safely parse JSON string, returning null if parsing fails
|
|
9
|
+
*/
|
|
10
|
+
function parseJsonSafely(jsonString) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(jsonString);
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
function toBuffer(body) {
|
|
8
19
|
if (body === undefined || body === null) {
|
|
9
20
|
return Buffer.alloc(0);
|
|
@@ -13,11 +24,7 @@ function toBuffer(body) {
|
|
|
13
24
|
return body;
|
|
14
25
|
}
|
|
15
26
|
|
|
16
|
-
if (
|
|
17
|
-
typeof body === 'object' &&
|
|
18
|
-
body.type === 'Buffer' &&
|
|
19
|
-
Array.isArray(body.data)
|
|
20
|
-
) {
|
|
27
|
+
if (typeof body === 'object' && body.type === 'Buffer' && Array.isArray(body.data)) {
|
|
21
28
|
return Buffer.from(body.data);
|
|
22
29
|
}
|
|
23
30
|
|
|
@@ -52,7 +59,7 @@ function captureResponse(res) {
|
|
|
52
59
|
const wrapped = new Proxy(res, {
|
|
53
60
|
get(target, prop, receiver) {
|
|
54
61
|
if (prop === 'write') {
|
|
55
|
-
return
|
|
62
|
+
return (chunk, ...args) => {
|
|
56
63
|
if (chunk) {
|
|
57
64
|
const buf = toBuffer(chunk);
|
|
58
65
|
chunks.push(buf);
|
|
@@ -62,7 +69,7 @@ function captureResponse(res) {
|
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
if (prop === 'end') {
|
|
65
|
-
return
|
|
72
|
+
return (chunk, ...args) => {
|
|
66
73
|
if (chunk) {
|
|
67
74
|
const buf = toBuffer(chunk);
|
|
68
75
|
chunks.push(buf);
|
|
@@ -114,12 +121,12 @@ function rebuildReq(req, buf) {
|
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
function waitForResponseFinish(res) {
|
|
117
|
-
return new Promise(resolve => {
|
|
118
|
-
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const state = { done: false };
|
|
119
126
|
|
|
120
127
|
function finishOnce() {
|
|
121
|
-
if (!done) {
|
|
122
|
-
done = true;
|
|
128
|
+
if (!state.done) {
|
|
129
|
+
state.done = true;
|
|
123
130
|
resolve();
|
|
124
131
|
}
|
|
125
132
|
}
|
|
@@ -135,24 +142,26 @@ export async function withAuditRequestResponseHandler(
|
|
|
135
142
|
transport,
|
|
136
143
|
req,
|
|
137
144
|
res,
|
|
138
|
-
auditLogger
|
|
145
|
+
auditLogger,
|
|
146
|
+
requestedMcpServer,
|
|
147
|
+
initialSessionId
|
|
139
148
|
) {
|
|
140
149
|
const reqBuf = await readBody(req);
|
|
141
150
|
const reqJsonRpc = tryParseJsonRpc(reqBuf);
|
|
142
151
|
|
|
143
152
|
// Extract session ID from request
|
|
144
153
|
// If no session ID exists, it's an initiation request
|
|
145
|
-
const
|
|
154
|
+
const sessionIdFromRequest = getSessionFromRequest(req);
|
|
155
|
+
const sessionId =
|
|
156
|
+
sessionIdFromRequest === null ||
|
|
157
|
+
sessionIdFromRequest === undefined ||
|
|
158
|
+
sessionIdFromRequest === ''
|
|
159
|
+
? initialSessionId
|
|
160
|
+
: sessionIdFromRequest;
|
|
146
161
|
|
|
147
162
|
// Extract request body as string
|
|
148
163
|
const reqBodyStr = reqBuf.toString('utf8');
|
|
149
|
-
const reqBodyJson = (
|
|
150
|
-
try {
|
|
151
|
-
return JSON.parse(reqBodyStr);
|
|
152
|
-
} catch {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
})();
|
|
164
|
+
const reqBodyJson = parseJsonSafely(reqBodyStr);
|
|
156
165
|
|
|
157
166
|
// Log request packet to database
|
|
158
167
|
const requestResult = auditLogger.logRequestPacket({
|
|
@@ -161,7 +170,7 @@ export async function withAuditRequestResponseHandler(
|
|
|
161
170
|
headers: req.headers,
|
|
162
171
|
body: reqBodyJson || reqBodyStr,
|
|
163
172
|
userAgent: req.headers['user-agent'] || req.headers['User-Agent'] || null,
|
|
164
|
-
remoteAddress:
|
|
173
|
+
remoteAddress: requestedMcpServer,
|
|
165
174
|
sessionId: sessionId || null,
|
|
166
175
|
});
|
|
167
176
|
|
|
@@ -169,15 +178,7 @@ export async function withAuditRequestResponseHandler(
|
|
|
169
178
|
|
|
170
179
|
const replayReq = req.body ? req : rebuildReq(req, reqBuf);
|
|
171
180
|
|
|
172
|
-
const parsedForTransport = reqJsonRpc
|
|
173
|
-
? (() => {
|
|
174
|
-
try {
|
|
175
|
-
return JSON.parse(reqBuf.toString('utf8'));
|
|
176
|
-
} catch {
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
})()
|
|
180
|
-
: undefined;
|
|
181
|
+
const parsedForTransport = reqJsonRpc ? parseJsonSafely(reqBuf.toString('utf8')) : undefined;
|
|
181
182
|
|
|
182
183
|
// hand over to transport
|
|
183
184
|
if (!transport || typeof transport.handleRequest !== 'function') {
|
|
@@ -198,19 +199,10 @@ export async function withAuditRequestResponseHandler(
|
|
|
198
199
|
|
|
199
200
|
// Extract response body as string
|
|
200
201
|
const resBodyStr = resBuf.toString('utf8');
|
|
201
|
-
const resBodyJson = (
|
|
202
|
-
try {
|
|
203
|
-
return JSON.parse(resBodyStr);
|
|
204
|
-
} catch {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
})();
|
|
202
|
+
const resBodyJson = parseJsonSafely(resBodyStr);
|
|
208
203
|
|
|
209
204
|
// Extract JSON-RPC ID from request for correlation
|
|
210
|
-
const jsonrpcId =
|
|
211
|
-
reqJsonRpc?.payload?.id !== undefined
|
|
212
|
-
? String(reqJsonRpc.payload.id)
|
|
213
|
-
: null;
|
|
205
|
+
const jsonrpcId = reqJsonRpc?.payload?.id !== undefined ? String(reqJsonRpc.payload.id) : null;
|
|
214
206
|
|
|
215
207
|
// Log response packet to database
|
|
216
208
|
// Use the same session ID from the request
|
|
@@ -223,6 +215,6 @@ export async function withAuditRequestResponseHandler(
|
|
|
223
215
|
jsonrpcId,
|
|
224
216
|
sessionId: sessionId || null,
|
|
225
217
|
userAgent: req.headers['user-agent'] || req.headers['User-Agent'] || null,
|
|
226
|
-
remoteAddress:
|
|
218
|
+
remoteAddress: requestedMcpServer || null,
|
|
227
219
|
});
|
|
228
220
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { normalizeConfig } from './config.js';
|
|
2
|
-
import { runExternalServer } from './single/run.js';
|
|
3
1
|
import { CompositeError, getErrors } from '../../common/error.js';
|
|
2
|
+
import { normalizeConfig } from './config.js';
|
|
4
3
|
import { buildKv } from './kv.js';
|
|
4
|
+
import { runExternalServer } from './single/run.js';
|
|
5
5
|
|
|
6
6
|
export class RunAllExternalServersError extends CompositeError {
|
|
7
7
|
constructor(message, error, errors = []) {
|
|
@@ -13,9 +13,7 @@ export class RunAllExternalServersError extends CompositeError {
|
|
|
13
13
|
export async function runAllExternalServers(logger, parsedConfig) {
|
|
14
14
|
const configs = normalizeConfig(parsedConfig);
|
|
15
15
|
const results = await Promise.all(
|
|
16
|
-
Object.entries(configs).map(([name, config]) =>
|
|
17
|
-
runExternalServer({ logger, name, config })
|
|
18
|
-
)
|
|
16
|
+
Object.entries(configs).map(([name, config]) => runExternalServer({ logger, name, config }))
|
|
19
17
|
);
|
|
20
18
|
|
|
21
19
|
const flattenedResults = results.flat();
|
|
@@ -28,5 +26,6 @@ export async function runAllExternalServers(logger, parsedConfig) {
|
|
|
28
26
|
);
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
const kv = buildKv(flattenedResults);
|
|
30
|
+
return { kv, servers: flattenedResults };
|
|
32
31
|
}
|
|
@@ -16,9 +16,7 @@ function parseConfig(configPath) {
|
|
|
16
16
|
}
|
|
17
17
|
return new ConfigError(
|
|
18
18
|
'Invalid config file',
|
|
19
|
-
new Error(
|
|
20
|
-
`Invalid config file: ${configPath} - ${JSON.stringify(conf, null, 2)}`
|
|
21
|
-
)
|
|
19
|
+
new Error(`Invalid config file: ${configPath} - ${JSON.stringify(conf, null, 2)}`)
|
|
22
20
|
);
|
|
23
21
|
} catch (error) {
|
|
24
22
|
return new ConfigError(
|
|
@@ -1,25 +1,8 @@
|
|
|
1
1
|
const kv = new Map();
|
|
2
2
|
|
|
3
|
-
function buildName(name, typeName) {
|
|
4
|
-
return `${name}.${typeName}`;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function extractName(name) {
|
|
8
|
-
const [serverName, typeName] = name.split('.');
|
|
9
|
-
return { serverName, typeName };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
3
|
export function buildKv(downstreamServers) {
|
|
13
4
|
for (const downstreamServer of downstreamServers) {
|
|
14
|
-
const {
|
|
15
|
-
name,
|
|
16
|
-
tools,
|
|
17
|
-
resources,
|
|
18
|
-
prompts,
|
|
19
|
-
callTool,
|
|
20
|
-
getPrompt,
|
|
21
|
-
readResource,
|
|
22
|
-
} = downstreamServer;
|
|
5
|
+
const { name, tools, resources, prompts, callTool, getPrompt, readResource } = downstreamServer;
|
|
23
6
|
|
|
24
7
|
if (!kv.has(name)) {
|
|
25
8
|
const toolsMap = new Map();
|
|
@@ -42,17 +25,17 @@ export function buildKv(downstreamServers) {
|
|
|
42
25
|
toolsMap,
|
|
43
26
|
resourcesMap,
|
|
44
27
|
promptsMap,
|
|
45
|
-
tools: tools.map(tool => {
|
|
46
|
-
return { ...tool, name:
|
|
28
|
+
tools: tools.map((tool) => {
|
|
29
|
+
return { ...tool, name: tool.name };
|
|
47
30
|
}),
|
|
48
|
-
resources: resources.map(resource => {
|
|
31
|
+
resources: resources.map((resource) => {
|
|
49
32
|
return {
|
|
50
33
|
...resource,
|
|
51
|
-
name:
|
|
34
|
+
name: resource.name,
|
|
52
35
|
};
|
|
53
36
|
}),
|
|
54
|
-
prompts: prompts.map(prompt => {
|
|
55
|
-
return { ...prompt, name:
|
|
37
|
+
prompts: prompts.map((prompt) => {
|
|
38
|
+
return { ...prompt, name: prompt.name };
|
|
56
39
|
}),
|
|
57
40
|
});
|
|
58
41
|
}
|
|
@@ -61,42 +44,40 @@ export function buildKv(downstreamServers) {
|
|
|
61
44
|
return kv;
|
|
62
45
|
}
|
|
63
46
|
|
|
64
|
-
export function getBy(database, calledName, action) {
|
|
65
|
-
const
|
|
66
|
-
if (!serverName || !typeName) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const entry = database.get(serverName);
|
|
47
|
+
export function getBy(database, requestedMcpServer, calledName, action) {
|
|
48
|
+
const entry = database.get(requestedMcpServer);
|
|
70
49
|
if (!entry) {
|
|
71
50
|
return null;
|
|
72
51
|
}
|
|
73
52
|
|
|
74
53
|
// Type-based lookup
|
|
75
54
|
if (action === 'getTools') {
|
|
76
|
-
return entry.toolsMap.get(
|
|
55
|
+
return entry.toolsMap.get(calledName);
|
|
77
56
|
}
|
|
78
57
|
if (action === 'getResources') {
|
|
79
|
-
return entry.resourcesMap.get(
|
|
58
|
+
return entry.resourcesMap.get(calledName);
|
|
80
59
|
}
|
|
81
60
|
if (action === 'getPrompts') {
|
|
82
|
-
return entry.promptsMap.get(
|
|
61
|
+
return entry.promptsMap.get(calledName);
|
|
83
62
|
}
|
|
84
63
|
|
|
85
64
|
// Action-based lookup
|
|
86
65
|
if (action === 'callTool') {
|
|
87
|
-
return entry.toolsMap.get(
|
|
66
|
+
return entry.toolsMap.get(calledName);
|
|
88
67
|
}
|
|
89
68
|
if (action === 'readResource') {
|
|
90
|
-
return entry.resourcesMap.get(
|
|
69
|
+
return entry.resourcesMap.get(calledName);
|
|
91
70
|
}
|
|
92
71
|
if (action === 'getPrompt') {
|
|
93
|
-
return entry.promptsMap.get(
|
|
72
|
+
return entry.promptsMap.get(calledName);
|
|
94
73
|
}
|
|
95
74
|
return null;
|
|
96
75
|
}
|
|
97
76
|
|
|
98
|
-
export function listAll(database, type) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
export function listAll(database, requestedMcpServer, type) {
|
|
78
|
+
const serverEntry = database.get(requestedMcpServer);
|
|
79
|
+
if (!serverEntry) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
return serverEntry[type];
|
|
102
83
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ListToolsResultSchema,
|
|
3
|
-
ListResourcesResultSchema,
|
|
4
2
|
ListPromptsResultSchema,
|
|
3
|
+
ListResourcesResultSchema,
|
|
4
|
+
ListToolsResultSchema,
|
|
5
5
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
|
|
7
7
|
import { CompositeError } from '../../../common/error.js';
|
|
@@ -22,10 +22,7 @@ async function getListOf(client, typeOfList, schema) {
|
|
|
22
22
|
[typeOfList]: [],
|
|
23
23
|
};
|
|
24
24
|
try {
|
|
25
|
-
const result = await client.request(
|
|
26
|
-
{ method: `${typeOfList}/list` },
|
|
27
|
-
schema
|
|
28
|
-
);
|
|
25
|
+
const result = await client.request({ method: `${typeOfList}/list` }, schema);
|
|
29
26
|
fetchedList[typeOfList] = result[typeOfList];
|
|
30
27
|
return fetchedList;
|
|
31
28
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CompositeError, getErrors, isError } from '../../../common/error.js';
|
|
2
2
|
import { createClient } from './client.js';
|
|
3
3
|
import * as requests from './request.js';
|
|
4
|
-
import {
|
|
4
|
+
import { makeTransport } from './transport.js';
|
|
5
5
|
|
|
6
6
|
export class RunError extends CompositeError {
|
|
7
7
|
constructor(message, error, errors = []) {
|
|
@@ -11,27 +11,18 @@ export class RunError extends CompositeError {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export async function runExternalServer({ logger, name, config }) {
|
|
14
|
-
logger.debug(
|
|
15
|
-
`Starting external server run for server ${name} with config:`,
|
|
16
|
-
config
|
|
17
|
-
);
|
|
14
|
+
logger.debug(`Starting external server run for server ${name} with config:`, config);
|
|
18
15
|
|
|
19
16
|
// Create transport
|
|
20
17
|
const transport = makeTransport(config);
|
|
21
18
|
if (isError(transport)) {
|
|
22
|
-
return new RunError(
|
|
23
|
-
`Error creating transport for external server ${name}`,
|
|
24
|
-
transport.error
|
|
25
|
-
);
|
|
19
|
+
return new RunError(`Error creating transport for external server ${name}`, transport.error);
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
// Create client
|
|
29
23
|
const client = await createClient({ transport });
|
|
30
24
|
if (isError(client)) {
|
|
31
|
-
return new RunError(
|
|
32
|
-
`Error creating client for external server ${name}`,
|
|
33
|
-
client.error
|
|
34
|
-
);
|
|
25
|
+
return new RunError(`Error creating client for external server ${name}`, client.error);
|
|
35
26
|
}
|
|
36
27
|
|
|
37
28
|
// Run requests
|
|
@@ -52,17 +43,15 @@ export async function runExternalServer({ logger, name, config }) {
|
|
|
52
43
|
);
|
|
53
44
|
}
|
|
54
45
|
|
|
55
|
-
const [{ tools }, { resources }, { prompts }] = results.map(
|
|
56
|
-
result => result.value
|
|
57
|
-
);
|
|
46
|
+
const [{ tools }, { resources }, { prompts }] = results.map((result) => result.value);
|
|
58
47
|
return {
|
|
59
48
|
name,
|
|
60
49
|
client,
|
|
61
50
|
tools,
|
|
62
51
|
resources,
|
|
63
52
|
prompts,
|
|
64
|
-
callTool: args => client.callTool.bind(client)(args),
|
|
65
|
-
readResource: resourceUri => {
|
|
53
|
+
callTool: (args) => client.callTool.bind(client)(args),
|
|
54
|
+
readResource: (resourceUri) => {
|
|
66
55
|
return client.readResource.bind(client)(resourceUri);
|
|
67
56
|
},
|
|
68
57
|
getPrompt: (promptName, args) => {
|