@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.
Files changed (203) hide show
  1. package/README.md +84 -645
  2. package/bin/mcp-shark.js +30 -36
  3. package/mcp-server/index.js +115 -0
  4. package/mcp-server/lib/auditor/audit.js +22 -38
  5. package/mcp-server/lib/common/error.js +1 -1
  6. package/mcp-server/lib/server/external/all.js +5 -6
  7. package/mcp-server/lib/server/external/config.js +1 -3
  8. package/mcp-server/lib/server/external/kv.js +4 -12
  9. package/mcp-server/lib/server/external/single/request.js +3 -6
  10. package/mcp-server/lib/server/external/single/run.js +8 -19
  11. package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
  12. package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
  13. package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
  14. package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
  15. package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
  16. package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
  17. package/mcp-server/lib/server/internal/run.js +4 -16
  18. package/mcp-server/lib/server/internal/server.js +6 -7
  19. package/mcp-server/lib/server/internal/session.js +2 -15
  20. package/mcp-server/mcp-shark.js +16 -66
  21. package/package.json +23 -38
  22. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  23. package/ui/dist/assets/index-srLDlk97.js +35 -0
  24. package/ui/dist/index.html +17 -0
  25. package/ui/dist/og-image.png +0 -0
  26. package/ui/server/routes/backups/deleteBackup.js +54 -0
  27. package/ui/server/routes/backups/index.js +15 -0
  28. package/ui/server/routes/backups/listBackups.js +75 -0
  29. package/ui/server/routes/backups/restoreBackup.js +83 -0
  30. package/ui/server/routes/backups/viewBackup.js +47 -0
  31. package/ui/server/routes/composite/index.js +46 -0
  32. package/ui/server/routes/composite/servers.js +18 -0
  33. package/ui/server/routes/composite/setup.js +129 -0
  34. package/ui/server/routes/composite/status.js +7 -0
  35. package/ui/server/routes/composite/stop.js +39 -0
  36. package/ui/server/routes/composite/utils.js +45 -0
  37. package/ui/server/routes/config.js +34 -30
  38. package/ui/server/routes/conversations.js +3 -3
  39. package/ui/server/routes/help.js +2 -2
  40. package/ui/server/routes/logs.js +5 -5
  41. package/ui/server/routes/playground.js +45 -47
  42. package/ui/server/routes/requests.js +112 -108
  43. package/ui/server/routes/sessions.js +4 -4
  44. package/ui/server/routes/settings.js +199 -0
  45. package/ui/server/routes/smartscan/discover.js +7 -6
  46. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  47. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  48. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  49. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  51. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  52. package/ui/server/routes/smartscan/scans.js +3 -3
  53. package/ui/server/routes/smartscan/token.js +4 -3
  54. package/ui/server/routes/smartscan/transport.js +1 -1
  55. package/ui/server/routes/smartscan.js +1 -1
  56. package/ui/server/routes/statistics.js +13 -10
  57. package/ui/server/utils/config-update.js +7 -6
  58. package/ui/server/utils/config.js +4 -4
  59. package/ui/server/utils/logger.js +2 -0
  60. package/ui/server/utils/paths.js +210 -2
  61. package/ui/server/utils/port.js +2 -2
  62. package/ui/server/utils/process.js +0 -67
  63. package/ui/server/utils/scan-cache/all-results.js +76 -59
  64. package/ui/server/utils/scan-cache/directory.js +1 -1
  65. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  66. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  67. package/ui/server/utils/serialization.js +9 -3
  68. package/ui/server/utils/smartscan-token.js +4 -3
  69. package/ui/server.js +86 -41
  70. package/ui/src/App.jsx +5 -5
  71. package/ui/src/CompositeLogs.jsx +20 -20
  72. package/ui/src/CompositeSetup.jsx +9 -9
  73. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  74. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  75. package/ui/src/HelpGuide.jsx +17 -4
  76. package/ui/src/IntroTour.jsx +19 -5
  77. package/ui/src/LogDetail.jsx +1 -0
  78. package/ui/src/LogTable.jsx +24 -6
  79. package/ui/src/PacketDetail.jsx +21 -16
  80. package/ui/src/PacketFilters.jsx +29 -14
  81. package/ui/src/PacketList.jsx +4 -5
  82. package/ui/src/SmartScan.jsx +5 -5
  83. package/ui/src/TabNavigation.jsx +5 -5
  84. package/ui/src/components/App/HelpButton.jsx +4 -0
  85. package/ui/src/components/App/TrafficTab.jsx +4 -4
  86. package/ui/src/components/App/useAppState.js +118 -24
  87. package/ui/src/components/BackupList.jsx +6 -2
  88. package/ui/src/components/CollapsibleSection.jsx +16 -2
  89. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  90. package/ui/src/components/ConfirmationModal.jsx +20 -3
  91. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  92. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  93. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  94. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  95. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetectedPathsList.jsx +5 -2
  97. package/ui/src/components/FileInput.jsx +3 -1
  98. package/ui/src/components/GroupHeader.jsx +14 -0
  99. package/ui/src/components/GroupedByMcpView.jsx +3 -4
  100. package/ui/src/components/GroupedByServerView.jsx +1 -1
  101. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  102. package/ui/src/components/HexTab.jsx +17 -4
  103. package/ui/src/components/LogsToolbar.jsx +3 -1
  104. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  105. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
  108. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  109. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
  112. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  113. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
  116. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  117. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
  118. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
  119. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
  120. package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
  121. package/ui/src/components/McpPlayground.jsx +5 -2
  122. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  123. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  124. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  125. package/ui/src/components/RawTab.jsx +15 -2
  126. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  127. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  128. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  129. package/ui/src/components/RequestRow.jsx +17 -9
  130. package/ui/src/components/ServerControl.jsx +3 -1
  131. package/ui/src/components/ServiceSelector.jsx +2 -0
  132. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  133. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  136. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  137. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  138. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  139. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  140. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  141. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  142. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  143. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  144. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  145. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  146. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  147. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  148. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  149. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  150. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  151. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  152. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  153. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  154. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  155. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  156. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  157. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  158. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  159. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  160. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  161. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  162. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  163. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  164. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  165. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  166. package/ui/src/components/SmartScan/utils.js +3 -1
  167. package/ui/src/components/SmartScanIcons.jsx +6 -3
  168. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  169. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  170. package/ui/src/components/TabNavigation.jsx +8 -3
  171. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  172. package/ui/src/components/TourOverlay.jsx +1 -1
  173. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  174. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  175. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  176. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  177. package/ui/src/components/TourTooltip.jsx +11 -3
  178. package/ui/src/components/ViewModeTabs.jsx +3 -1
  179. package/ui/src/config/tourSteps.jsx +0 -2
  180. package/ui/src/hooks/useAnimation.js +15 -12
  181. package/ui/src/hooks/useConfigManagement.js +8 -8
  182. package/ui/src/hooks/useServiceExtraction.js +1 -1
  183. package/ui/src/index.css +3 -8
  184. package/ui/src/theme.js +3 -3
  185. package/ui/src/utils/hexUtils.js +11 -5
  186. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  187. package/ui/src/utils/requestPairing.js +89 -0
  188. package/ui/src/utils/requestUtils.js +32 -101
  189. package/ui/vite.config.js +1 -1
  190. package/mcp-server/.editorconfig +0 -15
  191. package/mcp-server/.prettierignore +0 -11
  192. package/mcp-server/.prettierrc +0 -12
  193. package/mcp-server/README.md +0 -280
  194. package/mcp-server/commitlint.config.cjs +0 -42
  195. package/mcp-server/eslint.config.js +0 -131
  196. package/mcp-server/package-lock.json +0 -4784
  197. package/mcp-server/package.json +0 -30
  198. package/ui/README.md +0 -212
  199. package/ui/package-lock.json +0 -3574
  200. package/ui/package.json +0 -12
  201. package/ui/paths.js +0 -282
  202. package/ui/server/routes/backups.js +0 -251
  203. 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
