@mcp-shark/mcp-shark 1.4.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 (212) hide show
  1. package/LICENSE +85 -0
  2. package/README.md +724 -0
  3. package/bin/mcp-shark.js +93 -0
  4. package/mcp-server/.editorconfig +15 -0
  5. package/mcp-server/.prettierignore +11 -0
  6. package/mcp-server/.prettierrc +12 -0
  7. package/mcp-server/README.md +280 -0
  8. package/mcp-server/commitlint.config.cjs +42 -0
  9. package/mcp-server/eslint.config.js +131 -0
  10. package/mcp-server/lib/auditor/audit.js +228 -0
  11. package/mcp-server/lib/common/error.js +15 -0
  12. package/mcp-server/lib/server/external/all.js +32 -0
  13. package/mcp-server/lib/server/external/config.js +59 -0
  14. package/mcp-server/lib/server/external/kv.js +102 -0
  15. package/mcp-server/lib/server/external/single/client.js +35 -0
  16. package/mcp-server/lib/server/external/single/request.js +49 -0
  17. package/mcp-server/lib/server/external/single/run.js +75 -0
  18. package/mcp-server/lib/server/external/single/transport.js +57 -0
  19. package/mcp-server/lib/server/internal/handlers/common.js +20 -0
  20. package/mcp-server/lib/server/internal/handlers/error.js +7 -0
  21. package/mcp-server/lib/server/internal/handlers/prompts-get.js +22 -0
  22. package/mcp-server/lib/server/internal/handlers/prompts-list.js +12 -0
  23. package/mcp-server/lib/server/internal/handlers/resources-list.js +12 -0
  24. package/mcp-server/lib/server/internal/handlers/resources-read.js +19 -0
  25. package/mcp-server/lib/server/internal/handlers/tools-call.js +37 -0
  26. package/mcp-server/lib/server/internal/handlers/tools-list.js +14 -0
  27. package/mcp-server/lib/server/internal/run.js +49 -0
  28. package/mcp-server/lib/server/internal/server.js +63 -0
  29. package/mcp-server/lib/server/internal/session.js +39 -0
  30. package/mcp-server/mcp-shark.js +72 -0
  31. package/mcp-server/package-lock.json +4784 -0
  32. package/mcp-server/package.json +30 -0
  33. package/package.json +103 -0
  34. package/ui/README.md +212 -0
  35. package/ui/index.html +16 -0
  36. package/ui/package-lock.json +3574 -0
  37. package/ui/package.json +12 -0
  38. package/ui/paths.js +282 -0
  39. package/ui/public/og-image.png +0 -0
  40. package/ui/server/routes/backups.js +251 -0
  41. package/ui/server/routes/composite.js +244 -0
  42. package/ui/server/routes/config.js +175 -0
  43. package/ui/server/routes/conversations.js +25 -0
  44. package/ui/server/routes/help.js +43 -0
  45. package/ui/server/routes/logs.js +32 -0
  46. package/ui/server/routes/playground.js +152 -0
  47. package/ui/server/routes/requests.js +235 -0
  48. package/ui/server/routes/sessions.js +27 -0
  49. package/ui/server/routes/smartscan/discover.js +117 -0
  50. package/ui/server/routes/smartscan/scans/clearCache.js +22 -0
  51. package/ui/server/routes/smartscan/scans/createBatchScans.js +123 -0
  52. package/ui/server/routes/smartscan/scans/createScan.js +42 -0
  53. package/ui/server/routes/smartscan/scans/getCachedResults.js +51 -0
  54. package/ui/server/routes/smartscan/scans/getScan.js +41 -0
  55. package/ui/server/routes/smartscan/scans/listScans.js +24 -0
  56. package/ui/server/routes/smartscan/scans.js +13 -0
  57. package/ui/server/routes/smartscan/token.js +56 -0
  58. package/ui/server/routes/smartscan/transport.js +53 -0
  59. package/ui/server/routes/smartscan.js +24 -0
  60. package/ui/server/routes/statistics.js +83 -0
  61. package/ui/server/utils/config-update.js +212 -0
  62. package/ui/server/utils/config.js +98 -0
  63. package/ui/server/utils/paths.js +23 -0
  64. package/ui/server/utils/port.js +28 -0
  65. package/ui/server/utils/process.js +80 -0
  66. package/ui/server/utils/scan-cache/all-results.js +180 -0
  67. package/ui/server/utils/scan-cache/directory.js +35 -0
  68. package/ui/server/utils/scan-cache/file-operations.js +104 -0
  69. package/ui/server/utils/scan-cache/hash.js +47 -0
  70. package/ui/server/utils/scan-cache/server-operations.js +80 -0
  71. package/ui/server/utils/scan-cache.js +12 -0
  72. package/ui/server/utils/serialization.js +13 -0
  73. package/ui/server/utils/smartscan-token.js +42 -0
  74. package/ui/server.js +199 -0
  75. package/ui/src/App.jsx +153 -0
  76. package/ui/src/CompositeLogs.jsx +164 -0
  77. package/ui/src/CompositeSetup.jsx +285 -0
  78. package/ui/src/HelpGuide/HelpGuideContent.jsx +118 -0
  79. package/ui/src/HelpGuide/HelpGuideFooter.jsx +58 -0
  80. package/ui/src/HelpGuide/HelpGuideHeader.jsx +56 -0
  81. package/ui/src/HelpGuide.jsx +65 -0
  82. package/ui/src/IntroTour.jsx +140 -0
  83. package/ui/src/LogDetail.jsx +122 -0
  84. package/ui/src/LogTable.jsx +242 -0
  85. package/ui/src/PacketDetail.jsx +190 -0
  86. package/ui/src/PacketFilters.jsx +222 -0
  87. package/ui/src/PacketList.jsx +183 -0
  88. package/ui/src/SmartScan.jsx +178 -0
  89. package/ui/src/TabNavigation.jsx +143 -0
  90. package/ui/src/components/App/HelpButton.jsx +64 -0
  91. package/ui/src/components/App/TrafficTab.jsx +69 -0
  92. package/ui/src/components/App/useAppState.js +163 -0
  93. package/ui/src/components/BackupList.jsx +192 -0
  94. package/ui/src/components/CollapsibleSection.jsx +82 -0
  95. package/ui/src/components/ConfigFileSection.jsx +84 -0
  96. package/ui/src/components/ConfigViewerModal.jsx +141 -0
  97. package/ui/src/components/ConfirmationModal.jsx +129 -0
  98. package/ui/src/components/DetailsTab/BodySection.jsx +27 -0
  99. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +70 -0
  100. package/ui/src/components/DetailsTab/HeadersSection.jsx +25 -0
  101. package/ui/src/components/DetailsTab/InfoSection.jsx +28 -0
  102. package/ui/src/components/DetailsTab/NetworkInfoSection.jsx +63 -0
  103. package/ui/src/components/DetailsTab/ProtocolInfoSection.jsx +75 -0
  104. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +46 -0
  105. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +66 -0
  106. package/ui/src/components/DetailsTab.jsx +31 -0
  107. package/ui/src/components/DetectedPathsList.jsx +171 -0
  108. package/ui/src/components/FileInput.jsx +144 -0
  109. package/ui/src/components/GroupHeader.jsx +76 -0
  110. package/ui/src/components/GroupedByMcpView.jsx +103 -0
  111. package/ui/src/components/GroupedByServerView.jsx +134 -0
  112. package/ui/src/components/GroupedBySessionView.jsx +127 -0
  113. package/ui/src/components/GroupedViews.jsx +2 -0
  114. package/ui/src/components/HexTab.jsx +188 -0
  115. package/ui/src/components/LogsDisplay.jsx +93 -0
  116. package/ui/src/components/LogsToolbar.jsx +193 -0
  117. package/ui/src/components/McpPlayground/LoadingModal.jsx +113 -0
  118. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +125 -0
  119. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +48 -0
  120. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +45 -0
  121. package/ui/src/components/McpPlayground/PromptsSection.jsx +106 -0
  122. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +89 -0
  123. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +59 -0
  124. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +45 -0
  125. package/ui/src/components/McpPlayground/ResourcesSection.jsx +91 -0
  126. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +125 -0
  127. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +48 -0
  128. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +45 -0
  129. package/ui/src/components/McpPlayground/ToolsSection.jsx +107 -0
  130. package/ui/src/components/McpPlayground/common/EmptyState.jsx +17 -0
  131. package/ui/src/components/McpPlayground/common/ErrorState.jsx +17 -0
  132. package/ui/src/components/McpPlayground/common/LoadingState.jsx +17 -0
  133. package/ui/src/components/McpPlayground/useMcpPlayground.js +280 -0
  134. package/ui/src/components/McpPlayground.jsx +171 -0
  135. package/ui/src/components/MessageDisplay.jsx +28 -0
  136. package/ui/src/components/PacketDetailHeader.jsx +88 -0
  137. package/ui/src/components/PacketFilters/ExportControls.jsx +126 -0
  138. package/ui/src/components/PacketFilters/FilterInput.jsx +59 -0
  139. package/ui/src/components/RawTab.jsx +142 -0
  140. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +155 -0
  141. package/ui/src/components/RequestRow/RequestRowMain.jsx +240 -0
  142. package/ui/src/components/RequestRow/ResponseRow.jsx +158 -0
  143. package/ui/src/components/RequestRow.jsx +70 -0
  144. package/ui/src/components/ServerControl.jsx +133 -0
  145. package/ui/src/components/ServiceSelector.jsx +209 -0
  146. package/ui/src/components/SetupHeader.jsx +30 -0
  147. package/ui/src/components/SharkLogo.jsx +21 -0
  148. package/ui/src/components/SmartScan/AnalysisResult.jsx +64 -0
  149. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +215 -0
  150. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +94 -0
  151. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +26 -0
  152. package/ui/src/components/SmartScan/DebugInfoSection.jsx +53 -0
  153. package/ui/src/components/SmartScan/EmptyState.jsx +57 -0
  154. package/ui/src/components/SmartScan/ErrorDisplay.jsx +48 -0
  155. package/ui/src/components/SmartScan/ExpandableSection.jsx +93 -0
  156. package/ui/src/components/SmartScan/FindingsTable.jsx +257 -0
  157. package/ui/src/components/SmartScan/ListViewContent.jsx +75 -0
  158. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +75 -0
  159. package/ui/src/components/SmartScan/OverallSummarySection.jsx +72 -0
  160. package/ui/src/components/SmartScan/RawDataSection.jsx +52 -0
  161. package/ui/src/components/SmartScan/RecommendationsSection.jsx +78 -0
  162. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +92 -0
  163. package/ui/src/components/SmartScan/ScanDetailView.jsx +141 -0
  164. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +49 -0
  165. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +201 -0
  166. package/ui/src/components/SmartScan/ScanListView.jsx +73 -0
  167. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +123 -0
  168. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +35 -0
  169. package/ui/src/components/SmartScan/ScanViewContent.jsx +68 -0
  170. package/ui/src/components/SmartScan/ScanningProgress.jsx +47 -0
  171. package/ui/src/components/SmartScan/ServerInfoSection.jsx +43 -0
  172. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +207 -0
  173. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +269 -0
  174. package/ui/src/components/SmartScan/SmartScanControls.jsx +290 -0
  175. package/ui/src/components/SmartScan/SmartScanHeader.jsx +77 -0
  176. package/ui/src/components/SmartScan/ViewModeTabs.jsx +57 -0
  177. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +34 -0
  178. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +121 -0
  179. package/ui/src/components/SmartScan/hooks/useScanList.js +193 -0
  180. package/ui/src/components/SmartScan/hooks/useScanOperations.js +87 -0
  181. package/ui/src/components/SmartScan/hooks/useServerStatus.js +26 -0
  182. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +53 -0
  183. package/ui/src/components/SmartScan/scanDataUtils.js +98 -0
  184. package/ui/src/components/SmartScan/useSmartScan.js +72 -0
  185. package/ui/src/components/SmartScan/utils.js +19 -0
  186. package/ui/src/components/SmartScanIcons.jsx +58 -0
  187. package/ui/src/components/TabNavigation/DesktopTabs.jsx +111 -0
  188. package/ui/src/components/TabNavigation/MobileDropdown.jsx +140 -0
  189. package/ui/src/components/TabNavigation.jsx +97 -0
  190. package/ui/src/components/TabNavigationIcons.jsx +40 -0
  191. package/ui/src/components/TableHeader.jsx +164 -0
  192. package/ui/src/components/TourOverlay.jsx +117 -0
  193. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +117 -0
  194. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +70 -0
  195. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +45 -0
  196. package/ui/src/components/TourTooltip/useTooltipPosition.js +108 -0
  197. package/ui/src/components/TourTooltip.jsx +83 -0
  198. package/ui/src/components/ViewModeTabs.jsx +91 -0
  199. package/ui/src/components/WhatThisDoesSection.jsx +61 -0
  200. package/ui/src/config/tourSteps.jsx +141 -0
  201. package/ui/src/hooks/useAnimation.js +92 -0
  202. package/ui/src/hooks/useConfigManagement.js +124 -0
  203. package/ui/src/hooks/useServiceExtraction.js +51 -0
  204. package/ui/src/index.css +42 -0
  205. package/ui/src/main.jsx +10 -0
  206. package/ui/src/theme.js +65 -0
  207. package/ui/src/utils/animations.js +170 -0
  208. package/ui/src/utils/groupingUtils.js +93 -0
  209. package/ui/src/utils/hexUtils.js +24 -0
  210. package/ui/src/utils/mcpGroupingUtils.js +262 -0
  211. package/ui/src/utils/requestUtils.js +297 -0
  212. package/ui/vite.config.js +18 -0
