@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
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="description" content="MCP Shark - Wireshark-like forensic analysis tool for Model Context Protocol communications" />
7
+ <title>MCP Shark</title>
8
+ <link rel="icon" type="image/png" href="/og-image.png" />
9
+ <link rel="apple-touch-icon" href="/og-image.png" />
10
+ <script type="module" crossorigin src="/assets/index-srLDlk97.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-Cc-IUa83.css">
12
+ </head>
13
+ <body>
14
+ <div id="root"></div>
15
+ </body>
16
+ </html>
17
+
Binary file
@@ -0,0 +1,54 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ export function deleteBackup(req, res, mcpSharkLogs, broadcastLogUpdate) {
6
+ try {
7
+ const { backupPath } = req.body;
8
+
9
+ if (!backupPath) {
10
+ return res.status(400).json({ error: 'backupPath is required' });
11
+ }
12
+
13
+ const resolvedBackupPath = backupPath.startsWith('~')
14
+ ? path.join(homedir(), backupPath.slice(1))
15
+ : backupPath;
16
+
17
+ if (!fs.existsSync(resolvedBackupPath)) {
18
+ return res.status(404).json({ error: 'Backup file not found', path: resolvedBackupPath });
19
+ }
20
+
21
+ fs.unlinkSync(resolvedBackupPath);
22
+
23
+ const timestamp = new Date().toISOString();
24
+ const deleteLog = {
25
+ timestamp,
26
+ type: 'stdout',
27
+ line: `[DELETE] Deleted backup: ${resolvedBackupPath.replace(homedir(), '~')}`,
28
+ };
29
+ mcpSharkLogs.push(deleteLog);
30
+ if (mcpSharkLogs.length > 10000) {
31
+ mcpSharkLogs.shift();
32
+ }
33
+ broadcastLogUpdate(deleteLog);
34
+
35
+ res.json({
36
+ success: true,
37
+ message: 'Backup file deleted successfully',
38
+ backupPath: resolvedBackupPath.replace(homedir(), '~'),
39
+ });
40
+ } catch (error) {
41
+ const timestamp = new Date().toISOString();
42
+ const errorLog = {
43
+ timestamp,
44
+ type: 'error',
45
+ line: `[DELETE ERROR] Failed to delete backup: ${error.message}`,
46
+ };
47
+ mcpSharkLogs.push(errorLog);
48
+ if (mcpSharkLogs.length > 10000) {
49
+ mcpSharkLogs.shift();
50
+ }
51
+ broadcastLogUpdate(errorLog);
52
+ res.status(500).json({ error: 'Failed to delete backup', details: error.message });
53
+ }
54
+ }
@@ -0,0 +1,15 @@
1
+ import { deleteBackup } from './deleteBackup.js';
2
+ import { listBackups } from './listBackups.js';
3
+ import { restoreBackup } from './restoreBackup.js';
4
+ import { viewBackup } from './viewBackup.js';
5
+
6
+ export function createBackupRoutes() {
7
+ const router = {};
8
+
9
+ router.listBackups = listBackups;
10
+ router.restoreBackup = restoreBackup;
11
+ router.viewBackup = viewBackup;
12
+ router.deleteBackup = deleteBackup;
13
+
14
+ return router;
15
+ }
@@ -0,0 +1,75 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ export function listBackups(_req, res) {
6
+ try {
7
+ const backups = [];
8
+ const homeDir = homedir();
9
+
10
+ const commonPaths = [
11
+ path.join(homeDir, '.cursor', 'mcp.json'),
12
+ path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
13
+ ];
14
+
15
+ const backupDirs = [path.join(homeDir, '.cursor'), path.join(homeDir, '.codeium', 'windsurf')];
16
+
17
+ // Find backups with new format: .mcp.json-mcpshark.<datetime>.json
18
+ backupDirs.forEach((dir) => {
19
+ if (fs.existsSync(dir)) {
20
+ const files = fs.readdirSync(dir);
21
+ files
22
+ .filter((file) => {
23
+ // Match pattern: .<basename>-mcpshark.<datetime>.json
24
+ return /^\.(.+)-mcpshark\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.json$/.test(file);
25
+ })
26
+ .forEach((file) => {
27
+ const backupPath = path.join(dir, file);
28
+ // Extract original filename from backup name
29
+ // .mcp.json-mcpshark.<datetime>.json -> mcp.json
30
+ const match = file.match(/^\.(.+)-mcpshark\./);
31
+ if (match) {
32
+ const originalBasename = match[1];
33
+ const originalPath = path.join(dir, originalBasename);
34
+ const stats = fs.statSync(backupPath);
35
+ backups.push({
36
+ originalPath: originalPath,
37
+ backupPath: backupPath,
38
+ createdAt: stats.birthtime.toISOString(),
39
+ modifiedAt: stats.mtime.toISOString(),
40
+ size: stats.size,
41
+ displayPath: originalPath.replace(homeDir, '~'),
42
+ backupFileName: file,
43
+ });
44
+ }
45
+ });
46
+ }
47
+ });
48
+
49
+ // Also check for old .backup format for backward compatibility
50
+ commonPaths.forEach((configPath) => {
51
+ const backupPath = `${configPath}.backup`;
52
+ if (fs.existsSync(backupPath)) {
53
+ const stats = fs.statSync(backupPath);
54
+ backups.push({
55
+ originalPath: configPath,
56
+ backupPath: backupPath,
57
+ createdAt: stats.birthtime.toISOString(),
58
+ modifiedAt: stats.mtime.toISOString(),
59
+ size: stats.size,
60
+ displayPath: configPath.replace(homeDir, '~'),
61
+ backupFileName: path.basename(backupPath),
62
+ });
63
+ }
64
+ });
65
+
66
+ // Sort by modifiedAt (latest first)
67
+ res.json({
68
+ backups: backups.sort(
69
+ (a, b) => new Date(b.modifiedAt || b.createdAt) - new Date(a.modifiedAt || a.createdAt)
70
+ ),
71
+ });
72
+ } catch (error) {
73
+ res.status(500).json({ error: 'Failed to list backups', details: error.message });
74
+ }
75
+ }
@@ -0,0 +1,83 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ export function restoreBackup(req, res, mcpSharkLogs, broadcastLogUpdate) {
6
+ try {
7
+ const { backupPath, originalPath } = req.body;
8
+
9
+ if (!backupPath) {
10
+ return res.status(400).json({ error: 'backupPath is required' });
11
+ }
12
+
13
+ const resolvedBackupPath = backupPath.startsWith('~')
14
+ ? path.join(homedir(), backupPath.slice(1))
15
+ : backupPath;
16
+
17
+ if (!fs.existsSync(resolvedBackupPath)) {
18
+ return res.status(404).json({ error: 'Backup file not found', path: resolvedBackupPath });
19
+ }
20
+
21
+ // Determine original path
22
+ const determineTargetPath = (originalPath, backupPath) => {
23
+ if (originalPath) {
24
+ return originalPath.startsWith('~')
25
+ ? path.join(homedir(), originalPath.slice(1))
26
+ : originalPath;
27
+ }
28
+
29
+ // Try to extract from backup filename
30
+ if (backupPath.endsWith('.backup')) {
31
+ return backupPath.replace('.backup', '');
32
+ }
33
+
34
+ // New format: .mcp.json-mcpshark.<datetime>.json
35
+ const match = path.basename(backupPath).match(/^\.(.+)-mcpshark\./);
36
+ if (match) {
37
+ const originalBasename = match[1];
38
+ return path.join(path.dirname(backupPath), originalBasename);
39
+ }
40
+
41
+ return null;
42
+ };
43
+
44
+ const targetPath = determineTargetPath(originalPath, resolvedBackupPath);
45
+ if (!targetPath) {
46
+ return res.status(400).json({ error: 'Could not determine original file path' });
47
+ }
48
+
49
+ const backupContent = fs.readFileSync(resolvedBackupPath, 'utf8');
50
+ fs.writeFileSync(targetPath, backupContent);
51
+
52
+ const timestamp = new Date().toISOString();
53
+ const restoreLog = {
54
+ timestamp,
55
+ type: 'stdout',
56
+ line: `[RESTORE] Restored config from backup: ${targetPath.replace(homedir(), '~')}`,
57
+ };
58
+ mcpSharkLogs.push(restoreLog);
59
+ if (mcpSharkLogs.length > 10000) {
60
+ mcpSharkLogs.shift();
61
+ }
62
+ broadcastLogUpdate(restoreLog);
63
+
64
+ res.json({
65
+ success: true,
66
+ message: 'Config file restored from backup',
67
+ originalPath: targetPath.replace(homedir(), '~'),
68
+ });
69
+ } catch (error) {
70
+ const timestamp = new Date().toISOString();
71
+ const errorLog = {
72
+ timestamp,
73
+ type: 'error',
74
+ line: `[RESTORE ERROR] Failed to restore: ${error.message}`,
75
+ };
76
+ mcpSharkLogs.push(errorLog);
77
+ if (mcpSharkLogs.length > 10000) {
78
+ mcpSharkLogs.shift();
79
+ }
80
+ broadcastLogUpdate(errorLog);
81
+ res.status(500).json({ error: 'Failed to restore backup', details: error.message });
82
+ }
83
+ }
@@ -0,0 +1,47 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ function tryParseJson(content) {
6
+ try {
7
+ return JSON.parse(content);
8
+ } catch (_e) {
9
+ return null;
10
+ }
11
+ }
12
+
13
+ export function viewBackup(req, res) {
14
+ try {
15
+ const { backupPath } = req.query;
16
+
17
+ if (!backupPath) {
18
+ return res.status(400).json({ error: 'backupPath is required' });
19
+ }
20
+
21
+ const resolvedBackupPath = backupPath.startsWith('~')
22
+ ? path.join(homedir(), backupPath.slice(1))
23
+ : backupPath;
24
+
25
+ if (!fs.existsSync(resolvedBackupPath)) {
26
+ return res.status(404).json({ error: 'Backup file not found', path: resolvedBackupPath });
27
+ }
28
+
29
+ const content = fs.readFileSync(resolvedBackupPath, 'utf-8');
30
+ const parsed = tryParseJson(content);
31
+
32
+ const stats = fs.statSync(resolvedBackupPath);
33
+
34
+ res.json({
35
+ success: true,
36
+ backupPath: resolvedBackupPath,
37
+ displayPath: resolvedBackupPath.replace(homedir(), '~'),
38
+ content: content,
39
+ parsed: parsed,
40
+ createdAt: stats.birthtime.toISOString(),
41
+ modifiedAt: stats.mtime.toISOString(),
42
+ size: stats.size,
43
+ });
44
+ } catch (error) {
45
+ res.status(500).json({ error: 'Failed to read backup file', details: error.message });
46
+ }
47
+ }
@@ -0,0 +1,46 @@
1
+ import { getServers } from './servers.js';
2
+ import { setup } from './setup.js';
3
+ import { getStatus } from './status.js';
4
+ import { stop } from './stop.js';
5
+
6
+ export function createCompositeRoutes(
7
+ getMcpSharkProcess,
8
+ setMcpSharkProcess,
9
+ mcpSharkLogs,
10
+ broadcastLogUpdate
11
+ ) {
12
+ const router = {};
13
+
14
+ router.setup = async (req, res) => {
15
+ return setup(
16
+ req,
17
+ res,
18
+ getMcpSharkProcess,
19
+ setMcpSharkProcess,
20
+ mcpSharkLogs,
21
+ broadcastLogUpdate
22
+ );
23
+ };
24
+
25
+ router.stop = async (req, res, restoreOriginalConfig) => {
26
+ return stop(
27
+ req,
28
+ res,
29
+ restoreOriginalConfig,
30
+ getMcpSharkProcess,
31
+ setMcpSharkProcess,
32
+ mcpSharkLogs,
33
+ broadcastLogUpdate
34
+ );
35
+ };
36
+
37
+ router.getStatus = (req, res) => {
38
+ return getStatus(req, res, getMcpSharkProcess);
39
+ };
40
+
41
+ router.getServers = (req, res) => {
42
+ return getServers(req, res);
43
+ };
44
+
45
+ return router;
46
+ }
@@ -0,0 +1,18 @@
1
+ import * as fs from 'node:fs';
2
+ import { getMcpConfigPath } from 'mcp-shark-common/configs/index.js';
3
+
4
+ export function getServers(_req, res) {
5
+ try {
6
+ const mcpsJsonPath = getMcpConfigPath();
7
+ if (!fs.existsSync(mcpsJsonPath)) {
8
+ return res.json({ servers: [] });
9
+ }
10
+
11
+ const configContent = fs.readFileSync(mcpsJsonPath, 'utf-8');
12
+ const config = JSON.parse(configContent);
13
+ const servers = config.servers ? Object.keys(config.servers) : [];
14
+ res.json({ servers });
15
+ } catch (error) {
16
+ res.status(500).json({ error: 'Failed to get servers', details: error.message });
17
+ }
18
+ }
@@ -0,0 +1,129 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { startMcpSharkServer } from '@mcp-shark/mcp-shark/mcp-server';
5
+ import { getMcpConfigPath } from 'mcp-shark-common/configs/index.js';
6
+ import { getSelectedServiceNames, updateConfigFile } from '../../utils/config-update.js';
7
+ import { clearOriginalConfig, convertMcpServersToServers } from '../../utils/config.js';
8
+ import logger from '../../utils/logger.js';
9
+ import { createLogEntry } from '../../utils/process.js';
10
+ import { filterServers, parseJsonConfig, resolveFileData } from './utils.js';
11
+
12
+ const MAX_LOG_LINES = 10000;
13
+
14
+ export async function setup(
15
+ req,
16
+ res,
17
+ getMcpSharkProcess,
18
+ setMcpSharkProcess,
19
+ mcpSharkLogs,
20
+ broadcastLogUpdate
21
+ ) {
22
+ mcpSharkLogs.length = 0;
23
+ const logEntry = createLogEntry(mcpSharkLogs, broadcastLogUpdate);
24
+
25
+ try {
26
+ const { filePath, fileContent, selectedServices } = req.body;
27
+
28
+ if (!filePath && !fileContent) {
29
+ return res.status(400).json({ error: 'Either filePath or fileContent is required' });
30
+ }
31
+
32
+ const fileData = resolveFileData(filePath, fileContent);
33
+
34
+ if (!fileData) {
35
+ const resolvedFilePath = filePath.startsWith('~')
36
+ ? path.join(homedir(), filePath.slice(1))
37
+ : filePath;
38
+ return res.status(404).json({ error: 'File not found', path: resolvedFilePath });
39
+ }
40
+
41
+ const parseResult = parseJsonConfig(fileData.content);
42
+
43
+ if (!parseResult.config) {
44
+ return res.status(400).json({
45
+ error: 'Invalid JSON file',
46
+ details: parseResult.error ? parseResult.error.message : 'Failed to parse JSON',
47
+ });
48
+ }
49
+
50
+ const originalConfig = parseResult.config;
51
+ const baseConvertedConfig = convertMcpServersToServers(originalConfig);
52
+
53
+ const convertedConfig =
54
+ selectedServices && Array.isArray(selectedServices) && selectedServices.length > 0
55
+ ? filterServers(baseConvertedConfig, selectedServices)
56
+ : baseConvertedConfig;
57
+
58
+ if (Object.keys(convertedConfig.servers).length === 0) {
59
+ return res.status(400).json({ error: 'No servers found in config' });
60
+ }
61
+
62
+ const mcpsJsonPath = getMcpConfigPath();
63
+ fs.writeFileSync(mcpsJsonPath, JSON.stringify(convertedConfig, null, 2));
64
+ logger.info({ path: mcpsJsonPath }, 'Wrote converted config');
65
+
66
+ const currentServer = getMcpSharkProcess();
67
+ if (currentServer?.stop) {
68
+ await currentServer.stop();
69
+ setMcpSharkProcess(null);
70
+ }
71
+
72
+ logEntry('info', '[UI Server] Starting MCP-Shark server as library...');
73
+ logEntry('info', `[UI Server] Config: ${mcpsJsonPath}`);
74
+
75
+ const serverInstance = await startMcpSharkServer({
76
+ configPath: mcpsJsonPath,
77
+ port: 9851,
78
+ onError: (err) => {
79
+ logEntry('error', `Failed to start mcp-shark server: ${err.message}`);
80
+ setMcpSharkProcess(null);
81
+ throw err;
82
+ },
83
+ onReady: () => {
84
+ logEntry('info', 'MCP Shark server is ready!');
85
+ logger.info('MCP Shark server is ready!');
86
+ },
87
+ });
88
+
89
+ setMcpSharkProcess(serverInstance);
90
+ logger.info('MCP Shark server started successfully');
91
+
92
+ const selectedServiceNames = getSelectedServiceNames(originalConfig, selectedServices);
93
+ const { updatedConfig, backupPath: createdBackupPath } = updateConfigFile(
94
+ originalConfig,
95
+ selectedServiceNames,
96
+ fileData.resolvedFilePath,
97
+ fileData.content,
98
+ mcpSharkLogs,
99
+ broadcastLogUpdate
100
+ );
101
+
102
+ if (!fileData.resolvedFilePath) {
103
+ clearOriginalConfig();
104
+ }
105
+
106
+ res.json({
107
+ success: true,
108
+ message: 'MCP Shark server started successfully and config file updated',
109
+ convertedConfig,
110
+ updatedConfig,
111
+ filePath: fileData.resolvedFilePath || null,
112
+ backupPath: createdBackupPath || null,
113
+ });
114
+ } catch (error) {
115
+ logger.error({ error: error.message }, 'Error setting up mcp-shark server');
116
+ const timestamp = new Date().toISOString();
117
+ const errorLog = {
118
+ timestamp,
119
+ type: 'error',
120
+ line: `[ERROR] Failed to setup mcp-shark server: ${error.message}`,
121
+ };
122
+ mcpSharkLogs.push(errorLog);
123
+ if (mcpSharkLogs.length > MAX_LOG_LINES) {
124
+ mcpSharkLogs.shift();
125
+ }
126
+ broadcastLogUpdate(errorLog);
127
+ res.status(500).json({ error: 'Failed to setup mcp-shark server', details: error.message });
128
+ }
129
+ }
@@ -0,0 +1,7 @@
1
+ export function getStatus(_req, res, getMcpSharkProcess) {
2
+ const currentServer = getMcpSharkProcess();
3
+ res.json({
4
+ running: currentServer !== null,
5
+ pid: null, // No process PID when using library
6
+ });
7
+ }
@@ -0,0 +1,39 @@
1
+ export async function stop(
2
+ _req,
3
+ res,
4
+ restoreOriginalConfig,
5
+ getMcpSharkProcess,
6
+ setMcpSharkProcess,
7
+ mcpSharkLogs,
8
+ broadcastLogUpdate
9
+ ) {
10
+ try {
11
+ const currentServer = getMcpSharkProcess();
12
+ if (currentServer?.stop) {
13
+ await currentServer.stop();
14
+ setMcpSharkProcess(null);
15
+
16
+ const restored = restoreOriginalConfig();
17
+
18
+ if (restored) {
19
+ const timestamp = new Date().toISOString();
20
+ const restoreLog = {
21
+ timestamp,
22
+ type: 'stdout',
23
+ line: '[RESTORE] Restored original config',
24
+ };
25
+ mcpSharkLogs.push(restoreLog);
26
+ if (mcpSharkLogs.length > 10000) {
27
+ mcpSharkLogs.shift();
28
+ }
29
+ broadcastLogUpdate(restoreLog);
30
+ }
31
+
32
+ res.json({ success: true, message: 'MCP Shark server stopped and config restored' });
33
+ } else {
34
+ res.json({ success: true, message: 'MCP Shark server was not running' });
35
+ }
36
+ } catch (error) {
37
+ res.status(500).json({ error: 'Failed to stop mcp-shark server', details: error.message });
38
+ }
39
+ }
@@ -0,0 +1,45 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ export function resolveFileData(filePath, fileContent) {
6
+ if (fileContent) {
7
+ const resolvedFilePath = filePath
8
+ ? filePath.startsWith('~')
9
+ ? path.join(homedir(), filePath.slice(1))
10
+ : filePath
11
+ : null;
12
+ return { content: fileContent, resolvedFilePath };
13
+ }
14
+
15
+ const resolvedFilePath = filePath.startsWith('~')
16
+ ? path.join(homedir(), filePath.slice(1))
17
+ : filePath;
18
+
19
+ if (!fs.existsSync(resolvedFilePath)) {
20
+ return null;
21
+ }
22
+
23
+ return {
24
+ content: fs.readFileSync(resolvedFilePath, 'utf-8'),
25
+ resolvedFilePath,
26
+ };
27
+ }
28
+
29
+ export function parseJsonConfig(content) {
30
+ try {
31
+ return { config: JSON.parse(content), error: null };
32
+ } catch (e) {
33
+ return { config: null, error: e };
34
+ }
35
+ }
36
+
37
+ export function filterServers(config, services) {
38
+ const filteredServers = {};
39
+ services.forEach((serviceName) => {
40
+ if (config.servers[serviceName]) {
41
+ filteredServers[serviceName] = config.servers[serviceName];
42
+ }
43
+ });
44
+ return { servers: filteredServers };
45
+ }
@@ -1,8 +1,35 @@
1
- import * as path from 'node:path';
2
1
  import * as fs from 'node:fs';