- console.log(banner);
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
- console.log('Installing dependencies...');
74
+ logger.info('Installing dependencies...');
74
75
  try {
75
76
  await runCommand('npm', ['install'], { cwd: rootDir });
76
- console.log('Dependencies installed successfully!\n');
77
+ logger.info('Dependencies installed successfully!\n');
77
78
  } catch (error) {
78
- console.error('Failed to install dependencies:', error.message);
79
+ logger.error({ error: error.message }, 'Failed to install dependencies');
79
80
  process.exit(1);
80
81
  }
81
82
  }
82
83
 
83
84
  /**
84
- * Build the UI for production
85
+ * Validate that UI dist directory exists
85
86
  */
86
- async function buildUI() {
87
- console.log('Building UI for production...');
88
- try {
89
- await runCommand('npm', ['run', 'build'], { cwd: uiDir });
90
- } catch (error) {
91
- console.error('Failed to build UI:', error.message);
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
- console.log('Starting MCP Shark UI server...');
109
- console.log(`Open ${SERVER_URL} in your browser`);
110
- console.log('Press Ctrl+C to stop\n');
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
- let isShuttingDown = false;
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
- console.error(`Server exited with code ${code}`);
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) return;
138
- isShuttingDown = true;
138
+ if (shutdownState.isShuttingDown) {
139
+ return;
140
+ }
141
+ shutdownState.isShuttingDown = true;
139
142
 
140
- console.log('Shutting down...');
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
- console.log('Forcefully terminating server process...');
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
- console.error('Error: UI directory not found. Please ensure you are in the correct directory.');
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
- // Ensure UI is built
210
- await ensureUIBuilt();
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
- console.error('Unexpected error:', error);
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 function (chunk, ...args) {
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 function (chunk, ...args) {
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
- let done = false;
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
@@ -11,5 +11,5 @@ export function isError(error) {
11
11
  }
12
12
 
13
13
  export function getErrors(results) {
14
- return results.filter(result => isError(result));
14
+ return results.filter((result) => isError(result));
15
15
  }
@@ -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
- return buildKv(flattenedResults);
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 { makeTransport } from './transport.js';
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 { isError, CompositeError, getErrors } from '../../../common/error.js';
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
- logger,
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
- logger,
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
- logger,
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
- logger,
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