@mcp-shark/mcp-shark 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +84 -645
  2. package/bin/mcp-shark.js +30 -36
  3. package/mcp-server/index.js +115 -0
  4. package/mcp-server/lib/auditor/audit.js +22 -38
  5. package/mcp-server/lib/common/error.js +1 -1
  6. package/mcp-server/lib/server/external/all.js +5 -6
  7. package/mcp-server/lib/server/external/config.js +1 -3
  8. package/mcp-server/lib/server/external/kv.js +4 -12
  9. package/mcp-server/lib/server/external/single/request.js +3 -6
  10. package/mcp-server/lib/server/external/single/run.js +8 -19
  11. package/mcp-server/lib/server/internal/handlers/prompts-get.js +3 -13
  12. package/mcp-server/lib/server/internal/handlers/prompts-list.js +2 -6
  13. package/mcp-server/lib/server/internal/handlers/resources-list.js +2 -6
  14. package/mcp-server/lib/server/internal/handlers/resources-read.js +3 -12
  15. package/mcp-server/lib/server/internal/handlers/tools-call.js +3 -9
  16. package/mcp-server/lib/server/internal/handlers/tools-list.js +2 -2
  17. package/mcp-server/lib/server/internal/run.js +4 -16
  18. package/mcp-server/lib/server/internal/server.js +6 -7
  19. package/mcp-server/lib/server/internal/session.js +2 -15
  20. package/mcp-server/mcp-shark.js +16 -66
  21. package/package.json +23 -38
  22. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  23. package/ui/dist/assets/index-srLDlk97.js +35 -0
  24. package/ui/dist/index.html +17 -0
  25. package/ui/dist/og-image.png +0 -0
  26. package/ui/server/routes/backups/deleteBackup.js +54 -0
  27. package/ui/server/routes/backups/index.js +15 -0
  28. package/ui/server/routes/backups/listBackups.js +75 -0
  29. package/ui/server/routes/backups/restoreBackup.js +83 -0
  30. package/ui/server/routes/backups/viewBackup.js +47 -0
  31. package/ui/server/routes/composite/index.js +46 -0
  32. package/ui/server/routes/composite/servers.js +18 -0
  33. package/ui/server/routes/composite/setup.js +129 -0
  34. package/ui/server/routes/composite/status.js +7 -0
  35. package/ui/server/routes/composite/stop.js +39 -0
  36. package/ui/server/routes/composite/utils.js +45 -0
  37. package/ui/server/routes/config.js +34 -30
  38. package/ui/server/routes/conversations.js +3 -3
  39. package/ui/server/routes/help.js +2 -2
  40. package/ui/server/routes/logs.js +5 -5
  41. package/ui/server/routes/playground.js +45 -47
  42. package/ui/server/routes/requests.js +112 -108
  43. package/ui/server/routes/sessions.js +4 -4
  44. package/ui/server/routes/settings.js +199 -0
  45. package/ui/server/routes/smartscan/discover.js +7 -6
  46. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  47. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  48. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  49. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  51. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  52. package/ui/server/routes/smartscan/scans.js +3 -3
  53. package/ui/server/routes/smartscan/token.js +4 -3
  54. package/ui/server/routes/smartscan/transport.js +1 -1
  55. package/ui/server/routes/smartscan.js +1 -1
  56. package/ui/server/routes/statistics.js +13 -10
  57. package/ui/server/utils/config-update.js +7 -6
  58. package/ui/server/utils/config.js +4 -4
  59. package/ui/server/utils/logger.js +2 -0
  60. package/ui/server/utils/paths.js +210 -2
  61. package/ui/server/utils/port.js +2 -2
  62. package/ui/server/utils/process.js +0 -67
  63. package/ui/server/utils/scan-cache/all-results.js +76 -59
  64. package/ui/server/utils/scan-cache/directory.js +1 -1
  65. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  66. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  67. package/ui/server/utils/serialization.js +9 -3
  68. package/ui/server/utils/smartscan-token.js +4 -3
  69. package/ui/server.js +86 -41
  70. package/ui/src/App.jsx +5 -5
  71. package/ui/src/CompositeLogs.jsx +20 -20
  72. package/ui/src/CompositeSetup.jsx +9 -9
  73. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  74. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  75. package/ui/src/HelpGuide.jsx +17 -4
  76. package/ui/src/IntroTour.jsx +19 -5
  77. package/ui/src/LogDetail.jsx +1 -0
  78. package/ui/src/LogTable.jsx +24 -6
  79. package/ui/src/PacketDetail.jsx +21 -16
  80. package/ui/src/PacketFilters.jsx +29 -14
  81. package/ui/src/PacketList.jsx +4 -5
  82. package/ui/src/SmartScan.jsx +5 -5
  83. package/ui/src/TabNavigation.jsx +5 -5
  84. package/ui/src/components/App/HelpButton.jsx +4 -0
  85. package/ui/src/components/App/TrafficTab.jsx +4 -4
  86. package/ui/src/components/App/useAppState.js +118 -24
  87. package/ui/src/components/BackupList.jsx +6 -2
  88. package/ui/src/components/CollapsibleSection.jsx +16 -2
  89. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  90. package/ui/src/components/ConfirmationModal.jsx +20 -3
  91. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  92. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  93. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  94. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  95. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetectedPathsList.jsx +5 -2
  97. package/ui/src/components/FileInput.jsx +3 -1
  98. package/ui/src/components/GroupHeader.jsx +14 -0
  99. package/ui/src/components/GroupedByMcpView.jsx +3 -4
  100. package/ui/src/components/GroupedByServerView.jsx +1 -1
  101. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  102. package/ui/src/components/HexTab.jsx +17 -4
  103. package/ui/src/components/LogsToolbar.jsx +3 -1
  104. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  105. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
  108. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  109. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
  112. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  113. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
  116. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  117. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
  118. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
  119. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
  120. package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
  121. package/ui/src/components/McpPlayground.jsx +5 -2
  122. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  123. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  124. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  125. package/ui/src/components/RawTab.jsx +15 -2
  126. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  127. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  128. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  129. package/ui/src/components/RequestRow.jsx +17 -9
  130. package/ui/src/components/ServerControl.jsx +3 -1
  131. package/ui/src/components/ServiceSelector.jsx +2 -0
  132. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  133. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  136. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  137. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  138. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  139. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  140. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  141. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  142. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  143. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  144. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  145. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  146. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  147. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  148. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  149. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  150. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  151. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  152. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  153. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  154. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  155. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  156. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  157. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  158. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  159. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  160. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  161. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  162. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  163. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  164. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  165. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  166. package/ui/src/components/SmartScan/utils.js +3 -1
  167. package/ui/src/components/SmartScanIcons.jsx +6 -3
  168. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  169. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  170. package/ui/src/components/TabNavigation.jsx +8 -3
  171. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  172. package/ui/src/components/TourOverlay.jsx +1 -1
  173. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  174. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  175. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  176. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  177. package/ui/src/components/TourTooltip.jsx +11 -3
  178. package/ui/src/components/ViewModeTabs.jsx +3 -1
  179. package/ui/src/config/tourSteps.jsx +0 -2
  180. package/ui/src/hooks/useAnimation.js +15 -12
  181. package/ui/src/hooks/useConfigManagement.js +8 -8
  182. package/ui/src/hooks/useServiceExtraction.js +1 -1
  183. package/ui/src/index.css +3 -8
  184. package/ui/src/theme.js +3 -3
  185. package/ui/src/utils/hexUtils.js +11 -5
  186. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  187. package/ui/src/utils/requestPairing.js +89 -0
  188. package/ui/src/utils/requestUtils.js +32 -101
  189. package/ui/vite.config.js +1 -1
  190. package/mcp-server/.editorconfig +0 -15
  191. package/mcp-server/.prettierignore +0 -11
  192. package/mcp-server/.prettierrc +0 -12
  193. package/mcp-server/README.md +0 -280
  194. package/mcp-server/commitlint.config.cjs +0 -42
  195. package/mcp-server/eslint.config.js +0 -131
  196. package/mcp-server/package-lock.json +0 -4784
  197. package/mcp-server/package.json +0 -30
  198. package/ui/README.md +0 -212
  199. package/ui/package-lock.json +0 -3574
  200. package/ui/package.json +0 -12
  201. package/ui/paths.js +0 -282
  202. package/ui/server/routes/backups.js +0 -251
  203. package/ui/server/routes/composite.js +0 -260
