@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,5 +1,5 @@
1
- import { colors, fonts } from '../theme';
2
1
  import { IconList, IconNetwork } from '@tabler/icons-react';
2
+ import { colors, fonts } from '../theme';
3
3
 
4
4
  function ViewModeTabs({ viewMode, onViewModeChange }) {
5
5
  return (
@@ -15,6 +15,7 @@ function ViewModeTabs({ viewMode, onViewModeChange }) {
15
15
  }}
16
16
  >
17
17
  <button
18
+ type="button"
18
19
  onClick={() => onViewModeChange('general')}
19
20
  style={{
20
21
  padding: '10px 18px',
@@ -49,6 +50,7 @@ function ViewModeTabs({ viewMode, onViewModeChange }) {
49
50
  General List
50
51
  </button>
51
52
  <button
53
+ type="button"
52
54
  onClick={() => onViewModeChange('groupedByMcp')}
53
55
  style={{
54
56
  padding: '10px 18px',
@@ -1,5 +1,3 @@
1
- import React from 'react';
2
-
3
1
  export const tourSteps = [
4
2
  {
5
3
  target: '[data-tour="tabs"]',
@@ -1,5 +1,5 @@
1
- import { useEffect, useRef } from 'react';
2
1
  import anime from 'animejs';
2
+ import { useEffect, useRef } from 'react';
3
3
 
4
4
  /**
5
5
  * React hook for animating elements on mount/unmount
@@ -18,7 +18,7 @@ export const useAnimation = (animationFn, deps = []) => {
18
18
  anime.remove(elementRef.current);
19
19
  }
20
20
  };
21
- }, deps);
21
+ }, [animationFn, ...deps]);
22
22
 
23
23
  return elementRef;
24
24
  };
@@ -28,6 +28,7 @@ export const useAnimation = (animationFn, deps = []) => {
28
28
  */
29
29
  export const useMountAnimation = (options = {}) => {
30
30
  const elementRef = useRef(null);
31
+ const { duration, easing } = options;
31
32
 
32
33
  useEffect(() => {
33
34
  if (elementRef.current) {
@@ -35,12 +36,12 @@ export const useMountAnimation = (options = {}) => {
35
36
  targets: elementRef.current,
36
37
  opacity: [0, 1],
37
38
  translateY: [20, 0],
38
- duration: options.duration || 400,
39
- easing: options.easing || 'easeOutExpo',
39
+ duration: duration || 400,
40
+ easing: easing || 'easeOutExpo',
40
41
  ...options,
41
42
  });
42
43
  }
43
- }, []);
44
+ }, [duration, easing, options]);
44
45
 
45
46
  return elementRef;
46
47
  };
@@ -50,6 +51,7 @@ export const useMountAnimation = (options = {}) => {
50
51
  */
51
52
  export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) => {
52
53
  const elementRef = useRef(null);
54
+ const { duration, easing } = options;
53
55
 
54
56
  useEffect(() => {
55
57
  if (shouldUnmount && elementRef.current) {
@@ -57,13 +59,13 @@ export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) =>
57
59
  targets: elementRef.current,
58
60
  opacity: [1, 0],
59
61
  translateY: [0, -20],
60
- duration: options.duration || 300,
61
- easing: options.easing || 'easeInExpo',
62
+ duration: duration || 300,
63
+ easing: easing || 'easeInExpo',
62
64
  complete: onComplete,
63
65
  ...options,
64
66
  });
65
67
  }
66
- }, [shouldUnmount, onComplete]);
68
+ }, [shouldUnmount, onComplete, duration, easing, options]);
67
69
 
68
70
  return elementRef;
69
71
  };
@@ -73,6 +75,7 @@ export const useUnmountAnimation = (shouldUnmount, onComplete, options = {}) =>
73
75
  */