3
2
  import { homedir } from 'node:os';
4
- import { extractServices, convertMcpServersToServers } from '../utils/config.js';
5
- import { createBackupRoutes } from './backups.js';
3
+ import * as path from 'node:path';
4
+ import { extractServices } from '../utils/config.js';
5
+ import { createBackupRoutes } from './backups/index.js';
6
+
7
+ function readFileContent(filePath) {
8
+ const resolvedFilePath = filePath.startsWith('~')
9
+ ? path.join(homedir(), filePath.slice(1))
10
+ : filePath;
11
+
12
+ if (!fs.existsSync(resolvedFilePath)) {
13
+ return null;
14
+ }
15
+ return fs.readFileSync(resolvedFilePath, 'utf-8');
16
+ }
17
+
18
+ function parseJsonSafely(content) {
19
+ try {
20
+ return { config: JSON.parse(content), error: null };
21
+ } catch (e) {
22
+ return { config: null, error: e };
23
+ }
24
+ }
25
+
26
+ function tryParseJson(content) {
27
+ try {
28
+ return JSON.parse(content);
29
+ } catch (_e) {
30
+ return null;
31
+ }
32
+ }
6
33
 
7
34
  export function createConfigRoutes() {
8
35
  const router = {};
@@ -15,18 +42,7 @@ export function createConfigRoutes() {
15
42
  return res.status(400).json({ error: 'Either filePath or fileContent is required' });
16
43
  }
17
44
 
18
- const content = fileContent
19
- ? fileContent
20
- : (() => {
21
- const resolvedFilePath = filePath.startsWith('~')
22
- ? path.join(homedir(), filePath.slice(1))
23
- : filePath;
24
-
25
- if (!fs.existsSync(resolvedFilePath)) {
26
- return null;
27
- }
28
- return fs.readFileSync(resolvedFilePath, 'utf-8');
29
- })();
45
+ const content = fileContent ? fileContent : readFileContent(filePath);
30
46
 
31
47
  if (!content) {
32
48
  const resolvedFilePath = filePath.startsWith('~')
@@ -35,13 +51,7 @@ export function createConfigRoutes() {
35
51
  return res.status(404).json({ error: 'File not found', path: resolvedFilePath });
36
52
  }
37
53
 
38
- const parseResult = (() => {
39
- try {
40
- return { config: JSON.parse(content), error: null };
41
- } catch (e) {
42
- return { config: null, error: e };
43
- }
44
- })();
54
+ const parseResult = parseJsonSafely(content);
45
55
 
46
56
  if (!parseResult.config) {
47
57
  return res.status(400).json({
@@ -76,13 +86,7 @@ export function createConfigRoutes() {
76
86
  }
77
87
 
78
88
  const content = fs.readFileSync(resolvedPath, 'utf-8');
79
- const parsed = (() => {
80
- try {
81
- return JSON.parse(content);
82
- } catch (e) {
83
- return null;
84
- }
85
- })();
89
+ const parsed = tryParseJson(content);
86
90
 
87
91
  res.json({
88
92
  success: true,
@@ -97,7 +101,7 @@ export function createConfigRoutes() {
97
101
  }
98
102
  };
99
103
 
100
- router.detectConfig = (req, res) => {
104
+ router.detectConfig = (_req, res) => {
101
105
  const detected = [];
102
106
  const platform = process.platform;
103
107
  const homeDir = homedir();