@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,235 @@
1
+ import { serializeBigInt } from '../utils/serialization.js';
2
+ import { queryRequests } from 'mcp-shark-common/db/query.js';
3
+
4
+ export function createRequestsRoutes(db) {
5
+ const router = {};
6
+
7
+ router.getRequests = (req, res) => {
8
+ try {
9
+ const limit = parseInt(req.query.limit) || 1000;
10
+ const offset = parseInt(req.query.offset) || 0;
11
+
12
+ // Sanitize search parameter - convert empty strings to null
13
+ let search = req.query.search;
14
+ if (search !== undefined && search !== null) {
15
+ search = String(search).trim();
16
+ search = search.length > 0 ? search : null;
17
+ } else {
18
+ search = null;
19
+ }
20
+
21
+ // Build filters object, ensuring all values are properly typed
22
+ const filters = {
23
+ sessionId: (req.query.sessionId && String(req.query.sessionId).trim()) || null,
24
+ direction: (req.query.direction && String(req.query.direction).trim()) || null,
25
+ method: (req.query.method && String(req.query.method).trim()) || null,
26
+ jsonrpcMethod: (req.query.jsonrpcMethod && String(req.query.jsonrpcMethod).trim()) || null,
27
+ statusCode: req.query.statusCode ? parseInt(req.query.statusCode) : null,
28
+ jsonrpcId: (req.query.jsonrpcId && String(req.query.jsonrpcId).trim()) || null,
29
+ search: search,
30
+ serverName: (req.query.serverName && String(req.query.serverName).trim()) || null,
31
+ startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
32
+ endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
33
+ limit,
34
+ offset,
35
+ };
36
+
37
+ // Remove undefined values to avoid issues
38
+ Object.keys(filters).forEach((key) => {
39
+ if (filters[key] === undefined) {
40
+ filters[key] = null;
41
+ }
42
+ });
43
+
44
+ const requests = queryRequests(db, filters);
45
+ res.json(serializeBigInt(requests));
46
+ } catch (error) {
47
+ console.error('Error in getRequests:', error);
48
+ res.status(500).json({ error: 'Failed to query requests', details: error.message });
49
+ }
50
+ };
51
+
52
+ router.getRequest = (req, res) => {
53
+ const stmt = db.prepare('SELECT * FROM packets WHERE frame_number = ?');
54
+ const request = stmt.get(parseInt(req.params.frameNumber));
55
+ if (!request) {
56
+ return res.status(404).json({ error: 'Request not found' });
57
+ }
58
+ res.json(serializeBigInt(request));
59
+ };
60
+
61
+ router.clearRequests = (req, res) => {
62
+ try {
63
+ // Disable foreign key constraints temporarily to avoid constraint violations
64
+ db.exec('PRAGMA foreign_keys = OFF');
65
+
66
+ // Get list of all tables in the database
67
+ const tablesResult = db
68
+ .prepare(
69
+ `
70
+ SELECT name FROM sqlite_master
71
+ WHERE type='table' AND name NOT LIKE 'sqlite_%'
72
+ `
73
+ )
74
+ .all();
75
+
76
+ const existingTables = tablesResult.map((row) => row.name);
77
+
78
+ // Based on mcp-shark-common schema, these are the traffic-related tables:
79
+ // - packets: Individual HTTP request/response packets
80
+ // - conversations: Correlated request/response pairs
81
+ // - sessions: Session tracking
82
+ // Clear in order to respect any foreign key dependencies
83
+ const trafficTables = [
84
+ 'conversations', // Clear conversations first (may reference packets)
85
+ 'packets', // Clear packets (main traffic data)
86
+ 'sessions', // Clear sessions (may reference packets)
87
+ ];
88
+
89
+ // Delete from each table that exists
90
+ let clearedCount = 0;
91
+ const clearedTables = [];
92
+ trafficTables.forEach((table) => {
93
+ if (existingTables.includes(table)) {
94
+ try {
95
+ db.exec(`DELETE FROM ${table}`);
96
+ clearedCount++;
97
+ clearedTables.push(table);
98
+ } catch (err) {
99
+ console.warn(`Error clearing table ${table}:`, err.message);
100
+ }
101
+ }
102
+ });
103
+
104
+ // Re-enable foreign key constraints
105
+ db.exec('PRAGMA foreign_keys = ON');
106
+
107
+ res.json({
108
+ success: true,
109
+ message: `Cleared ${clearedCount} table(s): ${clearedTables.join(', ')}. All captured traffic has been cleared.`,
110
+ });
111
+ } catch (error) {
112
+ // Make sure to re-enable foreign keys even if there's an error
113
+ try {
114
+ db.exec('PRAGMA foreign_keys = ON');
115
+ } catch (e) {
116
+ // Ignore
117
+ }
118
+ console.error('Error clearing requests:', error);
119
+ res.status(500).json({ error: 'Failed to clear traffic', details: error.message });
120
+ }
121
+ };
122
+
123
+ router.exportRequests = (req, res) => {
124
+ try {
125
+ // Sanitize search parameter - convert empty strings to null
126
+ let search = req.query.search;
127
+ if (search !== undefined && search !== null) {
128
+ search = String(search).trim();
129
+ search = search.length > 0 ? search : null;
130
+ } else {
131
+ search = null;
132
+ }
133
+
134
+ const filters = {
135
+ sessionId: req.query.sessionId || null,
136
+ direction: req.query.direction || null,
137
+ method: req.query.method || null,
138
+ jsonrpcMethod: req.query.jsonrpcMethod || null,
139
+ statusCode: req.query.statusCode ? parseInt(req.query.statusCode) : null,
140
+ jsonrpcId: req.query.jsonrpcId || null,
141
+ search: search,
142
+ serverName: req.query.serverName || null,
143
+ startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
144
+ endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
145
+ limit: 100000,
146
+ offset: 0,
147
+ };
148
+
149
+ const requests = queryRequests(db, filters);
150
+ const format = req.query.format || 'json';
151
+
152
+ let content, contentType, extension;
153
+
154
+ if (format === 'csv') {
155
+ const headers = [
156
+ 'Frame',
157
+ 'Time',
158
+ 'Source',
159
+ 'Destination',
160
+ 'Protocol',
161
+ 'Length',
162
+ 'Method',
163
+ 'Status',
164
+ 'JSON-RPC Method',
165
+ 'Session ID',
166
+ 'Server Name',
167
+ ];
168
+ const rows = requests.map((req) => [
169
+ req.frame_number || '',
170
+ req.timestamp_iso || '',
171
+ req.request?.host || '',
172
+ req.request?.host || '',
173
+ 'HTTP',
174
+ req.length || '',
175
+ req.request?.method || '',
176
+ req.response?.status_code || '',
177
+ req.jsonrpc_method || '',
178
+ req.session_id || '',
179
+ req.server_name || '',
180
+ ]);
181
+
182
+ content = [
183
+ headers.join(','),
184
+ ...rows.map((row) =>
185
+ row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')
186
+ ),
187
+ ].join('\n');
188
+ contentType = 'text/csv';
189
+ extension = 'csv';
190
+ } else if (format === 'txt') {
191
+ content = requests
192
+ .map((req, idx) => {
193
+ const lines = [
194
+ `=== Request/Response #${idx + 1} (Frame ${req.frame_number || 'N/A'}) ===`,
195
+ `Time: ${req.timestamp_iso || 'N/A'}`,
196
+ `Session ID: ${req.session_id || 'N/A'}`,
197
+ `Server: ${req.server_name || 'N/A'}`,
198
+ `Direction: ${req.direction || 'N/A'}`,
199
+ `Method: ${req.request?.method || 'N/A'}`,
200
+ `Status: ${req.response?.status_code || 'N/A'}`,
201
+ `JSON-RPC Method: ${req.jsonrpc_method || 'N/A'}`,
202
+ `JSON-RPC ID: ${req.jsonrpc_id || 'N/A'}`,
203
+ `Length: ${req.length || 0} bytes`,
204
+ '',
205
+ 'Request:',
206
+ JSON.stringify(req.request || {}, null, 2),
207
+ '',
208
+ 'Response:',
209
+ JSON.stringify(req.response || {}, null, 2),
210
+ '',
211
+ '---',
212
+ '',
213
+ ];
214
+ return lines.join('\n');
215
+ })
216
+ .join('\n');
217
+ contentType = 'text/plain';
218
+ extension = 'txt';
219
+ } else {
220
+ content = JSON.stringify(serializeBigInt(requests), null, 2);
221
+ contentType = 'application/json';
222
+ extension = 'json';
223
+ }
224
+
225
+ const filename = `mcp-shark-traffic-${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`;
226
+ res.setHeader('Content-Type', contentType);
227
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
228
+ res.send(content);
229
+ } catch (error) {
230
+ res.status(500).json({ error: 'Failed to export traffic', details: error.message });
231
+ }
232
+ };
233
+
234
+ return router;
235
+ }
@@ -0,0 +1,27 @@
1
+ import { serializeBigInt } from '../utils/serialization.js';
2
+ import { getSessions, getSessionRequests } from 'mcp-shark-common/db/query.js';
3
+
4
+ export function createSessionsRoutes(db) {
5
+ const router = {};
6
+
7
+ router.getSessions = (req, res) => {
8
+ const limit = parseInt(req.query.limit) || 1000;
9
+ const offset = parseInt(req.query.offset) || 0;
10
+ const filters = {
11
+ startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
12
+ endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
13
+ limit,
14
+ offset,
15
+ };
16
+ const sessions = getSessions(db, filters);
17
+ res.json(serializeBigInt(sessions));
18
+ };
19
+
20
+ router.getSessionRequests = (req, res) => {
21
+ const limit = parseInt(req.query.limit) || 10000;
22
+ const requests = getSessionRequests(db, req.params.sessionId, limit);
23
+ res.json(serializeBigInt(requests));
24
+ };
25
+
26
+ return router;
27
+ }
@@ -0,0 +1,117 @@
1
+ import { getMcpConfigPath } from 'mcp-shark-common/configs/index.js';
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
+ import { convertMcpServersToServers } from '../../utils/config.js';
5
+ import { createTransport } from './transport.js';
6
+
7
+ /**
8
+ * Discover a single MCP server
9
+ */
10
+ async function discoverServer(serverName, serverConfig) {
11
+ const transport = createTransport(serverConfig, serverName);
12
+ const client = new Client(
13
+ { name: 'mcp-shark-smart-scan', version: '1.0.0' },
14
+ {
15
+ capabilities: {
16
+ tools: {},
17
+ resources: {},
18
+ prompts: {},
19
+ },
20
+ }
21
+ );
22
+
23
+ try {
24
+ await client.connect(transport);
25
+
26
+ const [toolsResult, resourcesResult, promptsResult] = await Promise.allSettled([
27
+ client.listTools(),
28
+ client.listResources(),
29
+ client.listPrompts(),
30
+ ]);
31
+
32
+ const tools = toolsResult.status === 'fulfilled' ? toolsResult.value?.tools || [] : [];
33
+ const resources =
34
+ resourcesResult.status === 'fulfilled' ? resourcesResult.value?.resources || [] : [];
35
+ const prompts = promptsResult.status === 'fulfilled' ? promptsResult.value?.prompts || [] : [];
36
+
37
+ await client.close();
38
+ if (transport.close) {
39
+ await transport.close();
40
+ }
41
+
42
+ return {
43
+ name: serverName,
44
+ tools,
45
+ resources,
46
+ prompts,
47
+ };
48
+ } catch (error) {
49
+ try {
50
+ await client.close();
51
+ if (transport.close) {
52
+ await transport.close();
53
+ }
54
+ } catch (closeError) {
55
+ // Ignore close errors
56
+ }
57
+ throw error;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Discover all MCP servers from config
63
+ * GET /api/smartscan/discover
64
+ */
65
+ export async function discoverServers(req, res) {
66
+ try {
67
+ const configPath = getMcpConfigPath();
68
+
69
+ if (!existsSync(configPath)) {
70
+ return res.status(404).json({
71
+ error: 'MCP config file not found',
72
+ message: `Config file not found at: ${configPath}`,
73
+ });
74
+ }
75
+
76
+ const configContent = readFileSync(configPath, 'utf-8');
77
+ const parsedConfig = JSON.parse(configContent);
78
+
79
+ const convertedConfig = convertMcpServersToServers(parsedConfig);
80
+ const servers = convertedConfig.servers || {};
81
+
82
+ if (Object.keys(servers).length === 0) {
83
+ return res.status(400).json({
84
+ error: 'No servers found in config',
85
+ message: 'The config file does not contain any MCP servers',
86
+ });
87
+ }
88
+
89
+ const discoveryPromises = Object.entries(servers).map(async ([serverName, serverConfig]) => {
90
+ try {
91
+ return await discoverServer(serverName, serverConfig);
92
+ } catch (error) {
93
+ console.error(`Error discovering server ${serverName}:`, error);
94
+ return {
95
+ name: serverName,
96
+ tools: [],
97
+ resources: [],
98
+ prompts: [],
99
+ error: error.message,
100
+ };
101
+ }
102
+ });
103
+
104
+ const discoveredServers = await Promise.all(discoveryPromises);
105
+
106
+ return res.json({
107
+ success: true,
108
+ servers: discoveredServers,
109
+ });
110
+ } catch (error) {
111
+ console.error('Error discovering servers:', error);
112
+ return res.status(500).json({
113
+ error: 'Failed to discover servers',
114
+ message: error.message,
115
+ });
116
+ }
117
+ }
@@ -0,0 +1,22 @@
1
+ import { clearAllScanResults } from '../../../utils/scan-cache.js';
2
+
3
+ /**
4
+ * Clear all cached scan results
5
+ * POST /api/smartscan/cache/clear
6
+ */
7
+ export function clearCache(req, res) {
8
+ try {
9
+ const deletedCount = clearAllScanResults();
10
+ return res.json({
11
+ success: true,
12
+ message: `Cleared ${deletedCount} cached scan result${deletedCount !== 1 ? 's' : ''}`,
13
+ deletedCount,
14
+ });
15
+ } catch (error) {
16
+ console.error('Error clearing cache:', error);
17
+ return res.status(500).json({
18
+ error: 'Failed to clear cache',
19
+ message: error.message,
20
+ });
21
+ }
22
+ }
@@ -0,0 +1,123 @@
1
+ const API_BASE_URL = 'https://smart.mcpshark.sh';
2
+ import { computeMcpHash, getCachedScanResult, storeScanResult } from '../../../utils/scan-cache.js';
3
+
4
+ /**
5
+ * Create scan for multiple servers (one request per server)
6
+ * POST /api/smartscan/scans/batch
7
+ */
8
+ export async function createBatchScans(req, res) {
9
+ try {
10
+ const { apiToken, servers } = req.body;
11
+
12
+ if (!apiToken) {
13
+ return res.status(400).json({
14
+ error: 'API token is required',
15
+ });
16
+ }
17
+
18
+ if (!servers || !Array.isArray(servers) || servers.length === 0) {
19
+ return res.status(400).json({
20
+ error: 'Servers array is required',
21
+ });
22
+ }
23
+
24
+ const scanPromises = servers.map(async (serverData) => {
25
+ const hash = computeMcpHash(serverData);
26
+ const cachedResult = getCachedScanResult(hash);
27
+ if (cachedResult) {
28
+ console.log(`Using cached scan result for server: ${serverData.name}`);
29
+ return {
30
+ serverName: serverData.name,
31
+ success: true,
32
+ status: 200,
33
+ data: cachedResult,
34
+ error: null,
35
+ cached: true,
36
+ };
37
+ }
38
+
39
+ const scanData = {
40
+ server: {
41
+ name: serverData.name || 'unknown',
42
+ description: serverData.description || null,
43
+ },
44
+ tools: (serverData.tools || []).map((tool) => {
45
+ const toolData = {
46
+ name: tool.name,
47
+ description: tool.description || null,
48
+ };
49
+ if (tool.inputSchema && typeof tool.inputSchema === 'object') {
50
+ toolData.input_schema = tool.inputSchema;
51
+ }
52
+ if (tool.outputSchema && typeof tool.outputSchema === 'object') {
53
+ toolData.output_schema = tool.outputSchema;
54
+ }
55
+ return toolData;
56
+ }),
57
+ resources: (serverData.resources || []).map((resource) => ({
58
+ uri: resource.uri,
59
+ name: resource.name || null,
60
+ description: resource.description || null,
61
+ mimeType: resource.mimeType || null,
62
+ })),
63
+ prompts: (serverData.prompts || []).map((prompt) => ({
64
+ name: prompt.name,
65
+ description: prompt.description || null,
66
+ arguments: prompt.arguments || [],
67
+ })),
68
+ };
69
+
70
+ try {
71
+ const response = await fetch(`${API_BASE_URL}/api/scans`, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ Accept: 'application/json',
76
+ Authorization: `Bearer ${apiToken}`,
77
+ },
78
+ body: JSON.stringify(scanData),
79
+ });
80
+
81
+ const data = await response.json();
82
+
83
+ const result = {
84
+ serverName: serverData.name,
85
+ success: response.ok,
86
+ status: response.status,
87
+ data: response.ok ? data : null,
88
+ error: response.ok ? null : data.error || data.message || 'Unknown error',
89
+ cached: false,
90
+ };
91
+
92
+ if (response.ok && data) {
93
+ storeScanResult(serverData.name, hash, data);
94
+ console.log(`Stored scan result in cache for server: ${serverData.name}`);
95
+ }
96
+
97
+ return result;
98
+ } catch (error) {
99
+ return {
100
+ serverName: serverData.name,
101
+ success: false,
102
+ status: 500,
103
+ data: null,
104
+ error: error.message,
105
+ cached: false,
106
+ };
107
+ }
108
+ });
109
+
110
+ const results = await Promise.all(scanPromises);
111
+
112
+ return res.json({
113
+ success: true,
114
+ results,
115
+ });
116
+ } catch (error) {
117
+ console.error('Smart Scan batch API error:', error);
118
+ return res.status(500).json({
119
+ error: 'Failed to create batch scans',
120
+ message: error.message,
121
+ });
122
+ }
123
+ }
@@ -0,0 +1,42 @@
1
+ const API_BASE_URL = 'https://smart.mcpshark.sh';
2
+
3
+ /**
4
+ * Proxy POST request to create a scan
5
+ * POST /api/smartscan/scans
6
+ */
7
+ export async function createScan(req, res) {
8
+ try {
9
+ const { apiToken, scanData } = req.body;
10
+
11
+ if (!apiToken) {
12
+ return res.status(400).json({
13
+ error: 'API token is required',
14
+ });
15
+ }
16
+
17
+ if (!scanData) {
18
+ return res.status(400).json({
19
+ error: 'Scan data is required',
20
+ });
21
+ }
22
+
23
+ const response = await fetch(`${API_BASE_URL}/api/scans`, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ Accept: 'application/json',
28
+ Authorization: `Bearer ${apiToken}`,
29
+ },
30
+ body: JSON.stringify(scanData),
31
+ });
32
+
33
+ const data = await response.json();
34
+ return res.status(response.status).json(data);
35
+ } catch (error) {
36
+ console.error('Smart Scan API error:', error);
37
+ return res.status(500).json({
38
+ error: 'Failed to create scan',
39
+ message: error.message,
40
+ });
41
+ }
42
+ }
@@ -0,0 +1,51 @@
1
+ import { computeMcpHash, getCachedScanResult } from '../../../utils/scan-cache.js';
2
+
3
+ /**
4
+ * Get cached scan results for discovered servers
5
+ * POST /api/smartscan/cached-results
6
+ */
7
+ export function getCachedResults(req, res) {
8
+ try {
9
+ const { servers } = req.body;
10
+
11
+ if (!servers || !Array.isArray(servers) || servers.length === 0) {
12
+ return res.status(400).json({
13
+ error: 'Servers array is required',
14
+ });
15
+ }
16
+
17
+ const cachedResults = servers.map((serverData) => {
18
+ const hash = computeMcpHash(serverData);
19
+ const cachedResult = getCachedScanResult(hash);
20
+
21
+ if (cachedResult) {
22
+ return {
23
+ serverName: serverData.name,
24
+ success: true,
25
+ data: cachedResult,
26
+ cached: true,
27
+ hash,
28
+ };
29
+ }
30
+
31
+ return {
32
+ serverName: serverData.name,
33
+ success: false,
34
+ data: null,
35
+ cached: false,
36
+ hash,
37
+ };
38
+ });
39
+
40
+ return res.json({
41
+ success: true,
42
+ results: cachedResults,
43
+ });
44
+ } catch (error) {
45
+ console.error('Error getting cached results:', error);
46
+ return res.status(500).json({
47
+ error: 'Failed to get cached results',
48
+ message: error.message,
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,41 @@
1
+ const API_BASE_URL = 'https://smart.mcpshark.sh';
2
+
3
+ /**
4
+ * Proxy GET request to get a scan by ID
5
+ * GET /api/smartscan/scans/:scanId
6
+ */
7
+ export async function getScan(req, res) {
8
+ try {
9
+ const { scanId } = req.params;
10
+ const apiToken = req.headers.authorization?.replace('Bearer ', '');
11
+
12
+ if (!apiToken) {
13
+ return res.status(401).json({
14
+ error: 'API token is required',
15
+ });
16
+ }
17
+
18
+ if (!scanId) {
19
+ return res.status(400).json({
20
+ error: 'Scan ID is required',
21
+ });
22
+ }
23
+
24
+ const response = await fetch(`${API_BASE_URL}/api/scans/${scanId}`, {
25
+ method: 'GET',
26
+ headers: {
27
+ Accept: 'application/json',
28
+ Authorization: `Bearer ${apiToken}`,
29
+ },
30
+ });
31
+
32
+ const data = await response.json();
33
+ return res.status(response.status).json(data);
34
+ } catch (error) {
35
+ console.error('Smart Scan API error:', error);
36
+ return res.status(500).json({
37
+ error: 'Failed to get scan',
38
+ message: error.message,
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,24 @@
1
+ import { getAllCachedScanResults } from '../../../utils/scan-cache.js';
2
+
3
+ /**
4
+ * List all scans from local cache only
5
+ * GET /api/smartscan/scans?cache=true
6
+ */
7
+ export async function listScans(req, res) {
8
+ try {
9
+ console.log('[listScans] Loading cached scans from local storage...');
10
+ const cachedScans = getAllCachedScanResults();
11
+ console.log(`[listScans] Returning ${cachedScans.length} cached scans`);
12
+ return res.json({
13
+ scans: cachedScans,
14
+ cached: true,
15
+ count: cachedScans.length,
16
+ });
17
+ } catch (error) {
18
+ console.error('[listScans] Error loading cached scans:', error);
19
+ return res.status(500).json({
20
+ error: 'Failed to load cached scans',
21
+ message: error.message,
22
+ });
23
+ }
24
+ }