@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
@@ -1,13 +1,13 @@
1
1
  // Tabler Icons for Tab Navigation
2
2
  // Using @tabler/icons-react - install with: npm install @tabler/icons-react
3
3
  import {
4
- IconNetwork,
4
+ IconBrandStackoverflow,
5
+ IconChevronDown,
5
6
  IconFileText,
7
+ IconMenu2,
8
+ IconNetwork,
6
9
  IconSettings,
7
- IconBrandStackoverflow,
8
10
  IconShield,
9
- IconMenu2,
10
- IconChevronDown,
11
11
  } from '@tabler/icons-react';
12
12
 
13
13
  // Wrapper components to match existing API
@@ -1,4 +1,4 @@
1
- function TourOverlay({ elementRect, onClick }) {
1
+ function TourOverlay({ elementRect, _onClick }) {
2
2
  if (!elementRect) {
3
3
  return (
4
4
  <div
@@ -19,6 +19,7 @@ export default function TourTooltipButtons({
19
19
  }}
20
20
  >
21
21
  <button
22
+ type="button"
22
23
  onClick={(e) => {
23
24
  e.stopPropagation();
24
25
  onSkip();
@@ -52,6 +53,7 @@ export default function TourTooltipButtons({
52
53
  <div style={{ display: 'flex', gap: '8px', pointerEvents: 'auto' }}>
53
54
  {currentStep > 0 && (
54
55
  <button
56
+ type="button"
55
57
  onClick={(e) => {
56
58
  e.stopPropagation();
57
59
  onPrevious();
@@ -82,6 +84,7 @@ export default function TourTooltipButtons({
82
84
  </button>
83
85
  )}
84
86
  <button
87
+ type="button"
85
88
  onClick={(e) => {
86
89
  e.stopPropagation();
87
90
  onNext();
@@ -36,6 +36,7 @@ export default function TourTooltipHeader({ step, currentStep, totalSteps, isDra
36
36
  </p>
37
37
  </div>
38
38
  <button
39
+ type="button"
39
40
  onClick={(e) => {
40
41
  e.stopPropagation();
41
42
  onSkip();
@@ -8,7 +8,10 @@ export const CloseIcon = ({ size = 16, color = 'currentColor' }) => (
8
8
  strokeWidth="2"
9
9
  strokeLinecap="round"
10
10
  strokeLinejoin="round"
11
+ role="img"
12
+ aria-label="Close icon"
11
13
  >
14
+ <title>Close icon</title>
12
15
  <line x1="18" y1="6" x2="6" y2="18" />
13
16
  <line x1="6" y1="6" x2="18" y2="18" />
14
17
  </svg>
@@ -24,7 +27,10 @@ export const ChevronRight = ({ size = 16, color = 'currentColor' }) => (
24
27
  strokeWidth="2"
25
28
  strokeLinecap="round"
26
29
  strokeLinejoin="round"
30
+ role="img"
31
+ aria-label="Chevron right icon"
27
32
  >
33
+ <title>Chevron right icon</title>
28
34
  <polyline points="9 18 15 12 9 6" />
29
35
  </svg>
30
36
  );
@@ -39,7 +45,10 @@ export const ChevronLeft = ({ size = 16, color = 'currentColor' }) => (
39
45
  strokeWidth="2"
40
46
  strokeLinecap="round"
41
47
  strokeLinejoin="round"
48
+ role="img"
49
+ aria-label="Chevron left icon"
42
50
  >
51
+ <title>Chevron left icon</title>
43
52
  <polyline points="15 18 9 12 15 6" />
44
53
  </svg>
45
54
  );
@@ -1,13 +1,13 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
 
3
- export function useTooltipPosition(elementRect, step, currentStep) {
3
+ export function useTooltipPosition(elementRect, step, _currentStep) {
4
4
  const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
5
5
  const [isDragging, setIsDragging] = useState(false);
6
6
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
7
7
 
8
8
  useEffect(() => {
9
9
  setTooltipPosition({ x: 0, y: 0 });
10
- }, [currentStep]);
10
+ }, []);
11
11
 
12
12
  const handleMouseDown = (e, tooltipRef) => {
13
13
  e.preventDefault();
@@ -23,7 +23,9 @@ export function useTooltipPosition(elementRect, step, currentStep) {
23
23
  };
24
24
 
25
25
  useEffect(() => {
26
- if (!isDragging) return;
26
+ if (!isDragging) {
27
+ return;
28
+ }
27
29
 
28
30
  const handleMouseMove = (e) => {
29
31
  setTooltipPosition({
@@ -46,7 +48,9 @@ export function useTooltipPosition(elementRect, step, currentStep) {
46
48
  }, [isDragging, dragOffset]);
47
49
 
48
50
  const calculatePosition = () => {
49
- if (!elementRect) return { left: 0, top: 0, transform: 'none' };
51
+ if (!elementRect) {
52
+ return { left: 0, top: 0, transform: 'none' };
53
+ }
50
54
 
51
55
  const tooltipWidth = 350;
52
56
  const tooltipHeight = 200;
@@ -62,42 +66,65 @@ export function useTooltipPosition(elementRect, step, currentStep) {
62
66
  }
63
67
 
64
68
  const position = step.position || 'bottom';
65
- let left, top, transform;
66
-
67
- if (position === 'left') {
68
- left = elementRect.left - tooltipWidth - spacing;
69
- top = elementRect.top + elementRect.height / 2;
70
- transform = 'translateY(-50%)';
71
- if (left < 10) {
72
- left = elementRect.right + spacing;
73
- }
74
- } else if (position === 'right') {
75
- left = elementRect.right + spacing;
76
- top = elementRect.top + elementRect.height / 2;
77
- transform = 'translateY(-50%)';
78
- if (left + tooltipWidth > window.innerWidth - 10) {
79
- left = elementRect.left - tooltipWidth - spacing;
69
+
70
+ const calculatePosition = (position, elementRect, tooltipWidth, tooltipHeight, spacing) => {
71
+ if (position === 'left') {
72
+ const baseLeft = elementRect.left - tooltipWidth - spacing;
73
+ const left = baseLeft < 10 ? elementRect.right + spacing : baseLeft;
74
+ return {
75
+ left,
76
+ top: elementRect.top + elementRect.height / 2,
77
+ transform: 'translateY(-50%)',
78
+ };
80
79
  }
81
- } else if (position === 'top') {
82
- left = elementRect.left + elementRect.width / 2;
83
- top = elementRect.top - tooltipHeight - spacing;
84
- transform = 'translate(-50%, 0)';
85
- if (top < 10) {
86
- top = elementRect.bottom + spacing;
80
+
81
+ if (position === 'right') {
82
+ const baseLeft = elementRect.right + spacing;
83
+ const left =
84
+ baseLeft + tooltipWidth > window.innerWidth - 10
85
+ ? elementRect.left - tooltipWidth - spacing
86
+ : baseLeft;
87
+ return {
88
+ left,
89
+ top: elementRect.top + elementRect.height / 2,
90
+ transform: 'translateY(-50%)',
91
+ };
87
92
  }
88
- } else {
89
- left = elementRect.left + elementRect.width / 2;
90
- top = elementRect.bottom + spacing;
91
- transform = 'translate(-50%, 0)';
92
- if (top + tooltipHeight > window.innerHeight - 10) {
93
- top = elementRect.top - tooltipHeight - spacing;
93
+
94
+ if (position === 'top') {
95
+ const baseTop = elementRect.top - tooltipHeight - spacing;
96
+ const top = baseTop < 10 ? elementRect.bottom + spacing : baseTop;
97
+ return {
98
+ left: elementRect.left + elementRect.width / 2,
99
+ top,
100
+ transform: 'translate(-50%, 0)',
101
+ };
94
102
  }
95
- }
96
103
 
97
- left = Math.max(10, Math.min(left, window.innerWidth - tooltipWidth - 10));
98
- top = Math.max(10, Math.min(top, window.innerHeight - tooltipHeight - 10));
104
+ // bottom (default)
105
+ const baseTop = elementRect.bottom + spacing;
106
+ const top =
107
+ baseTop + tooltipHeight > window.innerHeight - 10
108
+ ? elementRect.top - tooltipHeight - spacing
109
+ : baseTop;
110
+ return {
111
+ left: elementRect.left + elementRect.width / 2,
112
+ top,
113
+ transform: 'translate(-50%, 0)',
114
+ };
115
+ };
99
116
 
100
- return { left, top, transform };
117
+ const rawPosition = calculatePosition(
118
+ position,
119
+ elementRect,
120
+ tooltipWidth,
121
+ tooltipHeight,
122
+ spacing
123
+ );
124
+ const left = Math.max(10, Math.min(rawPosition.left, window.innerWidth - tooltipWidth - 10));
125
+ const top = Math.max(10, Math.min(rawPosition.top, window.innerHeight - tooltipHeight - 10));
126
+
127
+ return { left, top, transform: rawPosition.transform };
101
128
  };
102
129
 
103
130
  return {
@@ -1,7 +1,7 @@
1
1
  import { useRef } from 'react';
2
2
  import { colors, fonts } from '../theme';
3
- import TourTooltipHeader from './TourTooltip/TourTooltipHeader';
4
3
  import TourTooltipButtons from './TourTooltip/TourTooltipButtons';
4
+ import TourTooltipHeader from './TourTooltip/TourTooltipHeader';
5
5
  import { useTooltipPosition } from './TourTooltip/useTooltipPosition';
6
6
 
7
7
  function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPrevious, onSkip }) {
@@ -17,9 +17,17 @@ function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPre
17
17
  }
18
18
 
19
19
  return (
20
- <div
20
+ <button
21
+ type="button"
21
22
  ref={tooltipRef}
22
23
  onMouseDown={(e) => handleMouseDown(e, tooltipRef)}
24
+ onKeyDown={(e) => {
25
+ if (e.key === 'Enter' || e.key === ' ') {
26
+ e.preventDefault();
27
+ handleMouseDown(e, tooltipRef);
28
+ }
29
+ }}
30
+ aria-label="Draggable tooltip"
23
31
  style={{
24
32
  position: 'fixed',
25
33
  left: `${position.left}px`,
@@ -76,7 +84,7 @@ function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPre
76
84
  onPrevious={onPrevious}
77
85
  onSkip={onSkip}
78
86
  />
79
- </div>
87
+ </button>
80
88
  );
81
89
  }
82
90
 
@@ -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