@@ -0,0 +1,89 @@
1
+ import { getJsonRpcMethod } from './requestUtils.js';
2
+
3
+ export function pairRequestsWithResponses(requests) {
4
+ const pairs = [];
5
+ const processed = new Set();
6
+
7
+ // Helper function to check if two requests match (same session, JSON-RPC method, and optionally jsonrpc_id)
8
+ const matches = (req, resp) => {
9
+ // Session ID must match (or both null for initiation)
10
+ const sessionMatch = req.session_id === resp.session_id;
11
+ if (!sessionMatch) {
12
+ return false;
13
+ }
14
+
15
+ // JSON-RPC Method must match
16
+ const reqMethod = getJsonRpcMethod(req);
17
+ const respMethod = getJsonRpcMethod(resp);
18
+
19
+ // Both must have a method, and they must match
20
+ if (!reqMethod || !respMethod) {
21
+ // If either doesn't have a method, we can't match by method
22
+ // Fall back to JSON-RPC ID matching only
23
+ if (req.jsonrpc_id && resp.jsonrpc_id) {
24
+ return req.jsonrpc_id === resp.jsonrpc_id;
25
+ }
26
+ // If no method and no ID, we can't match reliably
27
+ return false;
28
+ }
29
+
30
+ const methodMatch = reqMethod === respMethod;
31
+ if (!methodMatch) {
32
+ return false;
33
+ }
34
+
35
+ // If JSON-RPC ID exists, it must match (for more precise pairing)
36
+ if (req.jsonrpc_id && resp.jsonrpc_id) {
37
+ return req.jsonrpc_id === resp.jsonrpc_id;
38
+ }
39
+
40
+ // If no JSON-RPC ID, match by session and method only
41
+ return true;
42
+ };
43
+
44
+ requests.forEach((request) => {
45
+ if (processed.has(request.frame_number)) {
46
+ return;
47
+ }
48
+
49
+ if (request.direction === 'request') {
50
+ // Find matching response - must match session, endpoint, and optionally jsonrpc_id
51
+ const response = requests.find(
52
+ (r) =>
53
+ r.direction === 'response' &&
54
+ !processed.has(r.frame_number) &&
55
+ matches(request, r) &&
56
+ r.frame_number > request.frame_number
57
+ );
58
+
59
+ if (response) {
60
+ pairs.push({ request, response, frame_number: request.frame_number });
61
+ processed.add(request.frame_number);
62
+ processed.add(response.frame_number);
63
+ } else {
64
+ // Request without response
65
+ pairs.push({ request, response: null, frame_number: request.frame_number });
66
+ processed.add(request.frame_number);
67
+ }
68
+ } else if (request.direction === 'response') {
69
+ // Find matching request - must match session, endpoint, and optionally jsonrpc_id
70
+ const matchingRequest = requests.find(
71
+ (r) =>
72
+ r.direction === 'request' &&
73
+ !processed.has(r.frame_number) &&
74
+ matches(r, request) &&
75
+ r.frame_number < request.frame_number
76
+ );
77
+
78
+ if (!matchingRequest) {
79
+ // Response without request (orphaned)
80
+ pairs.push({ request: null, response: request, frame_number: request.frame_number });
81
+ processed.add(request.frame_number);
82
+ }
83
+ // If matching request exists, it will be handled when we iterate over it
84
+ }
85
+ });
86
+
87
+ // Sort by frame number (descending - latest first)
88
+ return pairs.sort((a, b) => b.frame_number - a.frame_number);
89
+ }
@@ -4,11 +4,11 @@ export function extractServerName(request) {
4
4
  try {
5
5
  const body =
6
6
  typeof request.body_json === 'string' ? JSON.parse(request.body_json) : request.body_json;
7
- if (body.params && body.params.name) {
7
+ if (body.params?.name) {
8
8
  const fullName = body.params.name;
9
9
  return fullName.includes('.') ? fullName.split('.')[0] : fullName;
10
10
  }
11
- } catch (e) {
11
+ } catch (_e) {
12
12
  // Failed to parse JSON, try body_raw
13
13
  }
14
14
  }
@@ -17,11 +17,11 @@ export function extractServerName(request) {
17
17
  try {
18
18
  const body =
19
19
  typeof request.body_raw === 'string' ? JSON.parse(request.body_raw) : request.body_raw;
20
- if (body.params && body.params.name) {
20
+ if (body.params?.name) {
21
21
  const fullName = body.params.name;
22
22
  return fullName.includes('.') ? fullName.split('.')[0] : fullName;
23
23
  }
24
- } catch (e) {
24
+ } catch (_e) {
25
25
  // Failed to parse
26
26
  }
27
27
  }
@@ -34,13 +34,17 @@ export function extractServerName(request) {
34
34
  }
35
35
 
36
36
  export function formatRelativeTime(timestampISO, firstTime) {
37
- if (!firstTime) return '0.000000';
37
+ if (!firstTime) {
38
+ return '0.000000';
39
+ }
38
40
  const diff = new Date(timestampISO) - new Date(firstTime);
39
41
  return (diff / 1000).toFixed(6);
40
42
  }
41
43
 
42
44
  export function formatDateTime(timestampISO) {
43
- if (!timestampISO) return '-';
45
+ if (!timestampISO) {
46
+ return '-';
47
+ }
44
48
  try {
45
49
  const date = new Date(timestampISO);
46
50
  return date.toLocaleString('en-US', {
@@ -52,7 +56,7 @@ export function formatDateTime(timestampISO) {
52
56
  second: '2-digit',
53
57
  hour12: false,
54
58
  });
55
- } catch (e) {
59
+ } catch (_e) {
56
60
  return timestampISO;
57
61
  }
58
62
  }
@@ -79,7 +83,7 @@ export function getEndpoint(request) {
79
83
  if (body && typeof body === 'object' && body.method) {
80
84
  return body.method;
81
85
  }
82
- } catch (e) {
86
+ } catch (_e) {
83
87
  // Failed to parse JSON, try body_raw
84
88
  }
85
89
  }
@@ -90,7 +94,7 @@ export function getEndpoint(request) {
90
94
  if (body && typeof body === 'object' && body.method) {
91
95
  return body.method;
92
96
  }
93
- } catch (e) {
97
+ } catch (_e) {
94
98
  // Failed to parse
95
99
  }
96
100
  }
@@ -101,7 +105,7 @@ export function getEndpoint(request) {
101
105
  try {
102
106
  const url = new URL(request.url);
103
107
  return url.pathname + (url.search || '');
104
- } catch (e) {
108
+ } catch (_e) {
105
109
  const url = request.url;
106
110
  const match = url.match(/^https?:\/\/[^\/]+(\/.*)$/);
107
111
  return match ? match[1] : url;
@@ -127,15 +131,19 @@ export function getInfo(request) {
127
131
  // If we have both HTTP method and endpoint, show both
128
132
  if (httpMethod && url) {
129
133
  return `${httpMethod} ${endpoint}`;
130
- } else if (httpMethod) {
134
+ }
135
+ if (httpMethod) {
131
136
  return `${httpMethod} ${endpoint}`;
132
137
  }
133
138
  return endpoint;
134
- } else if (httpMethod && url) {
139
+ }
140
+ if (httpMethod && url) {
135
141
  return `${httpMethod} ${url}`;
136
- } else if (httpMethod) {
142
+ }
143
+ if (httpMethod) {
137
144
  return httpMethod;
138
- } else if (url) {
145
+ }
146
+ if (url) {
139
147
  return url;
140
148
  }
141
149
  return 'Request';
@@ -149,9 +157,11 @@ export function getInfo(request) {
149
157
 
150
158
  if (status && rpcMethod) {
151
159
  return `${status} ${rpcMethod}`;
152
- } else if (status) {
160
+ }
161
+ if (status) {
153
162
  return `Status: ${status}`;
154
- } else if (rpcMethod) {
163
+ }
164
+ if (rpcMethod) {
155
165
  return rpcMethod;
156
166
  }
157
167
  return 'Response';
@@ -185,7 +195,7 @@ export function getJsonRpcMethod(req) {
185
195
  if (body && typeof body === 'object' && body.method) {
186
196
  return body.method;
187
197
  }
188
- } catch (e) {
198
+ } catch (_e) {
189
199
  // Failed to parse
190
200
  }
191
201
  }
@@ -195,7 +205,7 @@ export function getJsonRpcMethod(req) {
195
205
  if (body && typeof body === 'object' && body.method) {
196
206
  return body.method;
197
207
  }
198
- } catch (e) {
208
+ } catch (_e) {
199
209
  // Failed to parse
200
210
  }
201
211
  }
@@ -204,10 +214,10 @@ export function getJsonRpcMethod(req) {
204
214
  // For responses, try to extract from body if available
205
215
  if (req.direction === 'response' && req.body_json) {
206
216
  try {
207
- const body = typeof req.body_json === 'string' ? JSON.parse(req.body_json) : req.body_json;
217
+ const _body = typeof req.body_json === 'string' ? JSON.parse(req.body_json) : req.body_json;
208
218
  // Responses don't have a method field, but we can check if it's an error response
209
219
  // For now, we'll rely on jsonrpc_method field
210
- } catch (e) {
220
+ } catch (_e) {
211
221
  // Failed to parse
212
222
  }
213
223
  }
@@ -215,84 +225,5 @@ export function getJsonRpcMethod(req) {
215
225
  return null;
216
226
  }
217
227
 
218
- export function pairRequestsWithResponses(requests) {
219
- const pairs = [];
220
- const processed = new Set();
221
-
222
- // Helper function to check if two requests match (same session, JSON-RPC method, and optionally jsonrpc_id)
223
- const matches = (req, resp) => {
224
- // Session ID must match (or both null for initiation)
225
- const sessionMatch = req.session_id === resp.session_id;
226
- if (!sessionMatch) return false;
227
-
228
- // JSON-RPC Method must match
229
- const reqMethod = getJsonRpcMethod(req);
230
- const respMethod = getJsonRpcMethod(resp);
231
-
232
- // Both must have a method, and they must match
233
- if (!reqMethod || !respMethod) {
234
- // If either doesn't have a method, we can't match by method
235
- // Fall back to JSON-RPC ID matching only
236
- if (req.jsonrpc_id && resp.jsonrpc_id) {
237
- return req.jsonrpc_id === resp.jsonrpc_id;
238
- }
239
- // If no method and no ID, we can't match reliably
240
- return false;
241
- }
242
-
243
- const methodMatch = reqMethod === respMethod;
244
- if (!methodMatch) return false;
245
-
246
- // If JSON-RPC ID exists, it must match (for more precise pairing)
247
- if (req.jsonrpc_id && resp.jsonrpc_id) {
248
- return req.jsonrpc_id === resp.jsonrpc_id;
249
- }
250
-
251
- // If no JSON-RPC ID, match by session and method only
252
- return true;
253
- };
254
-
255
- requests.forEach((request) => {
256
- if (processed.has(request.frame_number)) return;
257
-
258
- if (request.direction === 'request') {
259
- // Find matching response - must match session, endpoint, and optionally jsonrpc_id
260
- const response = requests.find(
261
- (r) =>
262
- r.direction === 'response' &&
263
- !processed.has(r.frame_number) &&
264
- matches(request, r) &&
265
- r.frame_number > request.frame_number
266
- );
267
-
268
- if (response) {
269
- pairs.push({ request, response, frame_number: request.frame_number });
270
- processed.add(request.frame_number);
271
- processed.add(response.frame_number);
272
- } else {
273
- // Request without response
274
- pairs.push({ request, response: null, frame_number: request.frame_number });
275
- processed.add(request.frame_number);
276
- }
277
- } else if (request.direction === 'response') {
278
- // Find matching request - must match session, endpoint, and optionally jsonrpc_id
279
- const matchingRequest = requests.find(
280
- (r) =>
281
- r.direction === 'request' &&
282
- !processed.has(r.frame_number) &&
283
- matches(r, request) &&
284
- r.frame_number < request.frame_number
285
- );
286
-
287
- if (!matchingRequest) {
288
- // Response without request (orphaned)
289
- pairs.push({ request: null, response: request, frame_number: request.frame_number });
290
- processed.add(request.frame_number);
291
- }
292
- // If matching request exists, it will be handled when we iterate over it
293
- }
294
- });
295
-
296
- // Sort by frame number (descending - latest first)
297
- return pairs.sort((a, b) => b.frame_number - a.frame_number);
298
- }
228
+ // Re-export pairRequestsWithResponses from requestPairing.js
229
+ export { pairRequestsWithResponses } from './requestPairing.js';
package/ui/vite.config.js CHANGED
@@ -1,5 +1,5 @@
1
- import { defineConfig } from 'vite';
2
1
  import react from '@vitejs/plugin-react';
2
+ import { defineConfig } from 'vite';
3
3
 
4
4
  export default defineConfig({
5
5
  plugins: [react()],
@@ -1,15 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- charset = utf-8
5
- end_of_line = lf
6
- insert_final_newline = true
7
- trim_trailing_whitespace = true
8
-
9
- [*.{js,jsx,ts,tsx,json,css,md}]
10
- indent_style = space
11
- indent_size = 2
12
-
13
- [*.md]
14
- trim_trailing_whitespace = false
15
-
@@ -1,11 +0,0 @@
1
- node_modules
2
- dist
3
- build
4
- coverage
5
- *.min.js
6
- .git
7
- .husky
8
- package-lock.json
9
- yarn.lock
10
- pnpm-lock.yaml
11
-
@@ -1,12 +0,0 @@
1
- {
2
- "semi": true,
3
- "trailingComma": "es5",
4
- "singleQuote": true,
5
- "printWidth": 80,
6
- "tabWidth": 2,
7
- "useTabs": false,
8
- "arrowParens": "avoid",
9
- "endOfLine": "lf",
10
- "bracketSpacing": true
11
- }
12
-
@@ -1,280 +0,0 @@
1
- # MCP Shark Server
2
-
3
- > **Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface**
4
-
5
- MCP Shark Server is a powerful aggregation layer that combines multiple MCP servers (both HTTP and stdio-based) into one cohesive endpoint. It provides a unified API for tools, prompts, and resources from all connected servers, with comprehensive audit logging and session management.
6
-
7
- ## ✨ Features
8
-
9
- - **🔗 Multi-Server Aggregation**: Connect to multiple MCP servers simultaneously (HTTP and stdio)
10
- - **📊 Comprehensive Audit Logging**: SQLite-based logging with request/response tracking, performance metrics, and error handling
11
- - **🌐 HTTP Interface**: RESTful API endpoint for easy integration with any MCP client
12
- - **🔄 Session Management**: Automatic session handling for stateful MCP interactions
13
- - **🛠️ Unified Tool Access**: Access tools from all connected servers through a single interface
14
- - **📝 Prompt & Resource Aggregation**: Unified access to prompts and resources across all servers
15
- - **⚡ Streaming Support**: Full support for async iterable responses (streaming)
16
- - **🔍 Request Correlation**: Track request/response pairs with correlation IDs
17
-
18
- ## 🚀 Quick Start
19
-
20
- ### Installation
21
-
22
- ```bash
23
- npm install
24
- ```
25
-
26
- ### Configuration
27
-
28
- **Recommended: Use the UI**
29
-
30
- The recommended way to configure the MCP server is through the UI interface, which automatically detects and converts IDE configuration files.
31
-
32
- **Alternative: Manual Configuration**
33
-
34
- If running the server directly, create a configuration file at `~/.mcp-shark/mcps.json`:
35
-
36
- ```json
37
- {
38
- "servers": {
39
- "github": {
40
- "type": "http",
41
- "url": "https://api.githubcopilot.com/mcp/",
42
- "headers": {
43
- "Authorization": "Bearer YOUR_TOKEN"
44
- }
45
- },
46
- "@21st-dev/magic": {
47
- "type": "stdio",
48
- "command": "npx",
49
- "args": ["-y", "@21st-dev/magic@latest", "API_KEY=\"your-api-key\""]
50
- }
51
- }
52
- }
53
- ```
54
-
55
- ### Running
56
-
57
- **Recommended: Managed through UI**
58
-
59
- The recommended way to run the MCP server is through the UI interface:
60
-
61
- 1. Start the UI server (see main README)
62
- 2. Use the UI's "MCP Server Setup" tab to configure and start the server
63
-
64
- **Alternative: Run Directly**
65
-
66
- If you need to run the server independently:
67
-
68
- ```bash
69
- npm start
70
- ```
71
-
72
- The server will start on `http://localhost:9851/mcp`
73
-
74
- **Note:** When running directly, ensure you have a valid configuration file at `~/.mcp-shark/mcps.json` before starting. The UI handles configuration automatically when using the recommended workflow.
75
-
76
- ## 📖 Usage
77
-
78
- ### Endpoint
79
-
80
- All MCP requests should be sent to:
81
-
82
- ```
83
- POST http://localhost:9851/mcp
84
- ```
85
-
86
- ### Example: List All Tools
87
-
88
- ```bash
89
- curl -X POST http://localhost:9851/mcp \
90
- -H "Content-Type: application/json" \
91
- -d '{
92
- "jsonrpc": "2.0",
93
- "id": 1,
94
- "method": "tools/list"
95
- }'
96
- ```
97
-
98
- ### Example: Call a Tool
99
-
100
- Tools from different servers are prefixed with the server name:
101
-
102
- ```bash
103
- curl -X POST http://localhost:9851/mcp \
104
- -H "Content-Type: application/json" \
105
- -d '{
106
- "jsonrpc": "2.0",
107
- "id": 2,
108
- "method": "tools/call",
109
- "params": {
110
- "name": "github:search_repositories",
111
- "arguments": {
112
- "query": "language:javascript stars:>1000"
113
- }
114
- }
115
- }'
116
- ```
117
-
118
- ## 🏗️ Architecture
119
-
120
- ```
121
- ┌─────────────────┐
122
- │ MCP Client │
123
- └────────┬────────┘
124
- │ HTTP
125
-
126
- ┌─────────────────────────────────┐
127
- │ MCP Shark Server │
128
- │ (Express on port 9851) │
129
- │ │
130
- │ ┌────────────────────────────┐ │
131
- │ │ Internal MCP Server │ │
132
- │ │ - tools/list │ │
133
- │ │ - tools/call │ │
134
- │ │ - prompts/list │ │
135
- │ │ - prompts/get │ │
136
- │ │ - resources/list │ │
137
- │ │ - resources/read │ │
138
- │ └──────────┬──────────────────┘ │
139
- │ │ │
140
- │ ┌──────────▼──────────────────┐ │
141
- │ │ Audit Logger (SQLite) │ │
142
- │ └──────────────────────────────┘ │
143
- └──────────┬────────────────────────┘
144
-
145
- ├──► HTTP MCP Server
146
- ├──► stdio MCP Server
147
- └──► stdio MCP Server
148
- ```
149
-
150
- ## 📁 Project Structure
151
-
152
- ```
153
- mcp-server/
154
- ├── mcp-shark.js # Main entry point
155
- ├── lib/
156
- │ ├── server/
157
- │ │ ├── internal/ # Internal MCP server (aggregator)
158
- │ │ │ ├── server.js # Server creation
159
- │ │ │ ├── run.js # Express server setup
160
- │ │ │ ├── session.js # Session management
161
- │ │ │ └── handlers/ # Request handlers
162
- │ │ └── external/ # External MCP server clients
163
- │ │ ├── all.js # Multi-server orchestration
164
- │ │ ├── config.js # Configuration parsing
165
- │ │ ├── kv.js # Key-value store for servers
166
- │ │ └── single/ # Single server client
167
- │ ├── db/
168
- │ │ ├── init.js # Database initialization
169
- │ │ ├── logger.js # Audit logging
170
- │ │ └── query.js # Database queries
171
- │ └── common/
172
- │ └── error.js # Error handling utilities
173
- └── lib/
174
- └── ...
175
- ```
176
-
177
- **Note:** Configuration and database files are stored in `~/.mcp-shark/` by default:
178
-
179
- - `~/.mcp-shark/mcps.json` - Server configuration
180
- - `~/.mcp-shark/db/mcp-shark.sqlite` - SQLite database
181
-
182
- ## 🔧 Configuration Format
183
-
184
- ### HTTP Server
185
-
186
- ```json
187
- {
188
- "servers": {
189
- "server-name": {
190
- "type": "http",
191
- "url": "https://api.example.com/mcp/",
192
- "headers": {
193
- "Authorization": "Bearer TOKEN"
194
- }
195
- }
196
- }
197
- }
198
- ```
199
-
200
- ### stdio Server
201
-
202
- ```json
203
- {
204
- "servers": {
205
- "server-name": {
206
- "type": "stdio",
207
- "command": "node",
208
- "args": ["path/to/server.js"]
209
- }
210
- }
211
- }
212
- ```
213
-
214
- ## 📊 Audit Logging
215
-
216
- All MCP communications are logged to SQLite (default location: `~/.mcp-shark/db/mcp-shark.sqlite`) with:
217
-
218
- - **Request/Response Tracking**: Full payload logging with correlation IDs
219
- - **Performance Metrics**: Duration, latency, and timing information
220
- - **Error Tracking**: Comprehensive error logging with stack traces
221
- - **Session Management**: Session ID tracking for stateful interactions
222
- - **Server Identification**: Track which external server handled each request
223
-
224
- ### Database Schema
225
-
226
- - `mcp_communications`: All request/response communications
227
- - `mcp_request_response_pairs`: Correlated request/response pairs
228
-
229
- ## 🛠️ Development
230
-
231
- ### Scripts
232
-
233
- ```bash
234
- # Start the server
235
- npm start
236
-
237
- # Lint code
238
- npm run lint
239
-
240
- # Fix linting issues
241
- npm run lint:fix
242
-
243
- # Format code
244
- npm run format
245
-
246
- # Check formatting
247
- npm run format:check
248
- ```
249
-
250
- ### Dependencies
251
-
252
- This package uses `mcp-shark-common` for shared database utilities and configuration management. The database initialization and logging functionality is provided by the common package.
253
-
254
- ### Code Quality
255
-
256
- - **ESLint**: Code linting with Prettier integration
257
- - **Prettier**: Code formatting
258
- - **Husky**: Git hooks for pre-commit checks
259
- - **Commitlint**: Conventional commit message validation
260
-
261
- ## 🔌 Supported MCP Methods
262
-
263
- - `tools/list` - List all tools from all servers
264
- - `tools/call` - Call a tool from any server
265
- - `prompts/list` - List all prompts from all servers
266
- - `prompts/get` - Get a specific prompt
267
- - `resources/list` - List all resources from all servers
268
- - `resources/read` - Read a specific resource
269
-
270
- ## 📝 License
271
-
272
- ISC
273
-
274
- ## 🤝 Contributing
275
-
276
- Contributions are welcome! Please ensure your code passes linting and formatting checks before submitting.
277
-
278
- ---
279
-
280
- **Built with ❤️ using the Model Context Protocol SDK**
@@ -1,42 +0,0 @@
1
- module.exports = {
2
- extends: ['@commitlint/config-conventional'],
3
- rules: {
4
- // Type must be one of the following
5
- 'type-enum': [
6
- 2,
7
- 'always',
8
- [
9
- 'feat', // A new feature
10
- 'fix', // A bug fix
11
- 'docs', // Documentation only changes
12
- 'style', // Changes that do not affect the meaning of the code
13
- 'refactor', // A code change that neither fixes a bug nor adds a feature
14
- 'perf', // A code change that improves performance
15
- 'test', // Adding missing tests or correcting existing tests
16
- 'build', // Changes that affect the build system or external dependencies
17
- 'ci', // Changes to CI configuration files and scripts
18
- 'chore', // Other changes that don't modify src or test files
19
- 'revert', // Reverts a previous commit
20
- ],
21
- ],
22
- // Type case: must be lowercase
23
- 'type-case': [2, 'always', 'lower-case'],
24
- // Type cannot be empty
25
- 'type-empty': [2, 'never'],
26
- // Scope case: must be lowercase
27
- 'scope-case': [2, 'always', 'lower-case'],
28
- // Subject case: sentence-case (first letter lowercase, rest as written)
29
- 'subject-case': [2, 'never', ['upper-case', 'pascal-case', 'start-case']],
30
- // Subject cannot be empty
31
- 'subject-empty': [2, 'never'],
32
- // Subject must end with a period
33
- 'subject-full-stop': [0, 'never'],
34
- // Header must not exceed 72 characters (disabled, using max line length instead)
35
- 'header-max-length': [2, 'always', 100],
36
- // Body must start with blank line
37
- 'body-leading-blank': [2, 'always'],
38
- // Footer must start with blank line
39
- 'footer-leading-blank': [2, 'always'],
40
- },
41
- };
42
-