74
76
  export const useStaggerAnimation = (items, options = {}) => {
75
77
  const containerRef = useRef(null);
78
+ const { duration, delay, easing } = options;
76
79
 
77
80
  useEffect(() => {
78
81
  if (containerRef.current && items.length > 0) {
@@ -80,13 +83,13 @@ export const useStaggerAnimation = (items, options = {}) => {
80
83
  targets: containerRef.current.children,
81
84
  opacity: [0, 1],
82
85
  translateY: [20, 0],
83
- duration: options.duration || 400,
84
- delay: anime.stagger(options.delay || 50),
85
- easing: options.easing || 'easeOutExpo',
86
+ duration: duration || 400,
87
+ delay: anime.stagger(delay || 50),
88
+ easing: easing || 'easeOutExpo',
86
89
  ...options,
87
90
  });
88
91
  }
89
- }, [items.length]);
92
+ }, [items.length, duration, delay, easing, options]);
90
93
 
91
94
  return containerRef;
92
95
  };
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
 
3
3
  export function useConfigManagement() {
4
4
  const [detectedPaths, setDetectedPaths] = useState([]);
@@ -12,7 +12,7 @@ export function useConfigManagement() {
12
12
  const [backupContent, setBackupContent] = useState(null);
13
13
  const [loadingBackup, setLoadingBackup] = useState(false);
14
14
 
15
- const detectConfigPaths = async () => {
15
+ const detectConfigPaths = useCallback(async () => {
16
16
  setDetecting(true);
17
17
  try {
18
18
  const res = await fetch('/api/config/detect');
@@ -23,9 +23,9 @@ export function useConfigManagement() {
23
23
  } finally {
24
24
  setDetecting(false);
25
25
  }
26
- };
26
+ }, []);
27
27
 
28
- const loadBackups = async () => {
28
+ const loadBackups = useCallback(async () => {
29
29
  setLoadingBackups(true);
30
30
  try {
31
31
  const res = await fetch('/api/config/backups');
@@ -36,7 +36,7 @@ export function useConfigManagement() {
36
36
  } finally {
37
37
  setLoadingBackups(false);
38
38
  }
39
- };
39
+ }, []);
40
40
 
41
41
  const handleViewConfig = async (filePath) => {
42
42
  setLoadingConfig(true);
@@ -49,7 +49,7 @@ export function useConfigManagement() {
49
49
  } else {
50
50
  setConfigContent(null);
51
51
  }
52
- } catch (err) {
52
+ } catch (_err) {
53
53
  setConfigContent(null);
54
54
  } finally {
55
55
  setLoadingConfig(false);
@@ -69,7 +69,7 @@ export function useConfigManagement() {
69
69
  } else {
70
70
  setBackupContent(null);
71
71
  }
72
- } catch (err) {
72
+ } catch (_err) {
73
73
  setBackupContent(null);
74
74
  } finally {
75
75
  setLoadingBackup(false);
@@ -98,7 +98,7 @@ export function useConfigManagement() {
98
98
  useEffect(() => {
99
99
  detectConfigPaths();
100
100
  loadBackups();
101
- }, []);
101
+ }, [detectConfigPaths, loadBackups]);
102
102
 
103
103
  return {
104
104
  detectedPaths,
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useCallback } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
 
3
3
  export function useServiceExtraction(fileContent, filePath) {
4
4
  const [services, setServices] = useState([]);
package/ui/src/index.css CHANGED
@@ -1,4 +1,4 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;500&display=swap');
1
+ @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;500&display=swap");
2
2
 
3
3
  * {
4
4
  margin: 0;
@@ -7,12 +7,7 @@
7
7
  }
8
8
 
9
9
  body {
10
- font-family:
11
- 'Roboto',
12
- -apple-system,
13
- BlinkMacSystemFont,
14
- 'Segoe UI',
15
- sans-serif;
10
+ font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
16
11
  background: #ffffff;
17
12
  color: #202124;
18
13
  overflow: hidden;
@@ -23,7 +18,7 @@ body {
23
18
  code,
24
19
  pre,
25
20
  .monospace {
26
- font-family: 'Roboto Mono', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
21
+ font-family: "Roboto Mono", "JetBrains Mono", "Fira Code", "Consolas", monospace;
27
22
  }
28
23
 
29
24
  #root {
package/ui/src/theme.js CHANGED
@@ -53,9 +53,9 @@ export function withOpacity(color, opacity) {
53
53
  // Remove # if present
54
54
  const hex = color.replace('#', '');
55
55
  // Convert to RGB
56
- const r = parseInt(hex.substring(0, 2), 16);
57
- const g = parseInt(hex.substring(2, 4), 16);
58
- const b = parseInt(hex.substring(4, 6), 16);
56
+ const r = Number.parseInt(hex.substring(0, 2), 16);
57
+ const g = Number.parseInt(hex.substring(2, 4), 16);
58
+ const b = Number.parseInt(hex.substring(4, 6), 16);
59
59
  return `rgba(${r}, ${g}, ${b}, ${opacity})`;
60
60
  }
61
61
 
@@ -1,16 +1,22 @@
1
1
  export function generateHexDump(text) {
2
- if (!text) return [];
2
+ if (!text) {
3
+ return [];
4
+ }
3
5
  const bytes = new TextEncoder().encode(text);
4
6
  const lines = [];
5
- for (let i = 0; i < bytes.length; i += 16) {
6
- const chunk = bytes.slice(i, i + 16);
7
+ const chunkSize = 16;
8
+ const totalChunks = Math.ceil(bytes.length / chunkSize);
9
+
10
+ for (const chunkIndex of Array.from({ length: totalChunks }, (_, idx) => idx)) {
11
+ const startOffset = chunkIndex * chunkSize;
12
+ const chunk = bytes.slice(startOffset, startOffset + chunkSize);
7
13
  const hex = Array.from(chunk)
8
14
  .map((b) => b.toString(16).padStart(2, '0'))
9
15
  .join(' ');
10
16
  const ascii = Array.from(chunk)
11
17
  .map((b) => (b >= 32 && b < 127 ? String.fromCharCode(b) : '.'))
12
18
  .join('');
13
- const offset = i.toString(16).padStart(8, '0');
19
+ const offset = startOffset.toString(16).padStart(8, '0');
14
20
  lines.push({ offset, hex, ascii });
15
21
  }
16
22
  return lines;
@@ -20,5 +26,5 @@ export function createFullRequestText(headers, bodyRaw) {
20
26
  const headersText = Object.entries(headers)
21
27
  .map(([key, value]) => `${key}: ${value}`)
22
28
  .join('\r\n');
23
- return headersText + (bodyRaw ? '\r\n\r\n' + bodyRaw : '');
29
+ return headersText + (bodyRaw ? `\r\n\r\n${bodyRaw}` : '');
24
30
  }
@@ -1,13 +1,13 @@
1
- import { pairRequestsWithResponses } from './requestUtils.js';
2
1
  import {
3
- IconRefresh,
4
- IconTool,
2
+ IconBell,
5
3
  IconDatabase,
6
4
  IconMessage,
7
- IconBell,
8
- IconUser,
9
5
  IconPackage,
6
+ IconRefresh,
7
+ IconTool,
8
+ IconUser,
10
9
  } from '@tabler/icons-react';
10
+ import { pairRequestsWithResponses } from './requestUtils.js';
11
11
 
12
12
  /**
13
13
  * MCP Method Categories based on the protocol specification
@@ -27,7 +27,9 @@ export const MCP_METHOD_CATEGORIES = {
27
27
  * Categorize an MCP method into its protocol category
28
28
  */
29
29
  export function categorizeMcpMethod(method) {
30
- if (!method) return MCP_METHOD_CATEGORIES.OTHER;
30
+ if (!method) {
31
+ return MCP_METHOD_CATEGORIES.OTHER;
32
+ }
31
33
 
32
34
  // Lifecycle methods
33
35
  if (method === 'initialize' || method === 'notifications/initialized') {
@@ -112,7 +114,9 @@ export function groupByMcpSessionAndCategory(requests) {
112
114
 
113
115
  pairs.forEach((pair) => {
114
116
  const request = pair.request || pair.response;
115
- if (!request) return;
117
+ if (!request) {
118
+ return;
119
+ }
116
120
 
117
121
  const sessionId = request.session_id || '__NO_SESSION__';
118
122
  const method = getJsonRpcMethod(request);
@@ -140,7 +144,7 @@ export function groupByMcpSessionAndCategory(requests) {
140
144
  });
141
145
 
142
146
  return Array.from(sessionGroups.entries())
143
- .map(([sessionId, session]) => ({
147
+ .map(([_sessionId, session]) => ({
144
148
  sessionId: session.sessionId,
145
149
  firstTimestamp: session.firstTimestamp,
146
150
  categories: Array.from(session.categories.entries())
@@ -182,7 +186,9 @@ export function groupByMcpCategory(requests) {
182
186
 
183
187
  pairs.forEach((pair) => {
184
188
  const request = pair.request || pair.response;
185
- if (!request) return;
189
+ if (!request) {
190
+ return;
191
+ }
186
192
 
187
193
  const method = getJsonRpcMethod(request);
188
194
  const category = categorizeMcpMethod(method || '');
@@ -226,7 +232,9 @@ export function groupByMcpCategory(requests) {
226
232
  * Based on the protocol specification
227
233
  */
228
234
  export function getMethodDescription(method) {
229
- if (!method) return 'Unknown operation';
235
+ if (!method) {
236
+ return 'Unknown operation';
237
+ }
230
238
 
231
239
  const descriptions = {
232
240
  // Lifecycle
@@ -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
+ }
@@ -1,13 +1,14 @@
1
+ const LLM_SERVER = 'LLM Server';
1
2
  export function extractServerName(request) {
2
3
  if (request.body_json) {
3
4
  try {
4
5
  const body =
5
6
  typeof request.body_json === 'string' ? JSON.parse(request.body_json) : request.body_json;
6
- if (body.params && body.params.name) {
7
+ if (body.params?.name) {
7
8
  const fullName = body.params.name;
8
9
  return fullName.includes('.') ? fullName.split('.')[0] : fullName;
9
10
  }
10
- } catch (e) {
11
+ } catch (_e) {
11
12
  // Failed to parse JSON, try body_raw
12
13
  }
13
14
  }
@@ -16,11 +17,11 @@ export function extractServerName(request) {
16
17
  try {
17
18
  const body =
18
19
  typeof request.body_raw === 'string' ? JSON.parse(request.body_raw) : request.body_raw;
19
- if (body.params && body.params.name) {
20
+ if (body.params?.name) {
20
21
  const fullName = body.params.name;
21
22
  return fullName.includes('.') ? fullName.split('.')[0] : fullName;
22
23
  }
23
- } catch (e) {
24
+ } catch (_e) {
24
25
  // Failed to parse
25
26
  }
26
27
  }
@@ -33,13 +34,17 @@ export function extractServerName(request) {
33
34
  }
34
35
 
35
36
  export function formatRelativeTime(timestampISO, firstTime) {
36
- if (!firstTime) return '0.000000';
37
+ if (!firstTime) {
38
+ return '0.000000';
39
+ }
37
40
  const diff = new Date(timestampISO) - new Date(firstTime);
38
41
  return (diff / 1000).toFixed(6);
39
42
  }
40
43
 
41
44
  export function formatDateTime(timestampISO) {
42
- if (!timestampISO) return '-';
45
+ if (!timestampISO) {
46
+ return '-';
47
+ }
43
48
  try {
44
49
  const date = new Date(timestampISO);
45
50
  return date.toLocaleString('en-US', {
@@ -51,7 +56,7 @@ export function formatDateTime(timestampISO) {
51
56
  second: '2-digit',
52
57
  hour12: false,
53
58
  });
54
- } catch (e) {
59
+ } catch (_e) {
55
60
  return timestampISO;
56
61
  }
57
62
  }
@@ -59,13 +64,13 @@ export function formatDateTime(timestampISO) {
59
64
  export function getSourceDest(request) {
60
65
  if (request.direction === 'request') {
61
66
  return {
62
- source: request.remote_address || 'Client',
63
- dest: request.host || 'Server',
67
+ source: LLM_SERVER,
68
+ dest: request.remote_address || 'Unknown MCP Client',
64
69
  };
65
70
  }
66
71
  return {
67
- source: request.host || 'Server',
68
- dest: request.remote_address || 'Client',
72
+ source: request.remote_address || 'Unknown MCP Server',
73
+ dest: LLM_SERVER,
69
74
  };
70
75
  }
71
76
 
@@ -78,7 +83,7 @@ export function getEndpoint(request) {
78
83
  if (body && typeof body === 'object' && body.method) {
79
84
  return body.method;
80
85
  }
81
- } catch (e) {
86
+ } catch (_e) {
82
87
  // Failed to parse JSON, try body_raw
83
88
  }
84
89
  }
@@ -89,7 +94,7 @@ export function getEndpoint(request) {
89
94
  if (body && typeof body === 'object' && body.method) {
90
95
  return body.method;
91
96
  }
92
- } catch (e) {
97
+ } catch (_e) {
93
98
  // Failed to parse
94
99
  }
95
100
  }
@@ -100,7 +105,7 @@ export function getEndpoint(request) {
100
105
  try {
101
106
  const url = new URL(request.url);
102
107
  return url.pathname + (url.search || '');
103
- } catch (e) {
108
+ } catch (_e) {
104
109
  const url = request.url;
105
110
  const match = url.match(/^https?:\/\/[^\/]+(\/.*)$/);
106
111
  return match ? match[1] : url;
@@ -126,15 +131,19 @@ export function getInfo(request) {
126
131
  // If we have both HTTP method and endpoint, show both
127
132
  if (httpMethod && url) {
128
133
  return `${httpMethod} ${endpoint}`;
129
- } else if (httpMethod) {
134
+ }
135
+ if (httpMethod) {
130
136
  return `${httpMethod} ${endpoint}`;
131
137
  }
132
138
  return endpoint;
133
- } else if (httpMethod && url) {
139
+ }
140
+ if (httpMethod && url) {
134
141
  return `${httpMethod} ${url}`;
135
- } else if (httpMethod) {
142
+ }
143
+ if (httpMethod) {
136
144
  return httpMethod;
137
- } else if (url) {
145
+ }
146
+ if (url) {
138
147
  return url;
139
148
  }
140
149
  return 'Request';
@@ -148,9 +157,11 @@ export function getInfo(request) {
148
157
 
149
158
  if (status && rpcMethod) {
150
159
  return `${status} ${rpcMethod}`;
151
- } else if (status) {
160
+ }
161
+ if (status) {
152
162
  return `Status: ${status}`;
153
- } else if (rpcMethod) {
163
+ }
164
+ if (rpcMethod) {
154
165
  return rpcMethod;
155
166
  }
156
167
  return 'Response';
@@ -184,7 +195,7 @@ export function getJsonRpcMethod(req) {
184
195
  if (body && typeof body === 'object' && body.method) {
185
196
  return body.method;
186
197
  }
187
- } catch (e) {
198
+ } catch (_e) {
188
199
  // Failed to parse
189
200
  }
190
201
  }
@@ -194,7 +205,7 @@ export function getJsonRpcMethod(req) {
194
205
  if (body && typeof body === 'object' && body.method) {
195
206
  return body.method;
196
207
  }
197
- } catch (e) {
208
+ } catch (_e) {
198
209
  // Failed to parse
199
210
  }
200
211
  }
@@ -203,10 +214,10 @@ export function getJsonRpcMethod(req) {
203
214
  // For responses, try to extract from body if available
204
215
  if (req.direction === 'response' && req.body_json) {
205
216
  try {
206
- 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;
207
218
  // Responses don't have a method field, but we can check if it's an error response
208
219
  // For now, we'll rely on jsonrpc_method field
209
- } catch (e) {
220
+ } catch (_e) {
210
221
  // Failed to parse
211
222
  }
212
223
  }
@@ -214,84 +225,5 @@ export function getJsonRpcMethod(req) {
214
225
  return null;
215
226
  }
216
227
 
217
- export function pairRequestsWithResponses(requests) {
218
- const pairs = [];
219
- const processed = new Set();
220
-
221
- // Helper function to check if two requests match (same session, JSON-RPC method, and optionally jsonrpc_id)
222
- const matches = (req, resp) => {
223
- // Session ID must match (or both null for initiation)
224
- const sessionMatch = req.session_id === resp.session_id;
225
- if (!sessionMatch) return false;
226
-
227
- // JSON-RPC Method must match
228
- const reqMethod = getJsonRpcMethod(req);
229
- const respMethod = getJsonRpcMethod(resp);
230
-
231
- // Both must have a method, and they must match
232
- if (!reqMethod || !respMethod) {
233
- // If either doesn't have a method, we can't match by method
234
- // Fall back to JSON-RPC ID matching only
235
- if (req.jsonrpc_id && resp.jsonrpc_id) {
236
- return req.jsonrpc_id === resp.jsonrpc_id;
237
- }
238
- // If no method and no ID, we can't match reliably
239
- return false;
240
- }
241
-
242
- const methodMatch = reqMethod === respMethod;
243
- if (!methodMatch) return false;
244
-
245
- // If JSON-RPC ID exists, it must match (for more precise pairing)
246
- if (req.jsonrpc_id && resp.jsonrpc_id) {
247
- return req.jsonrpc_id === resp.jsonrpc_id;
248
- }
249
-
250
- // If no JSON-RPC ID, match by session and method only
251
- return true;
252
- };
253
-
254
- requests.forEach((request) => {
255
- if (processed.has(request.frame_number)) return;
256
-
257
- if (request.direction === 'request') {
258
- // Find matching response - must match session, endpoint, and optionally jsonrpc_id
259
- const response = requests.find(
260
- (r) =>
261
- r.direction === 'response' &&
262
- !processed.has(r.frame_number) &&
263
- matches(request, r) &&
264
- r.frame_number > request.frame_number
265
- );
266
-
267
- if (response) {
268
- pairs.push({ request, response, frame_number: request.frame_number });
269
- processed.add(request.frame_number);
270
- processed.add(response.frame_number);
271
- } else {
272
- // Request without response
273
- pairs.push({ request, response: null, frame_number: request.frame_number });
274
- processed.add(request.frame_number);
275
- }
276
- } else if (request.direction === 'response') {
277
- // Find matching request - must match session, endpoint, and optionally jsonrpc_id
278
- const matchingRequest = requests.find(
279
- (r) =>
280
- r.direction === 'request' &&
281
- !processed.has(r.frame_number) &&
282
- matches(r, request) &&
283
- r.frame_number < request.frame_number
284
- );
285
-
286
- if (!matchingRequest) {
287
- // Response without request (orphaned)
288
- pairs.push({ request: null, response: request, frame_number: request.frame_number });
289
- processed.add(request.frame_number);
290
- }
291
- // If matching request exists, it will be handled when we iterate over it
292
- }
293
- });
294
-
295
- // Sort by frame number (descending - latest first)
296
- return pairs.sort((a, b) => b.frame_number - a.frame_number);
297
- }
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()],