@mcp-shark/mcp-shark 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +84 -645
  2. package/bin/mcp-shark.js +30 -36
  3. package/mcp-server/index.js +115 -0
  4. package/mcp-server/lib/auditor/audit.js +34 -42
  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 +21 -40
  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 +5 -7
  12. package/mcp-server/lib/server/internal/handlers/prompts-list.js +5 -4
  13. package/mcp-server/lib/server/internal/handlers/resources-list.js +5 -4
  14. package/mcp-server/lib/server/internal/handlers/resources-read.js +5 -4
  15. package/mcp-server/lib/server/internal/handlers/tools-call.js +8 -10
  16. package/mcp-server/lib/server/internal/handlers/tools-list.js +6 -5
  17. package/mcp-server/lib/server/internal/run.js +5 -17
  18. package/mcp-server/lib/server/internal/server.js +14 -15
  19. package/mcp-server/lib/server/internal/session.js +8 -13
  20. package/mcp-server/mcp-shark.js +16 -66
  21. package/package.json +23 -38
  22. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  23. package/ui/dist/assets/index-srLDlk97.js +35 -0
  24. package/ui/dist/index.html +17 -0
  25. package/ui/dist/og-image.png +0 -0
  26. package/ui/server/routes/backups/deleteBackup.js +54 -0
  27. package/ui/server/routes/backups/index.js +15 -0
  28. package/ui/server/routes/backups/listBackups.js +75 -0
  29. package/ui/server/routes/backups/restoreBackup.js +83 -0
  30. package/ui/server/routes/backups/viewBackup.js +47 -0
  31. package/ui/server/routes/composite/index.js +46 -0
  32. package/ui/server/routes/composite/servers.js +18 -0
  33. package/ui/server/routes/composite/setup.js +129 -0
  34. package/ui/server/routes/composite/status.js +7 -0
  35. package/ui/server/routes/composite/stop.js +39 -0
  36. package/ui/server/routes/composite/utils.js +45 -0
  37. package/ui/server/routes/config.js +34 -30
  38. package/ui/server/routes/conversations.js +3 -3
  39. package/ui/server/routes/help.js +2 -2
  40. package/ui/server/routes/logs.js +5 -5
  41. package/ui/server/routes/playground.js +91 -62
  42. package/ui/server/routes/requests.js +112 -108
  43. package/ui/server/routes/sessions.js +4 -4
  44. package/ui/server/routes/settings.js +199 -0
  45. package/ui/server/routes/smartscan/discover.js +7 -6
  46. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  47. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  48. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  49. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  51. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  52. package/ui/server/routes/smartscan/scans.js +3 -3
  53. package/ui/server/routes/smartscan/token.js +4 -3
  54. package/ui/server/routes/smartscan/transport.js +1 -1
  55. package/ui/server/routes/smartscan.js +1 -1
  56. package/ui/server/routes/statistics.js +13 -10
  57. package/ui/server/utils/config-update.js +140 -112
  58. package/ui/server/utils/config.js +4 -4
  59. package/ui/server/utils/logger.js +2 -0
  60. package/ui/server/utils/paths.js +210 -2
  61. package/ui/server/utils/port.js +2 -2
  62. package/ui/server/utils/process.js +0 -67
  63. package/ui/server/utils/scan-cache/all-results.js +76 -59
  64. package/ui/server/utils/scan-cache/directory.js +1 -1
  65. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  66. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  67. package/ui/server/utils/serialization.js +9 -3
  68. package/ui/server/utils/smartscan-token.js +4 -3
  69. package/ui/server.js +87 -41
  70. package/ui/src/App.jsx +5 -5
  71. package/ui/src/CompositeLogs.jsx +20 -20
  72. package/ui/src/CompositeSetup.jsx +9 -9
  73. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  74. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  75. package/ui/src/HelpGuide.jsx +17 -4
  76. package/ui/src/IntroTour.jsx +19 -5
  77. package/ui/src/LogDetail.jsx +1 -0
  78. package/ui/src/LogTable.jsx +24 -6
  79. package/ui/src/PacketDetail.jsx +21 -16
  80. package/ui/src/PacketFilters.jsx +29 -14
  81. package/ui/src/PacketList.jsx +4 -5
  82. package/ui/src/SmartScan.jsx +5 -5
  83. package/ui/src/TabNavigation.jsx +5 -5
  84. package/ui/src/components/App/HelpButton.jsx +4 -0
  85. package/ui/src/components/App/TrafficTab.jsx +4 -4
  86. package/ui/src/components/App/useAppState.js +118 -24
  87. package/ui/src/components/BackupList.jsx +6 -2
  88. package/ui/src/components/CollapsibleSection.jsx +16 -2
  89. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  90. package/ui/src/components/ConfirmationModal.jsx +20 -3
  91. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  92. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  93. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  94. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  95. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetectedPathsList.jsx +5 -2
  97. package/ui/src/components/FileInput.jsx +3 -1
  98. package/ui/src/components/GroupHeader.jsx +14 -0
  99. package/ui/src/components/GroupedByMcpView.jsx +3 -10
  100. package/ui/src/components/GroupedByServerView.jsx +1 -1
  101. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  102. package/ui/src/components/HexTab.jsx +17 -4
  103. package/ui/src/components/LogsToolbar.jsx +3 -1
  104. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  105. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +52 -23
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +13 -11
  108. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  109. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +66 -34
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +13 -11
  112. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  113. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +52 -23
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +13 -11
  116. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  117. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
  118. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +70 -0
  119. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +90 -0
  120. package/ui/src/components/McpPlayground/useMcpPlayground.js +118 -159
  121. package/ui/src/components/McpPlayground.jsx +105 -23
  122. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  123. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  124. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  125. package/ui/src/components/RawTab.jsx +15 -2
  126. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  127. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  128. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  129. package/ui/src/components/RequestRow.jsx +17 -9
  130. package/ui/src/components/ServerControl.jsx +3 -1
  131. package/ui/src/components/ServiceSelector.jsx +2 -0
  132. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  133. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  136. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  137. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  138. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  139. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  140. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  141. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  142. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  143. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  144. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  145. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  146. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  147. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  148. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  149. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  150. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  151. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  152. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  153. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  154. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  155. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  156. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  157. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  158. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  159. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  160. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  161. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  162. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  163. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  164. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  165. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  166. package/ui/src/components/SmartScan/utils.js +3 -1
  167. package/ui/src/components/SmartScanIcons.jsx +6 -3
  168. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  169. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  170. package/ui/src/components/TabNavigation.jsx +8 -3
  171. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  172. package/ui/src/components/TourOverlay.jsx +1 -1
  173. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  174. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  175. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  176. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  177. package/ui/src/components/TourTooltip.jsx +11 -3
  178. package/ui/src/components/ViewModeTabs.jsx +3 -1
  179. package/ui/src/config/tourSteps.jsx +0 -2
  180. package/ui/src/hooks/useAnimation.js +15 -12
  181. package/ui/src/hooks/useConfigManagement.js +8 -8
  182. package/ui/src/hooks/useServiceExtraction.js +1 -1
  183. package/ui/src/index.css +3 -8
  184. package/ui/src/theme.js +3 -3
  185. package/ui/src/utils/hexUtils.js +11 -5
  186. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  187. package/ui/src/utils/requestPairing.js +89 -0
  188. package/ui/src/utils/requestUtils.js +37 -105
  189. package/ui/vite.config.js +1 -1
  190. package/mcp-server/.editorconfig +0 -15
  191. package/mcp-server/.prettierignore +0 -11
  192. package/mcp-server/.prettierrc +0 -12
  193. package/mcp-server/README.md +0 -280
  194. package/mcp-server/commitlint.config.cjs +0 -42
  195. package/mcp-server/eslint.config.js +0 -131
  196. package/mcp-server/package-lock.json +0 -4784
  197. package/mcp-server/package.json +0 -30
  198. package/ui/README.md +0 -212
  199. package/ui/package-lock.json +0 -3574
  200. package/ui/package.json +0 -12
  201. package/ui/paths.js +0 -282
  202. package/ui/server/routes/backups.js +0 -251
  203. package/ui/server/routes/composite.js +0 -244