@@ -0,0 +1,244 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { checkPortReady } from '../utils/port.js';
5
+ import { findMcpServerPath } from '../utils/paths.js';
6
+ import { convertMcpServersToServers, clearOriginalConfig } from '../utils/config.js';
7
+ import { getMcpConfigPath } from 'mcp-shark-common/configs/index.js';
8
+ import {
9
+ createLogEntry,
10
+ spawnMcpSharkServer,
11
+ setupProcessHandlers,
12
+ getMcpSharkJsPath,
13
+ } from '../utils/process.js';
14
+ import { updateConfigFile, getSelectedServiceNames } from '../utils/config-update.js';
15
+
16
+ const MAX_LOG_LINES = 10000;
17
+
18
+ export function createCompositeRoutes(
19
+ getMcpSharkProcess,
20
+ setMcpSharkProcess,
21
+ mcpSharkLogs,
22
+ broadcastLogUpdate
23
+ ) {
24
+ const router = {};
25
+
26
+ router.setup = async (req, res) => {
27
+ mcpSharkLogs.length = 0;
28
+ const logEntry = createLogEntry(mcpSharkLogs, broadcastLogUpdate);
29
+
30
+ try {
31
+ const { filePath, fileContent, selectedServices } = req.body;
32
+
33
+ if (!filePath && !fileContent) {
34
+ return res.status(400).json({ error: 'Either filePath or fileContent is required' });
35
+ }
36
+
37
+ const fileData = (() => {
38
+ if (fileContent) {
39
+ const resolvedFilePath = filePath
40
+ ? filePath.startsWith('~')
41
+ ? path.join(homedir(), filePath.slice(1))
42
+ : filePath
43
+ : null;
44
+ return { content: fileContent, resolvedFilePath };
45
+ }
46
+
47
+ const resolvedFilePath = filePath.startsWith('~')
48
+ ? path.join(homedir(), filePath.slice(1))
49
+ : filePath;
50
+
51
+ if (!fs.existsSync(resolvedFilePath)) {
52
+ return null;
53
+ }
54
+
55
+ return {
56
+ content: fs.readFileSync(resolvedFilePath, 'utf-8'),
57
+ resolvedFilePath,
58
+ };
59
+ })();
60
+
61
+ if (!fileData) {
62
+ const resolvedFilePath = filePath.startsWith('~')
63
+ ? path.join(homedir(), filePath.slice(1))
64
+ : filePath;
65
+ return res.status(404).json({ error: 'File not found', path: resolvedFilePath });
66
+ }
67
+
68
+ const parseResult = (() => {
69
+ try {
70
+ return { config: JSON.parse(fileData.content), error: null };
71
+ } catch (e) {
72
+ return { config: null, error: e };
73
+ }
74
+ })();
75
+
76
+ if (!parseResult.config) {
77
+ return res.status(400).json({
78
+ error: 'Invalid JSON file',
79
+ details: parseResult.error ? parseResult.error.message : 'Failed to parse JSON',
80
+ });
81
+ }
82
+
83
+ const originalConfig = parseResult.config;
84
+ let convertedConfig = convertMcpServersToServers(originalConfig);
85
+
86
+ if (selectedServices && Array.isArray(selectedServices) && selectedServices.length > 0) {
87
+ const filteredServers = {};
88
+ selectedServices.forEach((serviceName) => {
89
+ if (convertedConfig.servers[serviceName]) {
90
+ filteredServers[serviceName] = convertedConfig.servers[serviceName];
91
+ }
92
+ });
93
+ convertedConfig = { servers: filteredServers };
94
+ }
95
+
96
+ if (Object.keys(convertedConfig.servers).length === 0) {
97
+ return res.status(400).json({ error: 'No servers found in config' });
98
+ }
99
+
100
+ const mcpServerPath = findMcpServerPath();
101
+ const mcpsJsonPath = getMcpConfigPath();
102
+ fs.writeFileSync(mcpsJsonPath, JSON.stringify(convertedConfig, null, 2));
103
+ console.log(`Wrote converted config to: ${mcpsJsonPath}`);
104
+
105
+ const currentProcess = getMcpSharkProcess();
106
+ if (currentProcess) {
107
+ currentProcess.kill();
108
+ setMcpSharkProcess(null);
109
+ }
110
+
111
+ const mcpSharkJsPath = getMcpSharkJsPath();
112
+ if (!fs.existsSync(mcpSharkJsPath)) {
113
+ return res.status(500).json({
114
+ error: 'MCP Shark server not found',
115
+ details: `Could not find mcp-shark.js at ${mcpSharkJsPath}`,
116
+ });
117
+ }
118
+
119
+ const processHandle = spawnMcpSharkServer(mcpSharkJsPath, mcpsJsonPath, logEntry);
120
+ setMcpSharkProcess(processHandle);
121
+
122
+ setupProcessHandlers(
123
+ processHandle,
124
+ logEntry,
125
+ (err) => {
126
+ setMcpSharkProcess(null);
127
+ return res.status(500).json({
128
+ error: 'Failed to start mcp-shark server',
129
+ details: err.message,
130
+ });
131
+ },
132
+ () => {
133
+ setMcpSharkProcess(null);
134
+ }
135
+ );
136
+
137
+ try {
138
+ console.log('Waiting for mcp-shark server to start on port 9851...');
139
+ await checkPortReady(9851, 'localhost', 15000);
140
+ console.log('MCP Shark server is ready!');
141
+
142
+ const selectedServiceNames = getSelectedServiceNames(originalConfig, selectedServices);
143
+ const { updatedConfig, backupPath: createdBackupPath } = updateConfigFile(
144
+ originalConfig,
145
+ selectedServiceNames,
146
+ fileData.resolvedFilePath,
147
+ fileData.content,
148
+ mcpSharkLogs,
149
+ broadcastLogUpdate
150
+ );
151
+
152
+ if (!fileData.resolvedFilePath) {
153
+ clearOriginalConfig();
154
+ }
155
+
156
+ res.json({
157
+ success: true,
158
+ message: 'MCP Shark server started successfully and config file updated',
159
+ convertedConfig,
160
+ updatedConfig,
161
+ filePath: fileData.resolvedFilePath || null,
162
+ backupPath: createdBackupPath || null,
163
+ });
164
+ } catch (waitError) {
165
+ console.error('MCP Shark server did not start in time:', waitError);
166
+ if (processHandle) {
167
+ processHandle.kill();
168
+ setMcpSharkProcess(null);
169
+ }
170
+ const timestamp = new Date().toISOString();
171
+ const errorLog = {
172
+ timestamp,
173
+ type: 'error',
174
+ line: `[ERROR] MCP Shark server failed to start: ${waitError.message}`,
175
+ };
176
+ mcpSharkLogs.push(errorLog);
177
+ if (mcpSharkLogs.length > MAX_LOG_LINES) {
178
+ mcpSharkLogs.shift();
179
+ }
180
+ broadcastLogUpdate(errorLog);
181
+ return res.status(500).json({
182
+ error: 'MCP Shark server failed to start',
183
+ details: waitError.message,
184
+ });
185
+ }
186
+ } catch (error) {
187
+ console.error('Error setting up mcp-shark server:', error);
188
+ const timestamp = new Date().toISOString();
189
+ const errorLog = {
190
+ timestamp,
191
+ type: 'error',
192
+ line: `[ERROR] Failed to setup mcp-shark server: ${error.message}`,
193
+ };
194
+ mcpSharkLogs.push(errorLog);
195
+ if (mcpSharkLogs.length > MAX_LOG_LINES) {
196
+ mcpSharkLogs.shift();
197
+ }
198
+ broadcastLogUpdate(errorLog);
199
+ res.status(500).json({ error: 'Failed to setup mcp-shark server', details: error.message });
200
+ }
201
+ };
202
+
203
+ router.stop = (req, res, restoreOriginalConfig) => {
204
+ try {
205
+ const currentProcess = getMcpSharkProcess();
206
+ if (currentProcess) {
207
+ currentProcess.kill();
208
+ setMcpSharkProcess(null);
209
+
210
+ const restored = restoreOriginalConfig();
211
+
212
+ if (restored) {
213
+ const timestamp = new Date().toISOString();
214
+ const restoreLog = {
215
+ timestamp,
216
+ type: 'stdout',
217
+ line: `[RESTORE] Restored original config`,
218
+ };
219
+ mcpSharkLogs.push(restoreLog);
220
+ if (mcpSharkLogs.length > MAX_LOG_LINES) {
221
+ mcpSharkLogs.shift();
222
+ }
223
+ broadcastLogUpdate(restoreLog);
224
+ }
225
+
226
+ res.json({ success: true, message: 'MCP Shark server stopped and config restored' });
227
+ } else {
228
+ res.json({ success: true, message: 'MCP Shark server was not running' });
229
+ }
230
+ } catch (error) {
231
+ res.status(500).json({ error: 'Failed to stop mcp-shark server', details: error.message });
232
+ }
233
+ };
234
+
235
+ router.getStatus = (req, res) => {
236
+ const currentProcess = getMcpSharkProcess();
237
+ res.json({
238
+ running: currentProcess !== null,
239
+ pid: currentProcess?.pid || null,
240
+ });
241
+ };
242
+
243
+ return router;
244
+ }
@@ -0,0 +1,175 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { extractServices, convertMcpServersToServers } from '../utils/config.js';
5
+ import { createBackupRoutes } from './backups.js';
6
+
7
+ export function createConfigRoutes() {
8
+ const router = {};
9
+
10
+ router.extractServices = (req, res) => {
11
+ try {
12
+ const { filePath, fileContent } = req.body;
13
+
14
+ if (!filePath && !fileContent) {
15
+ return res.status(400).json({ error: 'Either filePath or fileContent is required' });
16
+ }
17
+
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
+ })();
30
+
31
+ if (!content) {
32
+ const resolvedFilePath = filePath.startsWith('~')
33
+ ? path.join(homedir(), filePath.slice(1))
34
+ : filePath;
35
+ return res.status(404).json({ error: 'File not found', path: resolvedFilePath });
36
+ }
37
+
38
+ const parseResult = (() => {
39
+ try {
40
+ return { config: JSON.parse(content), error: null };
41
+ } catch (e) {
42
+ return { config: null, error: e };
43
+ }
44
+ })();
45
+
46
+ if (!parseResult.config) {
47
+ return res.status(400).json({
48
+ error: 'Invalid JSON file',
49
+ details: parseResult.error ? parseResult.error.message : 'Failed to parse JSON',
50
+ });
51
+ }
52
+
53
+ const config = parseResult.config;
54
+
55
+ const services = extractServices(config);
56
+ res.json({ success: true, services });
57
+ } catch (error) {
58
+ res.status(500).json({ error: 'Failed to extract services', details: error.message });
59
+ }
60
+ };
61
+
62
+ router.readConfig = (req, res) => {
63
+ try {
64
+ const { filePath } = req.query;
65
+
66
+ if (!filePath) {
67
+ return res.status(400).json({ error: 'filePath is required' });
68
+ }
69
+
70
+ const resolvedPath = filePath.startsWith('~')
71
+ ? path.join(homedir(), filePath.slice(1))
72
+ : filePath;
73
+
74
+ if (!fs.existsSync(resolvedPath)) {
75
+ return res.status(404).json({ error: 'File not found', path: resolvedPath });
76
+ }
77
+
78
+ 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
+ })();
86
+
87
+ res.json({
88
+ success: true,
89
+ filePath: resolvedPath,
90
+ displayPath: resolvedPath.replace(homedir(), '~'),
91
+ content: content,
92
+ parsed: parsed,
93
+ exists: true,
94
+ });
95
+ } catch (error) {
96
+ res.status(500).json({ error: 'Failed to read file', details: error.message });
97
+ }
98
+ };
99
+
100
+ router.detectConfig = (req, res) => {
101
+ const detected = [];
102
+ const platform = process.platform;
103
+ const homeDir = homedir();
104
+
105
+ const cursorPaths = [
106
+ path.join(homeDir, '.cursor', 'mcp.json'),
107
+ ...(platform === 'win32'
108
+ ? [path.join(process.env.USERPROFILE || '', '.cursor', 'mcp.json')]
109
+ : []),
110
+ ];
111
+
112
+ const windsurfPaths = [
113
+ path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
114
+ ...(platform === 'win32'
115
+ ? [path.join(process.env.USERPROFILE || '', '.codeium', 'windsurf', 'mcp_config.json')]
116
+ : []),
117
+ ];
118
+
119
+ for (const cursorPath of cursorPaths) {
120
+ if (fs.existsSync(cursorPath)) {
121
+ detected.push({
122
+ editor: 'Cursor',
123
+ path: cursorPath,
124
+ displayPath: cursorPath.replace(homeDir, '~'),
125
+ exists: true,
126
+ });
127
+ break;
128
+ }
129
+ }
130
+
131
+ for (const windsurfPath of windsurfPaths) {
132
+ if (fs.existsSync(windsurfPath)) {
133
+ detected.push({
134
+ editor: 'Windsurf',
135
+ path: windsurfPath,
136
+ displayPath: windsurfPath.replace(homeDir, '~'),
137
+ exists: true,
138
+ });
139
+ break;
140
+ }
141
+ }
142
+
143
+ const defaultPaths = [
144
+ {
145
+ editor: 'Cursor',
146
+ path: path.join(homeDir, '.cursor', 'mcp.json'),
147
+ displayPath: '~/.cursor/mcp.json',
148
+ exists: fs.existsSync(path.join(homeDir, '.cursor', 'mcp.json')),
149
+ },
150
+ {
151
+ editor: 'Windsurf',
152
+ path: path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
153
+ displayPath: '~/.codeium/windsurf/mcp_config.json',
154
+ exists: fs.existsSync(path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json')),
155
+ },
156
+ ];
157
+
158
+ const result = detected.length > 0 ? detected : defaultPaths;
159
+
160
+ res.json({
161
+ detected: result,
162
+ platform,
163
+ homeDir,
164
+ });
165
+ };
166
+
167
+ // Delegate backup routes to separate module
168
+ const backupRoutes = createBackupRoutes();
169
+ router.listBackups = backupRoutes.listBackups;
170
+ router.restoreBackup = backupRoutes.restoreBackup;
171
+ router.viewBackup = backupRoutes.viewBackup;
172
+ router.deleteBackup = backupRoutes.deleteBackup;
173
+
174
+ return router;
175
+ }
@@ -0,0 +1,25 @@
1
+ import { serializeBigInt } from '../utils/serialization.js';
2
+ import { queryConversations } from 'mcp-shark-common/db/query.js';
3
+
4
+ export function createConversationsRoutes(db) {
5
+ const router = {};
6
+
7
+ router.getConversations = (req, res) => {
8
+ const limit = parseInt(req.query.limit) || 1000;
9
+ const offset = parseInt(req.query.offset) || 0;
10
+ const filters = {
11
+ sessionId: req.query.sessionId || null,
12
+ method: req.query.method || null,
13
+ status: req.query.status || null,
14
+ jsonrpcId: req.query.jsonrpcId || null,
15
+ startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
16
+ endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
17
+ limit,
18
+ offset,
19
+ };
20
+ const conversations = queryConversations(db, filters);
21
+ res.json(serializeBigInt(conversations));
22
+ };
23
+
24
+ return router;
25
+ }
@@ -0,0 +1,43 @@
1
+ import { readHelpState, writeHelpState } from 'mcp-shark-common/configs/index.js';
2
+
3
+ export function createHelpRoutes() {
4
+ const router = {};
5
+
6
+ router.getState = (req, res) => {
7
+ const state = readHelpState();
8
+ res.json({
9
+ dismissed: state.dismissed || false,
10
+ tourCompleted: state.tourCompleted || false,
11
+ });
12
+ };
13
+
14
+ router.dismiss = (req, res) => {
15
+ const { tourCompleted } = req.body || {};
16
+ const state = {
17
+ dismissed: true,
18
+ tourCompleted: tourCompleted || false,
19
+ };
20
+ const success = writeHelpState(state);
21
+ if (success) {
22
+ res.json({ success: true });
23
+ } else {
24
+ res.status(500).json({ error: 'Failed to save help state' });
25
+ }
26
+ };
27
+
28
+ router.reset = (req, res) => {
29
+ const state = {
30
+ dismissed: false,
31
+ tourCompleted: false,
32
+ dismissedAt: null,
33
+ };
34
+ const success = writeHelpState(state);
35
+ if (success) {
36
+ res.json({ success: true });
37
+ } else {
38
+ res.status(500).json({ error: 'Failed to reset help state' });
39
+ }
40
+ };
41
+
42
+ return router;
43
+ }
@@ -0,0 +1,32 @@
1
+ export function createLogsRoutes(mcpSharkLogs, broadcastLogUpdate) {
2
+ const router = {};
3
+
4
+ router.getLogs = (req, res) => {
5
+ const limit = parseInt(req.query.limit) || 1000;
6
+ const offset = parseInt(req.query.offset) || 0;
7
+ const logs = [...mcpSharkLogs].reverse().slice(offset, offset + limit);
8
+ res.json(logs);
9
+ };
10
+
11
+ router.clearLogs = (req, res) => {
12
+ mcpSharkLogs.length = 0;
13
+ res.json({ success: true, message: 'Logs cleared' });
14
+ };
15
+
16
+ router.exportLogs = (req, res) => {
17
+ try {
18
+ const logsText = mcpSharkLogs
19
+ .map((log) => `[${log.timestamp}] [${log.type.toUpperCase()}] ${log.line}`)
20
+ .join('\n');
21
+
22
+ const filename = `mcp-shark-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
23
+ res.setHeader('Content-Type', 'text/plain');
24
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
25
+ res.send(logsText);
26
+ } catch (error) {
27
+ res.status(500).json({ error: 'Failed to export logs', details: error.message });
28
+ }
29
+ };
30
+
31
+ return router;
32
+ }
@@ -0,0 +1,152 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+
4
+ const MCP_SERVER_URL = 'http://localhost:9851/mcp';
5
+
6
+ // Store client connections per session
7
+ const clientSessions = new Map();
8
+
9
+ export function createPlaygroundRoutes() {
10
+ const router = {};
11
+
12
+ // Get or create client for a session
13
+ async function getClient(sessionId) {
14
+ if (clientSessions.has(sessionId)) {
15
+ return clientSessions.get(sessionId);
16
+ }
17
+
18
+ const client = new Client(
19
+ { name: 'mcp-shark-playground', version: '1.0.0' },
20
+ {
21
+ capabilities: {
22
+ tools: {},
23
+ resources: {},
24
+ prompts: {},
25
+ },
26
+ }
27
+ );
28
+
29
+ const transport = new StreamableHTTPClientTransport(new URL(MCP_SERVER_URL));
30
+ await client.connect(transport);
31
+
32
+ const clientWrapper = {
33
+ client,
34
+ transport,
35
+ close: async () => {
36
+ await client.close();
37
+ transport.close?.();
38
+ },
39
+ };
40
+
41
+ clientSessions.set(sessionId, clientWrapper);
42
+ return clientWrapper;
43
+ }
44
+
45
+ router.proxyRequest = async (req, res) => {
46
+ try {
47
+ const { method, params } = req.body;
48
+
49
+ if (!method) {
50
+ return res.status(400).json({
51
+ error: 'Invalid request',
52
+ message: 'method field is required',
53
+ });
54
+ }
55
+
56
+ // Get or create session ID
57
+ const sessionId =
58
+ req.headers['mcp-session-id'] ||
59
+ req.headers['x-mcp-session-id'] ||
60
+ `playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
61
+
62
+ const { client } = await getClient(sessionId);
63
+
64
+ let result;
65
+ switch (method) {
66
+ case 'tools/list':
67
+ result = await client.listTools();
68
+ break;
69
+ case 'tools/call':
70
+ if (!params?.name) {
71
+ return res.status(400).json({
72
+ error: 'Invalid request',
73
+ message: 'Tool name is required',
74
+ });
75
+ }
76
+ result = await client.callTool({
77
+ name: params.name,
78
+ arguments: params.arguments || {},
79
+ });
80
+ break;
81
+ case 'prompts/list':
82
+ result = await client.listPrompts();
83
+ break;
84
+ case 'prompts/get':
85
+ if (!params?.name) {
86
+ return res.status(400).json({
87
+ error: 'Invalid request',
88
+ message: 'Prompt name is required',
89
+ });
90
+ }
91
+ result = await client.getPrompt({
92
+ name: params.name,
93
+ arguments: params.arguments || {},
94
+ });
95
+ break;
96
+ case 'resources/list':
97
+ result = await client.listResources();
98
+ break;
99
+ case 'resources/read':
100
+ if (!params?.uri) {
101
+ return res.status(400).json({
102
+ error: 'Invalid request',
103
+ message: 'Resource URI is required',
104
+ });
105
+ }
106
+ result = await client.readResource({ uri: params.uri });
107
+ break;
108
+ default:
109
+ return res.status(400).json({
110
+ error: 'Unsupported method',
111
+ message: `Method ${method} is not supported`,
112
+ });
113
+ }
114
+
115
+ // Return session ID in response
116
+ res.setHeader('Mcp-Session-Id', sessionId);
117
+ res.json({
118
+ result,
119
+ _sessionId: sessionId,
120
+ });
121
+ } catch (error) {
122
+ console.error('Error in playground proxy:', error);
123
+
124
+ // Check if it's a connection error
125
+ if (error.message?.includes('ECONNREFUSED') || error.message?.includes('connect')) {
126
+ return res.status(503).json({
127
+ error: 'MCP server unavailable',
128
+ message: error.message,
129
+ details: 'Make sure the MCP Shark server is running on port 9851',
130
+ });
131
+ }
132
+
133
+ res.status(500).json({
134
+ error: 'Internal server error',
135
+ message: error.message || 'Unknown error',
136
+ });
137
+ }
138
+ };
139
+
140
+ // Cleanup endpoint to close client connections
141
+ router.cleanup = async (req, res) => {
142
+ const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'];
143
+ if (sessionId && clientSessions.has(sessionId)) {
144
+ const clientWrapper = clientSessions.get(sessionId);
145
+ await clientWrapper.close();
146
+ clientSessions.delete(sessionId);
147
+ }
148
+ res.json({ success: true });
149
+ };
150
+
151
+ return router;
152
+ }