@mcp-shark/mcp-shark 1.4.2 → 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 +22 -38
- package/mcp-server/lib/common/error.js +1 -1
- package/mcp-server/lib/server/external/all.js +5 -6
- package/mcp-server/lib/server/external/config.js +1 -3
- package/mcp-server/lib/server/external/kv.js +4 -12
- package/mcp-server/lib/server/external/single/request.js +3 -6
- package/mcp-server/lib/server/external/single/run.js +8 -19
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
- package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
- package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
- package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
- package/mcp-server/lib/server/internal/run.js +4 -16
- package/mcp-server/lib/server/internal/server.js +6 -7
- package/mcp-server/lib/server/internal/session.js +2 -15
- package/mcp-server/mcp-shark.js +16 -66
- package/package.json +23 -38
- package/ui/dist/assets/index-Cc-IUa83.css +1 -0
- package/ui/dist/assets/index-srLDlk97.js +35 -0
- package/ui/dist/index.html +17 -0
- package/ui/dist/og-image.png +0 -0
- package/ui/server/routes/backups/deleteBackup.js +54 -0
- package/ui/server/routes/backups/index.js +15 -0
- package/ui/server/routes/backups/listBackups.js +75 -0
- package/ui/server/routes/backups/restoreBackup.js +83 -0
- package/ui/server/routes/backups/viewBackup.js +47 -0
- package/ui/server/routes/composite/index.js +46 -0
- package/ui/server/routes/composite/servers.js +18 -0
- package/ui/server/routes/composite/setup.js +129 -0
- package/ui/server/routes/composite/status.js +7 -0
- package/ui/server/routes/composite/stop.js +39 -0
- package/ui/server/routes/composite/utils.js +45 -0
- package/ui/server/routes/config.js +34 -30
- package/ui/server/routes/conversations.js +3 -3
- package/ui/server/routes/help.js +2 -2
- package/ui/server/routes/logs.js +5 -5
- package/ui/server/routes/playground.js +45 -47
- package/ui/server/routes/requests.js +112 -108
- package/ui/server/routes/sessions.js +4 -4
- package/ui/server/routes/settings.js +199 -0
- package/ui/server/routes/smartscan/discover.js +7 -6
- package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
- package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
- package/ui/server/routes/smartscan/scans/createScan.js +2 -1
- package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
- package/ui/server/routes/smartscan/scans/getScan.js +2 -1
- package/ui/server/routes/smartscan/scans/listScans.js +5 -4
- package/ui/server/routes/smartscan/scans.js +3 -3
- package/ui/server/routes/smartscan/token.js +4 -3
- package/ui/server/routes/smartscan/transport.js +1 -1
- package/ui/server/routes/smartscan.js +1 -1
- package/ui/server/routes/statistics.js +13 -10
- package/ui/server/utils/config-update.js +7 -6
- package/ui/server/utils/config.js +4 -4
- package/ui/server/utils/logger.js +2 -0
- package/ui/server/utils/paths.js +210 -2
- package/ui/server/utils/port.js +2 -2
- package/ui/server/utils/process.js +0 -67
- package/ui/server/utils/scan-cache/all-results.js +76 -59
- package/ui/server/utils/scan-cache/directory.js +1 -1
- package/ui/server/utils/scan-cache/file-operations.js +19 -16
- package/ui/server/utils/scan-cache/server-operations.js +14 -9
- package/ui/server/utils/serialization.js +9 -3
- package/ui/server/utils/smartscan-token.js +4 -3
- package/ui/server.js +86 -41
- package/ui/src/App.jsx +5 -5
- package/ui/src/CompositeLogs.jsx +20 -20
- package/ui/src/CompositeSetup.jsx +9 -9
- package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
- package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
- package/ui/src/HelpGuide.jsx +17 -4
- package/ui/src/IntroTour.jsx +19 -5
- package/ui/src/LogDetail.jsx +1 -0
- package/ui/src/LogTable.jsx +24 -6
- package/ui/src/PacketDetail.jsx +21 -16
- package/ui/src/PacketFilters.jsx +29 -14
- package/ui/src/PacketList.jsx +4 -5
- package/ui/src/SmartScan.jsx +5 -5
- package/ui/src/TabNavigation.jsx +5 -5
- package/ui/src/components/App/HelpButton.jsx +4 -0
- package/ui/src/components/App/TrafficTab.jsx +4 -4
- package/ui/src/components/App/useAppState.js +118 -24
- package/ui/src/components/BackupList.jsx +6 -2
- package/ui/src/components/CollapsibleSection.jsx +16 -2
- package/ui/src/components/ConfigViewerModal.jsx +17 -3
- package/ui/src/components/ConfirmationModal.jsx +20 -3
- package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
- package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
- package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
- package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
- package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
- package/ui/src/components/DetectedPathsList.jsx +5 -2
- package/ui/src/components/FileInput.jsx +3 -1
- package/ui/src/components/GroupHeader.jsx +14 -0
- package/ui/src/components/GroupedByMcpView.jsx +3 -4
- package/ui/src/components/GroupedByServerView.jsx +1 -1
- package/ui/src/components/GroupedBySessionView.jsx +1 -1
- package/ui/src/components/HexTab.jsx +17 -4
- package/ui/src/components/LogsToolbar.jsx +3 -1
- package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
- package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
- package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
- package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
- package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
- package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
- package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
- package/ui/src/components/McpPlayground.jsx +5 -2
- package/ui/src/components/PacketDetailHeader.jsx +8 -3
- package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
- package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
- package/ui/src/components/RawTab.jsx +15 -2
- package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
- package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
- package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
- package/ui/src/components/RequestRow.jsx +17 -9
- package/ui/src/components/ServerControl.jsx +3 -1
- package/ui/src/components/ServiceSelector.jsx +2 -0
- package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
- package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
- package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
- package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
- package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
- package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
- package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
- package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
- package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
- package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
- package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
- package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
- package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
- package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
- package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
- package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
- package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
- package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
- package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
- package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
- package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
- package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
- package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
- package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
- package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
- package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
- package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
- package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
- package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
- package/ui/src/components/SmartScan/useSmartScan.js +4 -4
- package/ui/src/components/SmartScan/utils.js +3 -1
- package/ui/src/components/SmartScanIcons.jsx +6 -3
- package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
- package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
- package/ui/src/components/TabNavigation.jsx +8 -3
- package/ui/src/components/TabNavigationIcons.jsx +4 -4
- package/ui/src/components/TourOverlay.jsx +1 -1
- package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
- package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
- package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
- package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
- package/ui/src/components/TourTooltip.jsx +11 -3
- package/ui/src/components/ViewModeTabs.jsx +3 -1
- package/ui/src/config/tourSteps.jsx +0 -2
- package/ui/src/hooks/useAnimation.js +15 -12
- package/ui/src/hooks/useConfigManagement.js +8 -8
- package/ui/src/hooks/useServiceExtraction.js +1 -1
- package/ui/src/index.css +3 -8
- package/ui/src/theme.js +3 -3
- package/ui/src/utils/hexUtils.js +11 -5
- package/ui/src/utils/mcpGroupingUtils.js +18 -10
- package/ui/src/utils/requestPairing.js +89 -0
- package/ui/src/utils/requestUtils.js +32 -101
- package/ui/vite.config.js +1 -1
- package/mcp-server/.editorconfig +0 -15
- package/mcp-server/.prettierignore +0 -11
- package/mcp-server/.prettierrc +0 -12
- package/mcp-server/README.md +0 -280
- package/mcp-server/commitlint.config.cjs +0 -42
- package/mcp-server/eslint.config.js +0 -131
- package/mcp-server/package-lock.json +0 -4784
- package/mcp-server/package.json +0 -30
- package/ui/README.md +0 -212
- package/ui/package-lock.json +0 -3574
- package/ui/package.json +0 -12
- package/ui/paths.js +0 -282
- package/ui/server/routes/backups.js +0 -251
- package/ui/server/routes/composite.js +0 -260
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
|
}
|
|
@@ -154,13 +161,7 @@ export async function withAuditRequestResponseHandler(
|
|
|
154
161
|
|
|
155
162
|
// Extract request body as string
|
|
156
163
|
const reqBodyStr = reqBuf.toString('utf8');
|
|
157
|
-
const reqBodyJson = (
|
|
158
|
-
try {
|
|
159
|
-
return JSON.parse(reqBodyStr);
|
|
160
|
-
} catch {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
})();
|
|
164
|
+
const reqBodyJson = parseJsonSafely(reqBodyStr);
|
|
164
165
|
|
|
165
166
|
// Log request packet to database
|
|
166
167
|
const requestResult = auditLogger.logRequestPacket({
|
|
@@ -177,15 +178,7 @@ export async function withAuditRequestResponseHandler(
|
|
|
177
178
|
|
|
178
179
|
const replayReq = req.body ? req : rebuildReq(req, reqBuf);
|
|
179
180
|
|
|
180
|
-
const parsedForTransport = reqJsonRpc
|
|
181
|
-
? (() => {
|
|
182
|
-
try {
|
|
183
|
-
return JSON.parse(reqBuf.toString('utf8'));
|
|
184
|
-
} catch {
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
})()
|
|
188
|
-
: undefined;
|
|
181
|
+
const parsedForTransport = reqJsonRpc ? parseJsonSafely(reqBuf.toString('utf8')) : undefined;
|
|
189
182
|
|
|
190
183
|
// hand over to transport
|
|
191
184
|
if (!transport || typeof transport.handleRequest !== 'function') {
|
|
@@ -206,19 +199,10 @@ export async function withAuditRequestResponseHandler(
|
|
|
206
199
|
|
|
207
200
|
// Extract response body as string
|
|
208
201
|
const resBodyStr = resBuf.toString('utf8');
|
|
209
|
-
const resBodyJson = (
|
|
210
|
-
try {
|
|
211
|
-
return JSON.parse(resBodyStr);
|
|
212
|
-
} catch {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
})();
|
|
202
|
+
const resBodyJson = parseJsonSafely(resBodyStr);
|
|
216
203
|
|
|
217
204
|
// Extract JSON-RPC ID from request for correlation
|
|
218
|
-
const jsonrpcId =
|
|
219
|
-
reqJsonRpc?.payload?.id !== undefined
|
|
220
|
-
? String(reqJsonRpc.payload.id)
|
|
221
|
-
: null;
|
|
205
|
+
const jsonrpcId = reqJsonRpc?.payload?.id !== undefined ? String(reqJsonRpc.payload.id) : null;
|
|
222
206
|
|
|
223
207
|
// Log response packet to database
|
|
224
208
|
// Use the same session ID from the request
|
|
@@ -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(
|
|
@@ -2,15 +2,7 @@ const kv = new Map();
|
|
|
2
2
|
|
|
3
3
|
export function buildKv(downstreamServers) {
|
|
4
4
|
for (const downstreamServer of downstreamServers) {
|
|
5
|
-
const {
|
|
6
|
-
name,
|
|
7
|
-
tools,
|
|
8
|
-
resources,
|
|
9
|
-
prompts,
|
|
10
|
-
callTool,
|
|
11
|
-
getPrompt,
|
|
12
|
-
readResource,
|
|
13
|
-
} = downstreamServer;
|
|
5
|
+
const { name, tools, resources, prompts, callTool, getPrompt, readResource } = downstreamServer;
|
|
14
6
|
|
|
15
7
|
if (!kv.has(name)) {
|
|
16
8
|
const toolsMap = new Map();
|
|
@@ -33,16 +25,16 @@ export function buildKv(downstreamServers) {
|
|
|
33
25
|
toolsMap,
|
|
34
26
|
resourcesMap,
|
|
35
27
|
promptsMap,
|
|
36
|
-
tools: tools.map(tool => {
|
|
28
|
+
tools: tools.map((tool) => {
|
|
37
29
|
return { ...tool, name: tool.name };
|
|
38
30
|
}),
|
|
39
|
-
resources: resources.map(resource => {
|
|
31
|
+
resources: resources.map((resource) => {
|
|
40
32
|
return {
|
|
41
33
|
...resource,
|
|
42
34
|
name: resource.name,
|
|
43
35
|
};
|
|
44
36
|
}),
|
|
45
|
-
prompts: prompts.map(prompt => {
|
|
37
|
+
prompts: prompts.map((prompt) => {
|
|
46
38
|
return { ...prompt, name: prompt.name };
|
|
47
39
|
}),
|
|
48
40
|
});
|
|
@@ -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) => {
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import { getBy } from '../../external/kv.js';
|
|
2
2
|
import { InternalServerError } from './error.js';
|
|
3
3
|
|
|
4
|
-
export function createPromptsGetHandler(
|
|
5
|
-
|
|
6
|
-
mcpServers,
|
|
7
|
-
requestedMcpServer
|
|
8
|
-
) {
|
|
9
|
-
return async req => {
|
|
4
|
+
export function createPromptsGetHandler(logger, mcpServers, requestedMcpServer) {
|
|
5
|
+
return async (req) => {
|
|
10
6
|
const name = req.params.name;
|
|
11
7
|
const promptArgs = req?.params?.arguments || {};
|
|
12
8
|
logger.debug('Prompt get', name, promptArgs);
|
|
13
9
|
|
|
14
|
-
const getPrompt = getBy(
|
|
15
|
-
mcpServers,
|
|
16
|
-
requestedMcpServer,
|
|
17
|
-
name,
|
|
18
|
-
'getPrompt',
|
|
19
|
-
promptArgs
|
|
20
|
-
);
|
|
10
|
+
const getPrompt = getBy(mcpServers, requestedMcpServer, name, 'getPrompt', promptArgs);
|
|
21
11
|
if (!getPrompt) {
|
|
22
12
|
throw new InternalServerError(`Prompt not found: ${name}`);
|
|
23
13
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { listAll } from '../../external/kv.js';
|
|
2
2
|
|
|
3
|
-
export function createPromptsListHandler(
|
|
4
|
-
|
|
5
|
-
mcpServers,
|
|
6
|
-
requestedMcpServer
|
|
7
|
-
) {
|
|
8
|
-
return async req => {
|
|
3
|
+
export function createPromptsListHandler(logger, mcpServers, requestedMcpServer) {
|
|
4
|
+
return async (req) => {
|
|
9
5
|
const path = req.path;
|
|
10
6
|
logger.debug('Prompts list', path);
|
|
11
7
|
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { listAll } from '../../external/kv.js';
|
|
2
2
|
|
|
3
|
-
export function createResourcesListHandler(
|
|
4
|
-
|
|
5
|
-
mcpServers,
|
|
6
|
-
requestedMcpServer
|
|
7
|
-
) {
|
|
8
|
-
return async req => {
|
|
3
|
+
export function createResourcesListHandler(logger, mcpServers, requestedMcpServer) {
|
|
4
|
+
return async (req) => {
|
|
9
5
|
const path = req.path;
|
|
10
6
|
logger.debug('Resources list', path);
|
|
11
7
|
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
import { getBy } from '../../external/kv.js';
|
|
2
2
|
import { InternalServerError } from './error.js';
|
|
3
3
|
|
|
4
|
-
export function createResourcesReadHandler(
|
|
5
|
-
|
|
6
|
-
mcpServers,
|
|
7
|
-
requestedMcpServer
|
|
8
|
-
) {
|
|
9
|
-
return async req => {
|
|
4
|
+
export function createResourcesReadHandler(logger, mcpServers, requestedMcpServer) {
|
|
5
|
+
return async (req) => {
|
|
10
6
|
const path = req.path;
|
|
11
7
|
const uri = req.params.uri;
|
|
12
8
|
logger.debug('Resource read', path, uri);
|
|
13
9
|
|
|
14
|
-
const readResource = getBy(
|
|
15
|
-
mcpServers,
|
|
16
|
-
requestedMcpServer,
|
|
17
|
-
uri,
|
|
18
|
-
'readResource'
|
|
19
|
-
);
|
|
10
|
+
const readResource = getBy(mcpServers, requestedMcpServer, uri, 'readResource');
|
|
20
11
|
if (!readResource) {
|
|
21
12
|
throw new InternalServerError(`Resource not found: ${uri}`);
|
|
22
13
|
}
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { getBy } from '../../external/kv.js';
|
|
2
2
|
import { InternalServerError } from './error.js';
|
|
3
3
|
|
|
4
|
-
const isAsyncIterable = v => v && typeof v[Symbol.asyncIterator] === 'function';
|
|
4
|
+
const isAsyncIterable = (v) => v && typeof v[Symbol.asyncIterator] === 'function';
|
|
5
5
|
|
|
6
6
|
export function createToolsCallHandler(logger, mcpServers, requestedMcpServer) {
|
|
7
|
-
return async req => {
|
|
7
|
+
return async (req) => {
|
|
8
8
|
const path = req.path;
|
|
9
9
|
const { name, arguments: args } = req.params;
|
|
10
10
|
logger.debug('Tool call', path, name, args);
|
|
11
11
|
|
|
12
|
-
const callTool = getBy(
|
|
13
|
-
mcpServers,
|
|
14
|
-
requestedMcpServer,
|
|
15
|
-
name,
|
|
16
|
-
'callTool',
|
|
17
|
-
args || {}
|
|
18
|
-
);
|
|
12
|
+
const callTool = getBy(mcpServers, requestedMcpServer, name, 'callTool', args || {});
|
|
19
13
|
if (!callTool) {
|
|
20
14
|
throw new InternalServerError(`Tool not found: ${name}`);
|
|
21
15
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { listAll } from '../../external/kv.js';
|
|
2
2
|
|
|
3
3
|
export function createToolsListHandler(logger, mcpServers, requestedMcpServer) {
|
|
4
|
-
return async req => {
|
|
4
|
+
return async (req) => {
|
|
5
5
|
const path = req.path;
|
|
6
6
|
logger.debug('Listing tools', path);
|
|
7
7
|
|
|
8
8
|
const res = await listAll(mcpServers, requestedMcpServer, 'tools');
|
|
9
|
-
logger.debug('Tools list result', res);
|
|
9
|
+
logger.debug('Tools list result', JSON.stringify(res));
|
|
10
10
|
|
|
11
11
|
const result = Array.isArray(res) ? { tools: res } : res;
|
|
12
12
|
|