@mcp-shark/mcp-shark 1.4.2 → 1.5.2

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 (204) 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/shared/logger.js +90 -0
  23. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  24. package/ui/dist/assets/index-srLDlk97.js +35 -0
  25. package/ui/dist/index.html +17 -0
  26. package/ui/dist/og-image.png +0 -0
  27. package/ui/server/routes/backups/deleteBackup.js +54 -0
  28. package/ui/server/routes/backups/index.js +15 -0
  29. package/ui/server/routes/backups/listBackups.js +75 -0
  30. package/ui/server/routes/backups/restoreBackup.js +83 -0
  31. package/ui/server/routes/backups/viewBackup.js +47 -0
  32. package/ui/server/routes/composite/index.js +46 -0
  33. package/ui/server/routes/composite/servers.js +18 -0
  34. package/ui/server/routes/composite/setup.js +129 -0
  35. package/ui/server/routes/composite/status.js +7 -0
  36. package/ui/server/routes/composite/stop.js +39 -0
  37. package/ui/server/routes/composite/utils.js +45 -0
  38. package/ui/server/routes/config.js +34 -30
  39. package/ui/server/routes/conversations.js +3 -3
  40. package/ui/server/routes/help.js +2 -2
  41. package/ui/server/routes/logs.js +5 -5
  42. package/ui/server/routes/playground.js +45 -47
  43. package/ui/server/routes/requests.js +112 -108
  44. package/ui/server/routes/sessions.js +4 -4
  45. package/ui/server/routes/settings.js +199 -0
  46. package/ui/server/routes/smartscan/discover.js +7 -6
  47. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  48. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  49. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  51. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  52. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  53. package/ui/server/routes/smartscan/scans.js +3 -3
  54. package/ui/server/routes/smartscan/token.js +4 -3
  55. package/ui/server/routes/smartscan/transport.js +1 -1
  56. package/ui/server/routes/smartscan.js +1 -1
  57. package/ui/server/routes/statistics.js +13 -10
  58. package/ui/server/utils/config-update.js +7 -6
  59. package/ui/server/utils/config.js +4 -4
  60. package/ui/server/utils/logger.js +2 -0
  61. package/ui/server/utils/paths.js +210 -2
  62. package/ui/server/utils/port.js +2 -2
  63. package/ui/server/utils/process.js +0 -67
  64. package/ui/server/utils/scan-cache/all-results.js +76 -59
  65. package/ui/server/utils/scan-cache/directory.js +1 -1
  66. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  67. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  68. package/ui/server/utils/serialization.js +9 -3
  69. package/ui/server/utils/smartscan-token.js +4 -3
  70. package/ui/server.js +86 -41
  71. package/ui/src/App.jsx +5 -5
  72. package/ui/src/CompositeLogs.jsx +20 -20
  73. package/ui/src/CompositeSetup.jsx +9 -9
  74. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  75. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  76. package/ui/src/HelpGuide.jsx +17 -4
  77. package/ui/src/IntroTour.jsx +19 -5
  78. package/ui/src/LogDetail.jsx +1 -0
  79. package/ui/src/LogTable.jsx +24 -6
  80. package/ui/src/PacketDetail.jsx +21 -16
  81. package/ui/src/PacketFilters.jsx +29 -14
  82. package/ui/src/PacketList.jsx +4 -5
  83. package/ui/src/SmartScan.jsx +5 -5
  84. package/ui/src/TabNavigation.jsx +5 -5
  85. package/ui/src/components/App/HelpButton.jsx +4 -0
  86. package/ui/src/components/App/TrafficTab.jsx +4 -4
  87. package/ui/src/components/App/useAppState.js +118 -24
  88. package/ui/src/components/BackupList.jsx +6 -2
  89. package/ui/src/components/CollapsibleSection.jsx +16 -2
  90. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  91. package/ui/src/components/ConfirmationModal.jsx +20 -3
  92. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  93. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  94. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  95. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  97. package/ui/src/components/DetectedPathsList.jsx +5 -2
  98. package/ui/src/components/FileInput.jsx +3 -1
  99. package/ui/src/components/GroupHeader.jsx +14 -0
  100. package/ui/src/components/GroupedByMcpView.jsx +3 -4
  101. package/ui/src/components/GroupedByServerView.jsx +1 -1
  102. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  103. package/ui/src/components/HexTab.jsx +17 -4
  104. package/ui/src/components/LogsToolbar.jsx +3 -1
  105. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
  108. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
  109. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
  112. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
  113. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
  116. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
  117. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  118. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
  119. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
  120. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
  121. package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
  122. package/ui/src/components/McpPlayground.jsx +5 -2
  123. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  124. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  125. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  126. package/ui/src/components/RawTab.jsx +15 -2
  127. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  128. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  129. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  130. package/ui/src/components/RequestRow.jsx +17 -9
  131. package/ui/src/components/ServerControl.jsx +3 -1
  132. package/ui/src/components/ServiceSelector.jsx +2 -0
  133. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  136. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  137. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  138. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  139. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  140. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  141. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  142. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  143. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  144. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  145. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  146. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  147. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  148. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  149. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  150. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  151. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  152. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  153. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  154. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  155. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  156. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  157. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  158. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  159. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  160. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  161. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  162. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  163. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  164. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  165. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  166. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  167. package/ui/src/components/SmartScan/utils.js +3 -1
  168. package/ui/src/components/SmartScanIcons.jsx +6 -3
  169. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  170. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  171. package/ui/src/components/TabNavigation.jsx +8 -3
  172. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  173. package/ui/src/components/TourOverlay.jsx +1 -1
  174. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  175. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  176. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  177. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  178. package/ui/src/components/TourTooltip.jsx +11 -3
  179. package/ui/src/components/ViewModeTabs.jsx +3 -1
  180. package/ui/src/config/tourSteps.jsx +0 -2
  181. package/ui/src/hooks/useAnimation.js +15 -12
  182. package/ui/src/hooks/useConfigManagement.js +8 -8
  183. package/ui/src/hooks/useServiceExtraction.js +1 -1
  184. package/ui/src/index.css +3 -8
  185. package/ui/src/theme.js +3 -3
  186. package/ui/src/utils/hexUtils.js +11 -5
  187. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  188. package/ui/src/utils/requestPairing.js +89 -0
  189. package/ui/src/utils/requestUtils.js +32 -101
  190. package/ui/vite.config.js +1 -1
  191. package/mcp-server/.editorconfig +0 -15
  192. package/mcp-server/.prettierignore +0 -11
  193. package/mcp-server/.prettierrc +0 -12
  194. package/mcp-server/README.md +0 -280
  195. package/mcp-server/commitlint.config.cjs +0 -42
  196. package/mcp-server/eslint.config.js +0 -131
  197. package/mcp-server/package-lock.json +0 -4784
  198. package/mcp-server/package.json +0 -30
  199. package/ui/README.md +0 -212
  200. package/ui/package-lock.json +0 -3574
  201. package/ui/package.json +0 -12
  202. package/ui/paths.js +0 -282
  203. package/ui/server/routes/backups.js +0 -251
  204. package/ui/server/routes/composite.js +0 -260