@@ -1,12 +1,12 @@
1
- import { serializeBigInt } from '../utils/serialization.js';
2
1
  import { queryConversations } from 'mcp-shark-common/db/query.js';
2
+ import { serializeBigInt } from '../utils/serialization.js';
3
3
 
4
4
  export function createConversationsRoutes(db) {
5
5
  const router = {};
6
6
 
7
7
  router.getConversations = (req, res) => {
8
- const limit = parseInt(req.query.limit) || 1000;
9
- const offset = parseInt(req.query.offset) || 0;
8
+ const limit = Number.parseInt(req.query.limit) || 1000;
9
+ const offset = Number.parseInt(req.query.offset) || 0;
10
10
  const filters = {
11
11
  sessionId: req.query.sessionId || null,
12
12
  method: req.query.method || null,
@@ -3,7 +3,7 @@ import { readHelpState, writeHelpState } from 'mcp-shark-common/configs/index.js
3
3
  export function createHelpRoutes() {
4
4
  const router = {};
5
5
 
6
- router.getState = (req, res) => {
6
+ router.getState = (_req, res) => {
7
7
  const state = readHelpState();
8
8
  res.json({
9
9
  dismissed: state.dismissed || false,
@@ -25,7 +25,7 @@ export function createHelpRoutes() {
25
25
  }
26
26
  };
27
27
 
28
- router.reset = (req, res) => {
28
+ router.reset = (_req, res) => {
29
29
  const state = {
30
30
  dismissed: false,
31
31
  tourCompleted: false,
@@ -1,19 +1,19 @@
1
- export function createLogsRoutes(mcpSharkLogs, broadcastLogUpdate) {
1
+ export function createLogsRoutes(mcpSharkLogs, _broadcastLogUpdate) {
2
2
  const router = {};
3
3
 
4
4
  router.getLogs = (req, res) => {
5
- const limit = parseInt(req.query.limit) || 1000;
6
- const offset = parseInt(req.query.offset) || 0;
5
+ const limit = Number.parseInt(req.query.limit) || 1000;
6
+ const offset = Number.parseInt(req.query.offset) || 0;
7
7
  const logs = [...mcpSharkLogs].reverse().slice(offset, offset + limit);
8
8
  res.json(logs);
9
9
  };
10
10
 
11
- router.clearLogs = (req, res) => {
11
+ router.clearLogs = (_req, res) => {
12
12
  mcpSharkLogs.length = 0;
13
13
  res.json({ success: true, message: 'Logs cleared' });
14
14
  };
15
15
 
16
- router.exportLogs = (req, res) => {
16
+ router.exportLogs = (_req, res) => {
17
17
  try {
18
18
  const logsText = mcpSharkLogs
19
19
  .map((log) => `[${log.timestamp}] [${log.type.toUpperCase()}] ${log.line}`)
@@ -1,20 +1,32 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import logger from '../utils/logger.js';
3
4
 
4
- const MCP_SERVER_URL = 'http://localhost:9851/mcp';
5
+ const MCP_SERVER_BASE_URL = 'http://localhost:9851/mcp';
5
6
 
6
- // Store client connections per session
7
+ // Store client connections per server and session
7
8
  const clientSessions = new Map();
8
9
 
10
+ function getSessionKey(serverName, sessionId) {
11
+ return `${serverName}:${sessionId}`;
12
+ }
13
+
9
14
  export function createPlaygroundRoutes() {
10
15
  const router = {};
11
16
 
12
- // Get or create client for a session
13
- async function getClient(sessionId) {
14
- if (clientSessions.has(sessionId)) {
15
- return clientSessions.get(sessionId);
17
+ // Get or create client for a session and server
18
+ async function getClient(serverName, sessionId) {
19
+ const sessionKey = getSessionKey(serverName, sessionId);
20
+ if (clientSessions.has(sessionKey)) {
21
+ return clientSessions.get(sessionKey);
16
22
  }
17
23
 
24
+ if (!serverName) {
25
+ throw new Error('Server name is required');
26
+ }
27
+
28
+ const mcpServerUrl = `${MCP_SERVER_BASE_URL}/${encodeURIComponent(serverName)}`;
29
+
18
30
  const client = new Client(
19
31
  { name: 'mcp-shark-playground', version: '1.0.0' },
20
32
  {
@@ -26,7 +38,7 @@ export function createPlaygroundRoutes() {
26
38
  }
27
39
  );
28
40
 
29
- const transport = new StreamableHTTPClientTransport(new URL(MCP_SERVER_URL));
41
+ const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl));
30
42
  await client.connect(transport);
31
43
 
32
44
  const clientWrapper = {
@@ -38,13 +50,13 @@ export function createPlaygroundRoutes() {
38
50
  },
39
51
  };
40
52
 
41
- clientSessions.set(sessionId, clientWrapper);
53
+ clientSessions.set(sessionKey, clientWrapper);
42
54
  return clientWrapper;
43
55
  }
44
56
 
45
57
  router.proxyRequest = async (req, res) => {
46
58
  try {
47
- const { method, params } = req.body;
59
+ const { method, params, serverName } = req.body;
48
60
 
49
61
  if (!method) {
50
62
  return res.status(400).json({
@@ -53,64 +65,68 @@ export function createPlaygroundRoutes() {
53
65
  });
54
66
  }
55
67
 
68
+ if (!serverName) {
69
+ return res.status(400).json({
70
+ error: 'Invalid request',
71
+ message: 'serverName field is required',
72
+ });
73
+ }
74
+
56
75
  // Get or create session ID
57
76
  const sessionId =
58
77
  req.headers['mcp-session-id'] ||
59
78
  req.headers['x-mcp-session-id'] ||
60
79
  `playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
61
80
 
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',
81
+ const { client } = await getClient(serverName, sessionId);
82
+
83
+ const executeMethod = async () => {
84
+ switch (method) {
85
+ case 'tools/list':
86
+ return await client.listTools();
87
+ case 'tools/call':
88
+ if (!params?.name) {
89
+ return res.status(400).json({
90
+ error: 'Invalid request',
91
+ message: 'Tool name is required',
92
+ });
93
+ }
94
+ return await client.callTool({
95
+ name: params.name,
96
+ arguments: params.arguments || {},
74
97
  });
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',
98
+ case 'prompts/list':
99
+ return await client.listPrompts();
100
+ case 'prompts/get':
101
+ if (!params?.name) {
102
+ return res.status(400).json({
103
+ error: 'Invalid request',
104
+ message: 'Prompt name is required',
105
+ });
106
+ }
107
+ return await client.getPrompt({
108
+ name: params.name,
109
+ arguments: params.arguments || {},
89
110
  });
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) {
111
+ case 'resources/list':
112
+ return await client.listResources();
113
+ case 'resources/read':
114
+ if (!params?.uri) {
115
+ return res.status(400).json({
116
+ error: 'Invalid request',
117
+ message: 'Resource URI is required',
118
+ });
119
+ }
120
+ return await client.readResource({ uri: params.uri });
121
+ default:
101
122
  return res.status(400).json({
102
- error: 'Invalid request',
103
- message: 'Resource URI is required',
123
+ error: 'Unsupported method',
124
+ message: `Method ${method} is not supported`,
104
125
  });
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
- }
126
+ }
127
+ };
128
+
129
+ const result = await executeMethod();
114
130
 
115
131
  // Return session ID in response
116
132
  res.setHeader('Mcp-Session-Id', sessionId);
@@ -119,7 +135,7 @@ export function createPlaygroundRoutes() {
119
135
  _sessionId: sessionId,
120
136
  });
121
137
  } catch (error) {
122
- console.error('Error in playground proxy:', error);
138
+ logger.error({ error: error.message }, 'Error in playground proxy');
123
139
 
124
140
  // Check if it's a connection error
125
141
  if (error.message?.includes('ECONNREFUSED') || error.message?.includes('connect')) {
@@ -140,10 +156,23 @@ export function createPlaygroundRoutes() {
140
156
  // Cleanup endpoint to close client connections
141
157
  router.cleanup = async (req, res) => {
142
158
  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);
159
+ const { serverName } = req.body || {};
160
+
161
+ if (serverName && sessionId) {
162
+ const sessionKey = getSessionKey(serverName, sessionId);
163
+ if (clientSessions.has(sessionKey)) {
164
+ const clientWrapper = clientSessions.get(sessionKey);
165
+ await clientWrapper.close();
166
+ clientSessions.delete(sessionKey);
167
+ }
168
+ } else if (sessionId) {
169
+ // Cleanup all sessions for this sessionId across all servers
170
+ for (const [key, clientWrapper] of clientSessions.entries()) {
171
+ if (key.endsWith(`:${sessionId}`)) {
172
+ await clientWrapper.close();
173
+ clientSessions.delete(key);
174
+ }
175
+ }
147
176
  }
148
177
  res.json({ success: true });
149
178
  };
@@ -1,22 +1,25 @@
1
- import { serializeBigInt } from '../utils/serialization.js';
2
1
  import { queryRequests } from 'mcp-shark-common/db/query.js';
2
+ import logger from '../utils/logger.js';
3
+ import { serializeBigInt } from '../utils/serialization.js';
4
+
5
+ const sanitizeSearch = (value) => {
6
+ if (value !== undefined && value !== null) {
7
+ const trimmed = String(value).trim();
8
+ return trimmed.length > 0 ? trimmed : null;
9
+ }
10
+ return null;
11
+ };
3
12
 
4
13
  export function createRequestsRoutes(db) {
5
14
  const router = {};
6
15
 
7
16
  router.getRequests = (req, res) => {
8
17
  try {
9
- const limit = parseInt(req.query.limit) || 1000;
10
- const offset = parseInt(req.query.offset) || 0;
18
+ const limit = Number.parseInt(req.query.limit) || 1000;
19
+ const offset = Number.parseInt(req.query.offset) || 0;
11
20
 
12
21
  // 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
- }
22
+ const search = sanitizeSearch(req.query.search);
20
23
 
21
24
  // Build filters object, ensuring all values are properly typed
22
25
  const filters = {
@@ -24,7 +27,7 @@ export function createRequestsRoutes(db) {
24
27
  direction: (req.query.direction && String(req.query.direction).trim()) || null,
25
28
  method: (req.query.method && String(req.query.method).trim()) || null,
26
29
  jsonrpcMethod: (req.query.jsonrpcMethod && String(req.query.jsonrpcMethod).trim()) || null,
27
- statusCode: req.query.statusCode ? parseInt(req.query.statusCode) : null,
30
+ statusCode: req.query.statusCode ? Number.parseInt(req.query.statusCode) : null,
28
31
  jsonrpcId: (req.query.jsonrpcId && String(req.query.jsonrpcId).trim()) || null,
29
32
  search: search,
30
33
  serverName: (req.query.serverName && String(req.query.serverName).trim()) || null,
@@ -44,21 +47,21 @@ export function createRequestsRoutes(db) {
44
47
  const requests = queryRequests(db, filters);
45
48
  res.json(serializeBigInt(requests));
46
49
  } catch (error) {
47
- console.error('Error in getRequests:', error);
50
+ logger.error({ error: error.message }, 'Error in getRequests');
48
51
  res.status(500).json({ error: 'Failed to query requests', details: error.message });
49
52
  }
50
53
  };
51
54
 
52
55
  router.getRequest = (req, res) => {
53
56
  const stmt = db.prepare('SELECT * FROM packets WHERE frame_number = ?');
54
- const request = stmt.get(parseInt(req.params.frameNumber));
57
+ const request = stmt.get(Number.parseInt(req.params.frameNumber));
55
58
  if (!request) {
56
59
  return res.status(404).json({ error: 'Request not found' });
57
60
  }
58
61
  res.json(serializeBigInt(request));
59
62
  };
60
63
 
61
- router.clearRequests = (req, res) => {
64
+ router.clearRequests = (_req, res) => {
62
65
  try {
63
66
  // Disable foreign key constraints temporarily to avoid constraint violations
64
67
  db.exec('PRAGMA foreign_keys = OFF');
@@ -87,19 +90,25 @@ export function createRequestsRoutes(db) {
87
90
  ];
88
91
 
89
92
  // 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);
93
+ const clearResults = trafficTables.reduce(
94
+ (acc, table) => {
95
+ if (existingTables.includes(table)) {
96
+ try {
97
+ db.exec(`DELETE FROM ${table}`);
98
+ return {
99
+ count: acc.count + 1,
100
+ tables: [...acc.tables, table],
101
+ };
102
+ } catch (err) {
103
+ logger.warn({ table, error: err.message }, 'Error clearing table');
104
+ }
100
105
  }
101
- }
102
- });
106
+ return acc;
107
+ },
108
+ { count: 0, tables: [] }
109
+ );
110
+ const clearedCount = clearResults.count;
111
+ const clearedTables = clearResults.tables;
103
112
 
104
113
  // Re-enable foreign key constraints
105
114
  db.exec('PRAGMA foreign_keys = ON');
@@ -112,10 +121,10 @@ export function createRequestsRoutes(db) {
112
121
  // Make sure to re-enable foreign keys even if there's an error
113
122
  try {
114
123
  db.exec('PRAGMA foreign_keys = ON');
115
- } catch (e) {
124
+ } catch (_e) {
116
125
  // Ignore
117
126
  }
118
- console.error('Error clearing requests:', error);
127
+ logger.error({ error: error.message }, 'Error clearing requests');
119
128
  res.status(500).json({ error: 'Failed to clear traffic', details: error.message });
120
129
  }
121
130
  };
@@ -123,20 +132,14 @@ export function createRequestsRoutes(db) {
123
132
  router.exportRequests = (req, res) => {
124
133
  try {
125
134
  // 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
- }
135
+ const search = sanitizeSearch(req.query.search);
133
136
 
134
137
  const filters = {
135
138
  sessionId: req.query.sessionId || null,
136
139
  direction: req.query.direction || null,
137
140
  method: req.query.method || null,
138
141
  jsonrpcMethod: req.query.jsonrpcMethod || null,
139
- statusCode: req.query.statusCode ? parseInt(req.query.statusCode) : null,
142
+ statusCode: req.query.statusCode ? Number.parseInt(req.query.statusCode) : null,
140
143
  jsonrpcId: req.query.jsonrpcId || null,
141
144
  search: search,
142
145
  serverName: req.query.serverName || null,
@@ -149,78 +152,79 @@ export function createRequestsRoutes(db) {
149
152
  const requests = queryRequests(db, filters);
150
153
  const format = req.query.format || 'json';
151
154
 
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
- }
155
+ const formatExport = (requests, format) => {
156
+ if (format === 'csv') {
157
+ const headers = [
158
+ 'Frame',
159
+ 'Time',
160
+ 'Source',
161
+ 'Destination',
162
+ 'Protocol',
163
+ 'Length',
164
+ 'Method',
165
+ 'Status',
166
+ 'JSON-RPC Method',
167
+ 'Session ID',
168
+ 'Server Name',
169
+ ];
170
+ const rows = requests.map((req) => [
171
+ req.frame_number || '',
172
+ req.timestamp_iso || '',
173
+ req.request?.host || '',
174
+ req.request?.host || '',
175
+ 'HTTP',
176
+ req.length || '',
177
+ req.request?.method || '',
178
+ req.response?.status_code || '',
179
+ req.jsonrpc_method || '',
180
+ req.session_id || '',
181
+ req.server_name || '',
182
+ ]);
183
+
184
+ const content = [
185
+ headers.join(','),
186
+ ...rows.map((row) =>
187
+ row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')
188
+ ),
189
+ ].join('\n');
190
+ return { content, contentType: 'text/csv', extension: 'csv' };
191
+ }
192
+
193
+ if (format === 'txt') {
194
+ const content = requests
195
+ .map((req, idx) => {
196
+ const lines = [
197
+ `=== Request/Response #${idx + 1} (Frame ${req.frame_number || 'N/A'}) ===`,
198
+ `Time: ${req.timestamp_iso || 'N/A'}`,
199
+ `Session ID: ${req.session_id || 'N/A'}`,
200
+ `Server: ${req.server_name || 'N/A'}`,
201
+ `Direction: ${req.direction || 'N/A'}`,
202
+ `Method: ${req.request?.method || 'N/A'}`,
203
+ `Status: ${req.response?.status_code || 'N/A'}`,
204
+ `JSON-RPC Method: ${req.jsonrpc_method || 'N/A'}`,
205
+ `JSON-RPC ID: ${req.jsonrpc_id || 'N/A'}`,
206
+ `Length: ${req.length || 0} bytes`,
207
+ '',
208
+ 'Request:',
209
+ JSON.stringify(req.request || {}, null, 2),
210
+ '',
211
+ 'Response:',
212
+ JSON.stringify(req.response || {}, null, 2),
213
+ '',
214
+ '---',
215
+ '',
216
+ ];
217
+ return lines.join('\n');
218
+ })
219
+ .join('\n');
220
+ return { content, contentType: 'text/plain', extension: 'txt' };
221
+ }
222
+
223
+ const content = JSON.stringify(serializeBigInt(requests), null, 2);
224
+ return { content, contentType: 'application/json', extension: 'json' };
225
+ };
226
+
227
+ const { content, contentType, extension } = formatExport(requests, format);
224
228
 
225
229
  const filename = `mcp-shark-traffic-${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`;
226
230
  res.setHeader('Content-Type', contentType);
@@ -1,12 +1,12 @@
1
+ import { getSessionRequests, getSessions } from 'mcp-shark-common/db/query.js';
1
2
  import { serializeBigInt } from '../utils/serialization.js';
2
- import { getSessions, getSessionRequests } from 'mcp-shark-common/db/query.js';
3
3
 
4
4
  export function createSessionsRoutes(db) {
5
5
  const router = {};
6
6
 
7
7
  router.getSessions = (req, res) => {
8
- const limit = parseInt(req.query.limit) || 1000;
9
- const offset = parseInt(req.query.offset) || 0;
8
+ const limit = Number.parseInt(req.query.limit) || 1000;
9
+ const offset = Number.parseInt(req.query.offset) || 0;
10
10
  const filters = {
11
11
  startTime: req.query.startTime ? BigInt(req.query.startTime) : null,
12
12
  endTime: req.query.endTime ? BigInt(req.query.endTime) : null,
@@ -18,7 +18,7 @@ export function createSessionsRoutes(db) {
18
18
  };
19
19
 
20
20
  router.getSessionRequests = (req, res) => {
21
- const limit = parseInt(req.query.limit) || 10000;
21
+ const limit = Number.parseInt(req.query.limit) || 10000;
22
22
  const requests = getSessionRequests(db, req.params.sessionId, limit);
23
23
  res.json(serializeBigInt(requests));
24
24
  };