@@ -1,10 +1,26 @@
1
- import * as path from 'node:path';
1
+ import { execSync } from 'node:child_process';
2
2
  import * as fs from 'node:fs';
3
- import { fileURLToPath } from 'url';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import logger from './logger.js';
4
7
 
5
8
  const __filename = fileURLToPath(import.meta.url);
6
9
  const __dirname = path.dirname(__filename);
7
10
 
11
+ function getNvmNodeBinPaths(homeDir) {
12
+ try {
13
+ const nvmVersionsPath = path.join(homeDir, '.nvm', 'versions', 'node');
14
+ if (fs.existsSync(nvmVersionsPath)) {
15
+ return fs
16
+ .readdirSync(nvmVersionsPath, { withFileTypes: true })
17
+ .filter((dirent) => dirent.isDirectory())
18
+ .map((dirent) => path.join(nvmVersionsPath, dirent.name, 'bin'));
19
+ }
20
+ } catch (_e) {}
21
+ return [];
22
+ }
23
+
8
24
  export function findMcpServerPath() {
9
25
  const pathsToCheck = [
10
26
  path.join(process.cwd(), '../mcp-server'),
@@ -21,3 +37,195 @@ export function findMcpServerPath() {
21
37
 
22
38
  return path.join(process.cwd(), '../mcp-server');
23
39
  }
40
+
41
+ /**
42
+ * Get system PATH from the host machine's shell environment
43
+ * This works in Electron by executing a shell command to get the actual PATH
44
+ * Includes both system PATH and user's custom PATH from shell config files
45
+ */
46
+ function getSystemPath() {
47
+ try {
48
+ if (process.platform === 'win32') {
49
+ const pathOutput = execSync('cmd /c echo %PATH%', {
50
+ encoding: 'utf8',
51
+ timeout: 2000,
52
+ stdio: ['ignore', 'pipe', 'ignore'],
53
+ });
54
+ return pathOutput.trim();
55
+ }
56
+ const userShell = process.env.SHELL || '/bin/zsh';
57
+ const shells = [userShell, '/bin/zsh', '/bin/bash', '/bin/sh'];
58
+
59
+ for (const shell of shells) {
60
+ if (fs.existsSync(shell)) {
61
+ try {
62
+ const shellName = path.basename(shell);
63
+
64
+ const getPathOutput = (shell, shellName) => {
65
+ const execOptions = {
66
+ encoding: 'utf8',
67
+ timeout: 2000,
68
+ stdio: ['ignore', 'pipe', 'ignore'],
69
+ maxBuffer: 1024 * 1024,
70
+ env: {
71
+ ...Object.fromEntries(
72
+ Object.entries(process.env).filter(([key]) => key !== 'PATH')
73
+ ),
74
+ },
75
+ };
76
+
77
+ if (shellName === 'zsh') {
78
+ try {
79
+ return execSync(`${shell} -i -c 'echo $PATH'`, execOptions);
80
+ } catch (_e) {
81
+ return execSync(`${shell} -l -c 'echo $PATH'`, execOptions);
82
+ }
83
+ }
84
+
85
+ return execSync(`${shell} -l -c 'echo $PATH'`, execOptions);
86
+ };
87
+
88
+ const pathOutput = getPathOutput(shell, shellName);
89
+ const systemPath = pathOutput.trim();
90
+ if (systemPath) {
91
+ logger.info({ shell, shellName }, 'Got PATH from shell');
92
+ return systemPath;
93
+ }
94
+ } catch (_e) {}
95
+ }
96
+ }
97
+
98
+ const homeDir = os.homedir();
99
+ const configFiles = [
100
+ { file: path.join(homeDir, '.zshrc'), shell: 'zsh', interactive: true },
101
+ { file: path.join(homeDir, '.zprofile'), shell: 'zsh', interactive: false },
102
+ { file: path.join(homeDir, '.zlogin'), shell: 'zsh', interactive: false },
103
+ { file: path.join(homeDir, '.bashrc'), shell: 'bash', interactive: true },
104
+ { file: path.join(homeDir, '.bash_profile'), shell: 'bash', interactive: false },
105
+ { file: path.join(homeDir, '.profile'), shell: 'sh', interactive: false },
106
+ ];
107
+
108
+ for (const { file, shell: shellName, interactive } of configFiles) {
109
+ if (fs.existsSync(file)) {
110
+ try {
111
+ const flag = shellName === 'zsh' && interactive ? '-i' : '';
112
+ const pathOutput = execSync(
113
+ `/bin/${shellName} ${flag} -c 'source ${file} 2>/dev/null; echo $PATH'`,
114
+ {
115
+ encoding: 'utf8',
116
+ timeout: 2000,
117
+ stdio: ['ignore', 'pipe', 'ignore'],
118
+ maxBuffer: 1024 * 1024,
119
+ env: {
120
+ ...Object.fromEntries(
121
+ Object.entries(process.env).filter(([key]) => key !== 'PATH')
122
+ ),
123
+ },
124
+ }
125
+ );
126
+ const systemPath = pathOutput.trim();
127
+ if (systemPath && systemPath.length > 10) {
128
+ logger.info({ file }, 'Got PATH from file');
129
+ return systemPath;
130
+ }
131
+ } catch (_e) {}
132
+ }
133
+ }
134
+ } catch (error) {
135
+ logger.warn({ error: error.message }, 'Could not get system PATH');
136
+ }
137
+ return null;
138
+ }
139
+
140
+ /**
141
+ * Enhance PATH environment variable to include system paths and user paths
142
+ * This is especially important in Electron where PATH might not include system executables
143
+ */
144
+ export function enhancePath(originalPath) {
145
+ const homeDir = os.homedir();
146
+ const pathSeparator = process.platform === 'win32' ? ';' : ':';
147
+
148
+ const systemPath = getSystemPath();
149
+ if (systemPath) {
150
+ logger.info('Using system PATH from host machine');
151
+ const userPaths = [
152
+ path.join(homeDir, '.local', 'bin'),
153
+ path.join(homeDir, '.npm-global', 'bin'),
154
+ path.join(homeDir, '.cargo', 'bin'),
155
+ path.join(homeDir, 'bin'),
156
+ path.join(homeDir, '.nvm', 'current', 'bin'),
157
+ ...getNvmNodeBinPaths(homeDir),
158
+ path.join(homeDir, '.fnm', 'node-versions', 'v20.0.0', 'install', 'bin'),
159
+ path.join(homeDir, '.pyenv', 'shims'),
160
+ path.join(homeDir, '.pyenv', 'bin'),
161
+ path.join(homeDir, '.gvm', 'bin'),
162
+ path.join(homeDir, '.gvm', 'gos', 'current', 'bin'),
163
+ path.join(homeDir, '.cargo', 'bin'),
164
+ path.join(homeDir, 'go', 'bin'),
165
+ path.join(homeDir, '.go', 'bin'),
166
+ '/Applications/iTerm.app/Contents/Resources/utilities',
167
+ ...(process.platform === 'win32'
168
+ ? [
169
+ path.join(homeDir, 'AppData', 'Local', 'Programs'),
170
+ path.join(homeDir, 'AppData', 'Roaming', 'npm'),
171
+ path.join(homeDir, 'AppData', 'Local', 'Microsoft', 'WindowsApps'),
172
+ ]
173
+ : []),
174
+ ].filter((p) => {
175
+ if (p.includes('v20.0.0') || p.includes('current')) {
176
+ return fs.existsSync(path.dirname(p));
177
+ }
178
+ return fs.existsSync(p);
179
+ });
180
+
181
+ return [systemPath, ...userPaths, originalPath || ''].filter((p) => p).join(pathSeparator);
182
+ }
183
+
184
+ logger.info('Could not get system PATH, adding common locations');
185
+ const pathsToAdd = [
186
+ '/usr/local/bin',
187
+ '/usr/bin',
188
+ '/opt/homebrew/bin',
189
+ '/usr/local/opt/node/bin',
190
+ '/opt/local/bin',
191
+ '/sbin',
192
+ '/usr/sbin',
193
+ ...(process.platform === 'darwin'
194
+ ? [
195
+ '/opt/homebrew/opt/python/bin',
196
+ '/usr/local/opt/python/bin',
197
+ '/Applications/Docker.app/Contents/Resources/bin',
198
+ ]
199
+ : []),
200
+ ...(process.platform === 'linux' ? ['/snap/bin', path.join(homeDir, '.local', 'bin')] : []),
201
+ ...(process.platform === 'win32'
202
+ ? [
203
+ path.join(process.env.ProgramFiles || '', 'nodejs'),
204
+ path.join(process.env['ProgramFiles(x86)'] || '', 'nodejs'),
205
+ path.join(homeDir, 'AppData', 'Roaming', 'npm'),
206
+ path.join(process.env.ProgramFiles || '', 'Docker', 'Docker', 'resources', 'bin'),
207
+ ]
208
+ : []),
209
+ path.join(homeDir, '.local', 'bin'),
210
+ path.join(homeDir, '.npm-global', 'bin'),
211
+ path.join(homeDir, '.cargo', 'bin'),
212
+ path.join(homeDir, 'bin'),
213
+ path.join(homeDir, '.nvm', 'current', 'bin'),
214
+ ...getNvmNodeBinPaths(homeDir),
215
+ path.join(homeDir, '.pyenv', 'shims'),
216
+ path.join(homeDir, '.pyenv', 'bin'),
217
+ path.join(homeDir, '.gvm', 'bin'),
218
+ path.join(homeDir, '.gvm', 'gos', 'current', 'bin'),
219
+ path.join(homeDir, 'go', 'bin'),
220
+ path.join(homeDir, '.go', 'bin'),
221
+ '/Applications/iTerm.app/Contents/Resources/utilities',
222
+ ...(process.platform === 'win32'
223
+ ? [
224
+ path.join(homeDir, 'AppData', 'Local', 'Programs'),
225
+ path.join(homeDir, 'AppData', 'Local', 'Microsoft', 'WindowsApps'),
226
+ ]
227
+ : []),
228
+ ].filter((p) => p && fs.existsSync(p));
229
+
230
+ return [...pathsToAdd, originalPath || ''].filter((p) => p).join(pathSeparator);
231
+ }
@@ -1,4 +1,4 @@
1
- import { createConnection } from 'net';
1
+ import { createConnection } from 'node:net';
2
2
 
3
3
  export function checkPortReady(port, host = 'localhost', timeout = 10000) {
4
4
  return new Promise((resolve, reject) => {
@@ -12,7 +12,7 @@ export function checkPortReady(port, host = 'localhost', timeout = 10000) {
12
12
  resolve(true);
13
13
  });
14
14
 
15
- socket.on('error', (err) => {
15
+ socket.on('error', (_err) => {
16
16
  socket.destroy();
17
17
  const elapsed = Date.now() - startTime;
18
18
  if (elapsed >= timeout) {
@@ -1,9 +1,3 @@
1
- import { spawn } from 'node:child_process';
2
- import * as path from 'node:path';
3
- import { findMcpServerPath } from './paths.js';
4
- import { enhancePath } from '../../paths.js';
5
- import { getMcpConfigPath, getWorkingDirectory } from 'mcp-shark-common/configs/index.js';
6
-
7
1
  const MAX_LOG_LINES = 10000;
8
2
 
9
3
  export function createLogEntry(mcpSharkLogs, broadcastLogUpdate) {
@@ -17,64 +11,3 @@ export function createLogEntry(mcpSharkLogs, broadcastLogUpdate) {
17
11
  broadcastLogUpdate({ timestamp, type, line });
18
12
  };
19
13
  }
20
-
21
- export function spawnMcpSharkServer(mcpSharkJsPath, mcpsJsonPath, logEntry) {
22
- const mcpServerPath = findMcpServerPath();
23
- const nodeExecutable = process.execPath || 'node';
24
- const enhancedPath = enhancePath(process.env.PATH);
25
-
26
- logEntry('info', `[UI Server] Spawning MCP-Shark server...`);
27
- logEntry('info', `[UI Server] Executable: ${nodeExecutable}`);
28
- logEntry('info', `[UI Server] Script: ${mcpSharkJsPath}`);
29
- logEntry('info', `[UI Server] Config: ${mcpsJsonPath}`);
30
- logEntry('info', `[UI Server] CWD: ${mcpServerPath}`);
31
- logEntry('info', `[UI Server] Data dir: ${getWorkingDirectory()}`);
32
- logEntry('info', `[UI Server] Enhanced PATH: ${enhancedPath}`);
33
-
34
- const processHandle = spawn(nodeExecutable, [mcpSharkJsPath, mcpsJsonPath], {
35
- cwd: mcpServerPath,
36
- stdio: ['ignore', 'pipe', 'pipe'],
37
- env: {
38
- ...process.env,
39
- PATH: enhancedPath,
40
- },
41
- });
42
-
43
- console.log(`[UI Server] MCP-Shark process spawned with PID: ${processHandle.pid}`);
44
-
45
- processHandle.stdout.on('data', (data) => {
46
- logEntry('stdout', data);
47
- process.stdout.write(data);
48
- });
49
-
50
- processHandle.stderr.on('data', (data) => {
51
- logEntry('stderr', data);
52
- process.stderr.write(data);
53
- });
54
-
55
- return processHandle;
56
- }
57
-
58
- export function setupProcessHandlers(processHandle, logEntry, onError, onExit) {
59
- processHandle.on('error', (err) => {
60
- console.error('Failed to start mcp-shark server:', err);
61
- logEntry('error', `Failed to start mcp-shark server: ${err.message}`);
62
- if (onError) onError(err);
63
- });
64
-
65
- processHandle.on('exit', (code, signal) => {
66
- const message = `MCP Shark server process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`;
67
- console.log(`[UI Server] ${message}`);
68
- logEntry('exit', message);
69
- if (code !== 0 && code !== null) {
70
- console.error(`[UI Server] MCP-Shark process exited with non-zero code: ${code}`);
71
- logEntry('error', `Process exited with code ${code}`);
72
- }
73
- if (onExit) onExit(code, signal);
74
- });
75
- }
76
-
77
- export function getMcpSharkJsPath() {
78
- const mcpServerPath = findMcpServerPath();
79
- return path.join(mcpServerPath, 'mcp-shark.js');
80
- }
@@ -1,5 +1,6 @@
1
- import { readFileSync, existsSync, readdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import logger from '../logger.js';
3
4
  import { getScanResultsDirectory } from './directory.js';
4
5
 
5
6
  /**
@@ -9,13 +10,13 @@ import { getScanResultsDirectory } from './directory.js';
9
10
  * @returns {string} Server name
10
11
  */
11
12
  function extractServerName(data, scanData) {
12
- let serverName = data.serverName; // From cache file metadata (most reliable)
13
+ const cachedServerName = data.serverName; // From cache file metadata (most reliable)
13
14
 
14
15
  // If not found at top level, try to extract from scan data (API response)
15
- if (!serverName || serverName === 'Unknown Server') {
16
+ if (!cachedServerName || cachedServerName === 'Unknown Server') {
16
17
  const actualScanData = scanData.data || scanData;
17
18
 
18
- serverName =
19
+ const serverName =
19
20
  actualScanData.serverName ||
20
21
  actualScanData.server_name ||
21
22
  actualScanData.server?.name ||
@@ -25,9 +26,11 @@ function extractServerName(data, scanData) {
25
26
  scanData.server?.name ||
26
27
  scanData.mcp_server_data?.server?.name ||
27
28
  'Unknown Server';
29
+
30
+ return serverName;
28
31
  }
29
32
 
30
- return serverName;
33
+ return cachedServerName;
31
34
  }
32
35
 
33
36
  /**
@@ -39,25 +42,29 @@ function extractServerName(data, scanData) {
39
42
  function processScanFile(file, scanResultsDir) {
40
43
  try {
41
44
  const filePath = join(scanResultsDir, file);
42
- console.log(`[getAllCachedScanResults] Reading file: ${file}`);
45
+ logger.debug({ file }, 'Reading cached scan result file');
43
46
 
44
47
  const fileContent = readFileSync(filePath, 'utf8');
45
48
  const data = JSON.parse(fileContent);
46
49
 
47
50
  // Validate that this is a scan result file
48
51
  if (!data || typeof data !== 'object') {
49
- console.warn(`[getAllCachedScanResults] Invalid data in file ${file}: not an object`);
52
+ logger.warn({ file }, 'Invalid data in file: not an object');
50
53
  return null;
51
54
  }
52
55
 
53
56
  // Debug: Log the structure to understand what we're working with
54
- console.log(`[getAllCachedScanResults] File ${file} structure:`, {
55
- hasServerName: !!data.serverName,
56
- serverName: data.serverName,
57
- hasScanData: !!data.scanData,
58
- scanDataKeys: data.scanData ? Object.keys(data.scanData) : [],
59
- topLevelKeys: Object.keys(data),
60
- });
57
+ logger.debug(
58
+ {
59
+ file,
60
+ hasServerName: !!data.serverName,
61
+ serverName: data.serverName,
62
+ hasScanData: !!data.scanData,
63
+ scanDataKeys: data.scanData ? Object.keys(data.scanData) : [],
64
+ topLevelKeys: Object.keys(data),
65
+ },
66
+ 'File structure'
67
+ );
61
68
 
62
69
  // Create a scan-like object with id, server name, and scan data
63
70
  const scanData = data.scanData || data;
@@ -66,17 +73,16 @@ function processScanFile(file, scanResultsDir) {
66
73
 
67
74
  // Log if we couldn't find server name
68
75
  if (serverName === 'Unknown Server') {
69
- console.warn(`[getAllCachedScanResults] Could not find server name in ${file}`);
70
- console.warn(`[getAllCachedScanResults] data.serverName: ${data.serverName}`);
71
- console.warn(`[getAllCachedScanResults] data keys: ${Object.keys(data || {}).join(', ')}`);
72
- console.warn(
73
- `[getAllCachedScanResults] scanData keys: ${Object.keys(scanData || {}).join(', ')}`
76
+ logger.warn(
77
+ {
78
+ file,
79
+ dataServerName: data.serverName,
80
+ dataKeys: Object.keys(data || {}).join(', '),
81
+ scanDataKeys: Object.keys(scanData || {}).join(', '),
82
+ scanDataDataKeys: scanData?.data ? Object.keys(scanData.data || {}).join(', ') : null,
83
+ },
84
+ 'Could not find server name in file'
74
85
  );
75
- if (scanData?.data) {
76
- console.warn(
77
- `[getAllCachedScanResults] scanData.data keys: ${Object.keys(scanData.data || {}).join(', ')}`
78
- );
79
- }
80
86
  }
81
87
 
82
88
  const scanResult = {
@@ -98,17 +104,18 @@ function processScanFile(file, scanResultsDir) {
98
104
  result: scanData,
99
105
  };
100
106
 
101
- console.log(`[getAllCachedScanResults] Successfully loaded scan: ${serverName} (${scanId})`);
107
+ logger.debug({ serverName, scanId }, 'Successfully loaded scan');
102
108
  return scanResult;
103
109
  } catch (error) {
104
- console.warn(
105
- `[getAllCachedScanResults] Error reading scan result file ${file}:`,
106
- error.message
110
+ logger.warn(
111
+ {
112
+ file,
113
+ filePath: join(scanResultsDir, file),
114
+ error: error.message,
115
+ stack: error.stack,
116
+ },
117
+ 'Error reading scan result file'
107
118
  );
108
- console.warn(`[getAllCachedScanResults] File path: ${join(scanResultsDir, file)}`);
109
- if (error.stack) {
110
- console.warn(`[getAllCachedScanResults] Stack: ${error.stack}`);
111
- }
112
119
  return null;
113
120
  }
114
121
  }
@@ -120,46 +127,46 @@ function processScanFile(file, scanResultsDir) {
120
127
  export function getAllCachedScanResults() {
121
128
  try {
122
129
  const scanResultsDir = getScanResultsDirectory();
123
- console.log('[getAllCachedScanResults] Reading cached scans from directory:', scanResultsDir);
130
+ logger.debug({ scanResultsDir }, 'Reading cached scans from directory');
124
131
 
125
132
  // Check if directory exists (don't create it, just check)
126
133
  if (!existsSync(scanResultsDir)) {
127
- console.log(
128
- '[getAllCachedScanResults] Scan results directory does not exist:',
129
- scanResultsDir
130
- );
134
+ logger.debug({ scanResultsDir }, 'Scan results directory does not exist');
131
135
  return [];
132
136
  }
133
137
 
134
138
  // Read all files in the directory
135
139
  const allFiles = readdirSync(scanResultsDir);
136
- console.log(`[getAllCachedScanResults] Total files in directory: ${allFiles.length}`);
140
+ logger.debug({ count: allFiles.length }, 'Total files in directory');
137
141
 
138
142
  // Filter for JSON files only
139
143
  const jsonFiles = allFiles.filter((f) => f.endsWith('.json'));
140
- console.log(
141
- `[getAllCachedScanResults] Found ${jsonFiles.length} JSON files in ${scanResultsDir}`
142
- );
144
+ logger.debug({ count: jsonFiles.length, scanResultsDir }, 'Found JSON files');
143
145
 
144
146
  if (jsonFiles.length === 0) {
145
- console.log('[getAllCachedScanResults] No cached scan JSON files found');
147
+ logger.debug('No cached scan JSON files found');
146
148
  return [];
147
149
  }
148
150
 
149
- const results = [];
150
- let successCount = 0;
151
- let errorCount = 0;
152
-
153
151
  // Read each file
154
- for (const file of jsonFiles) {
155
- const result = processScanFile(file, scanResultsDir);
156
- if (result) {
157
- results.push(result);
158
- successCount++;
159
- } else {
160
- errorCount++;
161
- }
162
- }
152
+ const { results, successCount, errorCount } = jsonFiles.reduce(
153
+ (acc, file) => {
154
+ const result = processScanFile(file, scanResultsDir);
155
+ if (result) {
156
+ return {
157
+ results: [...acc.results, result],
158
+ successCount: acc.successCount + 1,
159
+ errorCount: acc.errorCount,
160
+ };
161
+ }
162
+ return {
163
+ results: acc.results,
164
+ successCount: acc.successCount,
165
+ errorCount: acc.errorCount + 1,
166
+ };
167
+ },
168
+ { results: [], successCount: 0, errorCount: 0 }
169
+ );
163
170
 
164
171
  // Sort by updatedAt descending (most recent first)
165
172
  results.sort((a, b) => {
@@ -168,13 +175,23 @@ export function getAllCachedScanResults() {
168
175
  return bTime - aTime;
169
176
  });
170
177
 
171
- console.log(
172
- `[getAllCachedScanResults] Summary: ${successCount} successful, ${errorCount} errors, ${results.length} total scans loaded`
178
+ logger.info(
179
+ {
180
+ successCount,
181
+ errorCount,
182
+ total: results.length,
183
+ },
184
+ 'Summary: cached scans loaded'
173
185
  );
174
186
  return results;
175
187
  } catch (error) {
176
- console.error('[getAllCachedScanResults] Fatal error getting all cached scan results:', error);
177
- console.error('[getAllCachedScanResults] Error stack:', error.stack);
188
+ logger.error(
189
+ {
190
+ error: error.message,
191
+ stack: error.stack,
192
+ },
193
+ 'Fatal error getting all cached scan results'
194
+ );
178
195
  return [];
179
196
  }
180
197
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync } from 'node:fs';
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { getWorkingDirectory } from 'mcp-shark-common/configs/index.js';
4
4
 
@@ -1,5 +1,6 @@
1
- import { readFileSync, writeFileSync, existsSync, unlinkSync, readdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import logger from '../logger.js';
3
4
  import { getScanResultFilePath, getScanResultsDirectory } from './directory.js';
4
5
 
5
6
  /**
@@ -26,7 +27,7 @@ export function getCachedScanResult(hash) {
26
27
  serverName: data.serverName,
27
28
  };
28
29
  } catch (error) {
29
- console.error('Error getting cached scan result:', error);
30
+ logger.error({ hash, error: error.message }, 'Error getting cached scan result');
30
31
  return null;
31
32
  }
32
33
  }
@@ -44,17 +45,19 @@ export function storeScanResult(serverName, hash, scanData) {
44
45
  const now = Date.now();
45
46
 
46
47
  // Check if file exists to preserve original creation time
47
- let createdAt = now;
48
- if (existsSync(filePath)) {
48
+ const getCreatedAt = (filePath, defaultTime) => {
49
+ if (!existsSync(filePath)) {
50
+ return defaultTime;
51
+ }
49
52
  try {
50
53
  const existingContent = readFileSync(filePath, 'utf8');
51
54
  const existingData = JSON.parse(existingContent);
52
- createdAt = existingData.createdAt || now;
53
- } catch (e) {
54
- // If we can't read existing file, use current time
55
- createdAt = now;
55
+ return existingData.createdAt || defaultTime;
56
+ } catch (_e) {
57
+ return defaultTime;
56
58
  }
57
- }
59
+ };
60
+ const createdAt = getCreatedAt(filePath, now);
58
61
 
59
62
  const dataToStore = {
60
63
  serverName,
@@ -67,7 +70,7 @@ export function storeScanResult(serverName, hash, scanData) {
67
70
  writeFileSync(filePath, JSON.stringify(dataToStore, null, 2), 'utf8');
68
71
  return true;
69
72
  } catch (error) {
70
- console.error('Error storing scan result:', error);
73
+ logger.error({ serverName, hash, error: error.message }, 'Error storing scan result');
71
74
  return false;
72
75
  }
73
76
  }
@@ -84,21 +87,21 @@ export function clearAllScanResults() {
84
87
  }
85
88
 
86
89
  const files = readdirSync(scanResultsDir).filter((f) => f.endsWith('.json'));
87
- let deletedCount = 0;
88
90
 
89
- for (const file of files) {
91
+ const deletedCount = files.reduce((count, file) => {
90
92
  try {
91
93
  const filePath = join(scanResultsDir, file);
92
94
  unlinkSync(filePath);
93
- deletedCount++;
95
+ return count + 1;
94
96
  } catch (error) {
95
- console.warn(`Error deleting scan result file ${file}:`, error);
97
+ logger.warn({ file, error: error.message }, 'Error deleting scan result file');
98
+ return count;
96
99
  }
97
- }
100
+ }, 0);
98
101
 
99
102
  return deletedCount;
100
103
  } catch (error) {
101
- console.error('Error clearing all scan results:', error);
104
+ logger.error({ error: error.message }, 'Error clearing all scan results');
102
105
  return 0;
103
106
  }
104
107
  }