@mcp-shark/mcp-shark 1.4.2 → 1.5.2

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 (204) 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/shared/logger.js +90 -0
  23. package/ui/dist/assets/index-Cc-IUa83.css +1 -0
  24. package/ui/dist/assets/index-srLDlk97.js +35 -0
  25. package/ui/dist/index.html +17 -0
  26. package/ui/dist/og-image.png +0 -0
  27. package/ui/server/routes/backups/deleteBackup.js +54 -0
  28. package/ui/server/routes/backups/index.js +15 -0
  29. package/ui/server/routes/backups/listBackups.js +75 -0
  30. package/ui/server/routes/backups/restoreBackup.js +83 -0
  31. package/ui/server/routes/backups/viewBackup.js +47 -0
  32. package/ui/server/routes/composite/index.js +46 -0
  33. package/ui/server/routes/composite/servers.js +18 -0
  34. package/ui/server/routes/composite/setup.js +129 -0
  35. package/ui/server/routes/composite/status.js +7 -0
  36. package/ui/server/routes/composite/stop.js +39 -0
  37. package/ui/server/routes/composite/utils.js +45 -0
  38. package/ui/server/routes/config.js +34 -30
  39. package/ui/server/routes/conversations.js +3 -3
  40. package/ui/server/routes/help.js +2 -2
  41. package/ui/server/routes/logs.js +5 -5
  42. package/ui/server/routes/playground.js +45 -47
  43. package/ui/server/routes/requests.js +112 -108
  44. package/ui/server/routes/sessions.js +4 -4
  45. package/ui/server/routes/settings.js +199 -0
  46. package/ui/server/routes/smartscan/discover.js +7 -6
  47. package/ui/server/routes/smartscan/scans/clearCache.js +3 -2
  48. package/ui/server/routes/smartscan/scans/createBatchScans.js +4 -3
  49. package/ui/server/routes/smartscan/scans/createScan.js +2 -1
  50. package/ui/server/routes/smartscan/scans/getCachedResults.js +2 -1
  51. package/ui/server/routes/smartscan/scans/getScan.js +2 -1
  52. package/ui/server/routes/smartscan/scans/listScans.js +5 -4
  53. package/ui/server/routes/smartscan/scans.js +3 -3
  54. package/ui/server/routes/smartscan/token.js +4 -3
  55. package/ui/server/routes/smartscan/transport.js +1 -1
  56. package/ui/server/routes/smartscan.js +1 -1
  57. package/ui/server/routes/statistics.js +13 -10
  58. package/ui/server/utils/config-update.js +7 -6
  59. package/ui/server/utils/config.js +4 -4
  60. package/ui/server/utils/logger.js +2 -0
  61. package/ui/server/utils/paths.js +210 -2
  62. package/ui/server/utils/port.js +2 -2
  63. package/ui/server/utils/process.js +0 -67
  64. package/ui/server/utils/scan-cache/all-results.js +76 -59
  65. package/ui/server/utils/scan-cache/directory.js +1 -1
  66. package/ui/server/utils/scan-cache/file-operations.js +19 -16
  67. package/ui/server/utils/scan-cache/server-operations.js +14 -9
  68. package/ui/server/utils/serialization.js +9 -3
  69. package/ui/server/utils/smartscan-token.js +4 -3
  70. package/ui/server.js +86 -41
  71. package/ui/src/App.jsx +5 -5
  72. package/ui/src/CompositeLogs.jsx +20 -20
  73. package/ui/src/CompositeSetup.jsx +9 -9
  74. package/ui/src/HelpGuide/HelpGuideFooter.jsx +1 -0
  75. package/ui/src/HelpGuide/HelpGuideHeader.jsx +2 -1
  76. package/ui/src/HelpGuide.jsx +17 -4
  77. package/ui/src/IntroTour.jsx +19 -5
  78. package/ui/src/LogDetail.jsx +1 -0
  79. package/ui/src/LogTable.jsx +24 -6
  80. package/ui/src/PacketDetail.jsx +21 -16
  81. package/ui/src/PacketFilters.jsx +29 -14
  82. package/ui/src/PacketList.jsx +4 -5
  83. package/ui/src/SmartScan.jsx +5 -5
  84. package/ui/src/TabNavigation.jsx +5 -5
  85. package/ui/src/components/App/HelpButton.jsx +4 -0
  86. package/ui/src/components/App/TrafficTab.jsx +4 -4
  87. package/ui/src/components/App/useAppState.js +118 -24
  88. package/ui/src/components/BackupList.jsx +6 -2
  89. package/ui/src/components/CollapsibleSection.jsx +16 -2
  90. package/ui/src/components/ConfigViewerModal.jsx +17 -3
  91. package/ui/src/components/ConfirmationModal.jsx +20 -3
  92. package/ui/src/components/DetailsTab/BodySection.jsx +3 -1
  93. package/ui/src/components/DetailsTab/CollapsibleRequestResponse.jsx +14 -3
  94. package/ui/src/components/DetailsTab/InfoSection.jsx +4 -2
  95. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +7 -5
  96. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +7 -5
  97. package/ui/src/components/DetectedPathsList.jsx +5 -2
  98. package/ui/src/components/FileInput.jsx +3 -1
  99. package/ui/src/components/GroupHeader.jsx +14 -0
  100. package/ui/src/components/GroupedByMcpView.jsx +3 -4
  101. package/ui/src/components/GroupedByServerView.jsx +1 -1
  102. package/ui/src/components/GroupedBySessionView.jsx +1 -1
  103. package/ui/src/components/HexTab.jsx +17 -4
  104. package/ui/src/components/LogsToolbar.jsx +3 -1
  105. package/ui/src/components/McpPlayground/LoadingModal.jsx +7 -3
  106. package/ui/src/components/McpPlayground/PromptsSection/PromptCallPanel.jsx +5 -0
  107. package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +6 -2
  108. package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +4 -4
  109. package/ui/src/components/McpPlayground/PromptsSection.jsx +2 -1
  110. package/ui/src/components/McpPlayground/ResourcesSection/ResourceCallPanel.jsx +3 -0
  111. package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +6 -2
  112. package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +4 -4
  113. package/ui/src/components/McpPlayground/ResourcesSection.jsx +2 -1
  114. package/ui/src/components/McpPlayground/ToolsSection/ToolCallPanel.jsx +5 -0
  115. package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +6 -2
  116. package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +4 -4
  117. package/ui/src/components/McpPlayground/ToolsSection.jsx +2 -1
  118. package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +9 -9
  119. package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +10 -5
  120. package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +43 -23
  121. package/ui/src/components/McpPlayground/useMcpPlayground.js +72 -44
  122. package/ui/src/components/McpPlayground.jsx +5 -2
  123. package/ui/src/components/PacketDetailHeader.jsx +8 -3
  124. package/ui/src/components/PacketFilters/ExportControls.jsx +2 -1
  125. package/ui/src/components/PacketFilters/FilterInput.jsx +1 -1
  126. package/ui/src/components/RawTab.jsx +15 -2
  127. package/ui/src/components/RequestRow/OrphanedResponseRow.jsx +10 -2
  128. package/ui/src/components/RequestRow/RequestRowMain.jsx +12 -3
  129. package/ui/src/components/RequestRow/ResponseRow.jsx +11 -3
  130. package/ui/src/components/RequestRow.jsx +17 -9
  131. package/ui/src/components/ServerControl.jsx +3 -1
  132. package/ui/src/components/ServiceSelector.jsx +2 -0
  133. package/ui/src/components/SmartScan/AnalysisResult.jsx +2 -2
  134. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultItem.jsx +2 -1
  135. package/ui/src/components/SmartScan/BatchResultsDisplay/BatchResultsHeader.jsx +1 -1
  136. package/ui/src/components/SmartScan/BatchResultsDisplay.jsx +9 -3
  137. package/ui/src/components/SmartScan/EmptyState.jsx +3 -0
  138. package/ui/src/components/SmartScan/ErrorDisplay.jsx +4 -2
  139. package/ui/src/components/SmartScan/ExpandableSection.jsx +4 -0
  140. package/ui/src/components/SmartScan/FindingsTable.jsx +46 -42
  141. package/ui/src/components/SmartScan/ListViewContent.jsx +2 -2
  142. package/ui/src/components/SmartScan/NotablePatternsSection.jsx +13 -8
  143. package/ui/src/components/SmartScan/OverallSummarySection.jsx +36 -29
  144. package/ui/src/components/SmartScan/RecommendationsSection.jsx +10 -8
  145. package/ui/src/components/SmartScan/ScanDetailHeader.jsx +2 -1
  146. package/ui/src/components/SmartScan/ScanDetailView.jsx +4 -4
  147. package/ui/src/components/SmartScan/ScanListView/ScanListHeader.jsx +2 -1
  148. package/ui/src/components/SmartScan/ScanListView/ScanListItem.jsx +15 -1
  149. package/ui/src/components/SmartScan/ScanOverviewSection.jsx +3 -1
  150. package/ui/src/components/SmartScan/ScanResultsDisplay.jsx +1 -1
  151. package/ui/src/components/SmartScan/ScanViewContent.jsx +2 -2
  152. package/ui/src/components/SmartScan/ScanningProgress.jsx +4 -2
  153. package/ui/src/components/SmartScan/ServerInfoSection.jsx +3 -1
  154. package/ui/src/components/SmartScan/ServerSelectionRow.jsx +4 -2
  155. package/ui/src/components/SmartScan/SingleResultDisplay.jsx +5 -3
  156. package/ui/src/components/SmartScan/SmartScanControls.jsx +11 -7
  157. package/ui/src/components/SmartScan/SmartScanHeader.jsx +1 -1
  158. package/ui/src/components/SmartScan/ViewModeTabs.jsx +2 -0
  159. package/ui/src/components/SmartScan/hooks/useCacheManagement.js +1 -2
  160. package/ui/src/components/SmartScan/hooks/useMcpDiscovery.js +22 -26
  161. package/ui/src/components/SmartScan/hooks/useScanList.js +10 -9
  162. package/ui/src/components/SmartScan/hooks/useScanOperations.js +23 -14
  163. package/ui/src/components/SmartScan/hooks/useServerStatus.js +2 -2
  164. package/ui/src/components/SmartScan/hooks/useTokenManagement.js +2 -2
  165. package/ui/src/components/SmartScan/scanDataUtils.js +22 -17
  166. package/ui/src/components/SmartScan/useSmartScan.js +4 -4
  167. package/ui/src/components/SmartScan/utils.js +3 -1
  168. package/ui/src/components/SmartScanIcons.jsx +6 -3
  169. package/ui/src/components/TabNavigation/DesktopTabs.jsx +8 -3
  170. package/ui/src/components/TabNavigation/MobileDropdown.jsx +3 -1
  171. package/ui/src/components/TabNavigation.jsx +8 -3
  172. package/ui/src/components/TabNavigationIcons.jsx +4 -4
  173. package/ui/src/components/TourOverlay.jsx +1 -1
  174. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +3 -0
  175. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +1 -0
  176. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +9 -0
  177. package/ui/src/components/TourTooltip/useTooltipPosition.js +63 -36
  178. package/ui/src/components/TourTooltip.jsx +11 -3
  179. package/ui/src/components/ViewModeTabs.jsx +3 -1
  180. package/ui/src/config/tourSteps.jsx +0 -2
  181. package/ui/src/hooks/useAnimation.js +15 -12
  182. package/ui/src/hooks/useConfigManagement.js +8 -8
  183. package/ui/src/hooks/useServiceExtraction.js +1 -1
  184. package/ui/src/index.css +3 -8
  185. package/ui/src/theme.js +3 -3
  186. package/ui/src/utils/hexUtils.js +11 -5
  187. package/ui/src/utils/mcpGroupingUtils.js +18 -10
  188. package/ui/src/utils/requestPairing.js +89 -0
  189. package/ui/src/utils/requestUtils.js +32 -101
  190. package/ui/vite.config.js +1 -1
  191. package/mcp-server/.editorconfig +0 -15
  192. package/mcp-server/.prettierignore +0 -11
  193. package/mcp-server/.prettierrc +0 -12
  194. package/mcp-server/README.md +0 -280
  195. package/mcp-server/commitlint.config.cjs +0 -42
  196. package/mcp-server/eslint.config.js +0 -131
  197. package/mcp-server/package-lock.json +0 -4784
  198. package/mcp-server/package.json +0 -30
  199. package/ui/README.md +0 -212
  200. package/ui/package-lock.json +0 -3574
  201. package/ui/package.json +0 -12
  202. package/ui/paths.js +0 -282
  203. package/ui/server/routes/backups.js +0 -251
  204. package/ui/server/routes/composite.js +0 -260
@@ -10,10 +10,14 @@ function ConfirmationModal({
10
10
  cancelText = 'Cancel',
11
11
  danger = false,
12
12
  }) {
13
- if (!isOpen) return null;
13
+ if (!isOpen) {
14
+ return null;
15
+ }
14
16
 
15
17
  return (
16
- <div
18
+ <dialog
19
+ open
20
+ aria-modal="true"
17
21
  style={{
18
22
  position: 'fixed',
19
23
  top: 0,
@@ -25,10 +29,20 @@ function ConfirmationModal({
25
29
  alignItems: 'center',
26
30
  justifyContent: 'center',
27
31
  zIndex: 1000,
32
+ border: 'none',
33
+ margin: 0,
34
+ width: '100%',
35
+ height: '100%',
28
36
  }}
29
37
  onClick={onClose}
38
+ onKeyDown={(e) => {
39
+ if (e.key === 'Escape') {
40
+ onClose();
41
+ }
42
+ }}
30
43
  >
31
44
  <div
45
+ role="document"
32
46
  style={{
33
47
  background: colors.bgCard,
34
48
  borderRadius: '12px',
@@ -39,6 +53,7 @@ function ConfirmationModal({
39
53
  fontFamily: fonts.body,
40
54
  }}
41
55
  onClick={(e) => e.stopPropagation()}
56
+ onKeyDown={(e) => e.stopPropagation()}
42
57
  >
43
58
  <h3
44
59
  style={{
@@ -68,6 +83,7 @@ function ConfirmationModal({
68
83
  }}
69
84
  >
70
85
  <button
86
+ type="button"
71
87
  onClick={onClose}
72
88
  style={{
73
89
  padding: '10px 20px',
@@ -91,6 +107,7 @@ function ConfirmationModal({
91
107
  {cancelText}
92
108
  </button>
93
109
  <button
110
+ type="button"
94
111
  onClick={() => {
95
112
  onConfirm();
96
113
  onClose();
@@ -122,7 +139,7 @@ function ConfirmationModal({
122
139
  </button>
123
140
  </div>
124
141
  </div>
125
- </div>
142
+ </dialog>
126
143
  );
127
144
  }
128
145
 
@@ -2,7 +2,9 @@ import { colors, fonts } from '../../theme';
2
2
  import CollapsibleSection from '../CollapsibleSection';
3
3
 
4
4
  export default function BodySection({ body, title, titleColor }) {
5
- if (!body) return null;
5
+ if (!body) {
6
+ return null;
7
+ }
6
8
 
7
9
  return (
8
10
  <CollapsibleSection title={title || 'Body'} titleColor={titleColor}>
@@ -1,6 +1,6 @@
1
+ import { IconChevronDown } from '@tabler/icons-react';
1
2
  import { useState } from 'react';
2
3
  import { colors, fonts } from '../../theme';
3
- import { IconChevronDown } from '@tabler/icons-react';
4
4
 
5
5
  export default function CollapsibleRequestResponse({
6
6
  title,
@@ -20,8 +20,16 @@ export default function CollapsibleRequestResponse({
20
20
  marginBottom: '20px',
21
21
  }}
22
22
  >
23
- <div
23
+ <button
24
+ type="button"
24
25
  onClick={() => setIsExpanded(!isExpanded)}
26
+ onKeyDown={(e) => {
27
+ if (e.key === 'Enter' || e.key === ' ') {
28
+ e.preventDefault();
29
+ setIsExpanded(!isExpanded);
30
+ }
31
+ }}
32
+ aria-label={`Toggle ${title} section`}
25
33
  style={{
26
34
  padding: '16px 20px',
27
35
  background: isExpanded ? colors.bgCard : colors.bgSecondary,
@@ -32,6 +40,9 @@ export default function CollapsibleRequestResponse({
32
40
  alignItems: 'center',
33
41
  justifyContent: 'space-between',
34
42
  transition: 'background-color 0.15s ease',
43
+ width: '100%',
44
+ border: 'none',
45
+ textAlign: 'left',
35
46
  }}
36
47
  onMouseEnter={(e) => {
37
48
  e.currentTarget.style.background = colors.bgHover;
@@ -63,7 +74,7 @@ export default function CollapsibleRequestResponse({
63
74
  />
64
75
  {title}
65
76
  </div>
66
- </div>
77
+ </button>
67
78
  {isExpanded && <div style={{ padding: '20px' }}>{children}</div>}
68
79
  </div>
69
80
  );
@@ -1,9 +1,11 @@
1
1
  import { colors, fonts } from '../../theme';
2
- import CollapsibleSection from '../CollapsibleSection';
3
2
  import { getInfo } from '../../utils/requestUtils';
3
+ import CollapsibleSection from '../CollapsibleSection';
4
4
 
5
5
  export default function InfoSection({ data, titleColor }) {
6
- if (!data) return null;
6
+ if (!data) {
7
+ return null;
8
+ }
7
9
 
8
10
  const info = getInfo(data);
9
11
 
@@ -1,14 +1,16 @@
1
1
  import { colors } from '../../theme';
2
+ import CollapsibleSection from '../CollapsibleSection';
3
+ import BodySection from './BodySection';
2
4
  import CollapsibleRequestResponse from './CollapsibleRequestResponse';
3
- import NetworkInfoSection from './NetworkInfoSection';
4
- import ProtocolInfoSection from './ProtocolInfoSection';
5
5
  import HeadersSection from './HeadersSection';
6
- import BodySection from './BodySection';
7
6
  import InfoSection from './InfoSection';
8
- import CollapsibleSection from '../CollapsibleSection';
7
+ import NetworkInfoSection from './NetworkInfoSection';
8
+ import ProtocolInfoSection from './ProtocolInfoSection';
9
9
 
10
10
  export default function RequestDetailsSection({ request, requestHeaders, requestBody }) {
11
- if (!request) return null;
11
+ if (!request) {
12
+ return null;
13
+ }
12
14
 
13
15
  return (
14
16
  <CollapsibleRequestResponse
@@ -1,14 +1,16 @@
1
1
  import { colors, fonts } from '../../theme';
2
+ import CollapsibleSection from '../CollapsibleSection';
3
+ import BodySection from './BodySection';
2
4
  import CollapsibleRequestResponse from './CollapsibleRequestResponse';
3
- import NetworkInfoSection from './NetworkInfoSection';
4
- import ProtocolInfoSection from './ProtocolInfoSection';
5
5
  import HeadersSection from './HeadersSection';
6
- import BodySection from './BodySection';
7
6
  import InfoSection from './InfoSection';
8
- import CollapsibleSection from '../CollapsibleSection';
7
+ import NetworkInfoSection from './NetworkInfoSection';
8
+ import ProtocolInfoSection from './ProtocolInfoSection';
9
9
 
10
10
  export default function ResponseDetailsSection({ response, responseHeaders, responseBody }) {
11
- if (!response) return null;
11
+ if (!response) {
12
+ return null;
13
+ }
12
14
 
13
15
  return (
14
16
  <CollapsibleRequestResponse
@@ -1,5 +1,5 @@
1
+ import { IconClock, IconCode, IconEye, IconRefresh } from '@tabler/icons-react';
1
2
  import { colors, fonts } from '../theme';
2
- import { IconRefresh, IconEye, IconCode, IconClock } from '@tabler/icons-react';
3
3
 
4
4
  function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onView }) {
5
5
  if (detectedPaths.length === 0) {
@@ -27,6 +27,7 @@ function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onVie
27
27
  Detected Configuration Files:
28
28
  </div>
29
29
  <button
30
+ type="button"
30
31
  onClick={onDetect}
31
32
  disabled={detecting}
32
33
  title="Refresh detection"
@@ -67,7 +68,8 @@ function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onVie
67
68
  >
68
69
  {detectedPaths.map((item, idx) => (
69
70
  <button
70
- key={idx}
71
+ key={`${item.editor}-${item.path}-${idx}`}
72
+ type="button"
71
73
  data-tour={idx === 0 ? 'first-detected-editor' : undefined}
72
74
  onClick={() => onSelect(item.path)}
73
75
  onDoubleClick={() => {
@@ -137,6 +139,7 @@ function DetectedPathsList({ detectedPaths, detecting, onDetect, onSelect, onVie
137
139
  Found
138
140
  </span>
139
141
  <button
142
+ type="button"
140
143
  onClick={(e) => {
141
144
  e.stopPropagation();
142
145
  onView(item.path);
@@ -1,5 +1,5 @@
1
- import { colors, fonts } from '../theme';
2
1
  import { IconFileUpload } from '@tabler/icons-react';
2
+ import { colors, fonts } from '../theme';
3
3
 
4
4
  function FileInput({
5
5
  filePath,
@@ -70,6 +70,7 @@ function FileInput({
70
70
  {fileContent && !filePath && (
71
71
  <div>
72
72
  <label
73
+ htmlFor="update-path-input"
73
74
  style={{
74
75
  display: 'block',
75
76
  fontSize: '12px',
@@ -80,6 +81,7 @@ function FileInput({
80
81
  Optional: File Path to Update
81
82
  </label>
82
83
  <input
84
+ id="update-path-input"
83
85
  type="text"
84
86
  placeholder="Enter file path to update (e.g., ~/.cursor/mcp.json)"
85
87
  value={updatePath}
@@ -11,7 +11,10 @@ const ChevronDown = ({ size = 12, color = 'currentColor' }) => (
11
11
  strokeLinecap="round"
12
12
  strokeLinejoin="round"
13
13
  style={{ display: 'inline-block', verticalAlign: 'middle' }}
14
+ role="img"
15
+ aria-label="Chevron down icon"
14
16
  >
17
+ <title>Chevron down icon</title>
15
18
  <polyline points="6 9 12 15 18 9" />
16
19
  </svg>
17
20
  );
@@ -27,7 +30,10 @@ const ChevronRight = ({ size = 12, color = 'currentColor' }) => (
27
30
  strokeLinecap="round"
28
31
  strokeLinejoin="round"
29
32
  style={{ display: 'inline-block', verticalAlign: 'middle' }}
33
+ role="img"
34
+ aria-label="Chevron right icon"
30
35
  >
36
+ <title>Chevron right icon</title>
31
37
  <polyline points="9 18 15 12 9 6" />
32
38
  </svg>
33
39
  );
@@ -36,6 +42,14 @@ export default function GroupHeader({ children, onClick, isExpanded, indent = 0
36
42
  return (
37
43
  <tr
38
44
  onClick={onClick}
45
+ onKeyDown={(e) => {
46
+ if (e.key === 'Enter' || e.key === ' ') {
47
+ e.preventDefault();
48
+ onClick();
49
+ }
50
+ }}
51
+ tabIndex={0}
52
+ aria-label={`Toggle group ${children}`}
39
53
  style={{
40
54
  cursor: 'pointer',
41
55
  background: colors.bgSecondary,
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
- import { colors, fonts } from '../theme';
3
- import RequestRow from './RequestRow';
2
+ import { colors } from '../theme';
3
+ import { getCategoryIconComponent } from '../utils/mcpGroupingUtils';
4
4
  import GroupHeader from './GroupHeader';
5
- import { getCategoryIconComponent, getMethodDescription } from '../utils/mcpGroupingUtils';
6
- import { getJsonRpcMethod } from '../utils/requestUtils';
5
+ import RequestRow from './RequestRow';
7
6
 
8
7
  function GroupedByMcpView({
9
8
  groupedData,
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { colors, fonts } from '../theme';
3
- import RequestRow from './RequestRow';
4
3
  import GroupHeader from './GroupHeader';
4
+ import RequestRow from './RequestRow';
5
5
 
6
6
  export default function GroupedByServerView({
7
7
  groupedData,
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { colors, fonts } from '../theme';
3
- import RequestRow from './RequestRow';
4
3
  import GroupHeader from './GroupHeader';
4
+ import RequestRow from './RequestRow';
5
5
 
6
6
  export default function GroupedBySessionView({
7
7
  groupedData,
@@ -17,7 +17,10 @@ const ChevronDown = ({ size = 14, color = 'currentColor', rotated = false }) =>
17
17
  transform: rotated ? 'rotate(-90deg)' : 'rotate(0deg)',
18
18
  transition: 'transform 0.2s ease',
19
19
  }}
20
+ role="img"
21
+ aria-label="Chevron down icon"
20
22
  >
23
+ <title>Chevron down icon</title>
21
24
  <polyline points="6 9 12 15 18 9" />
22
25
  </svg>
23
26
  );
@@ -35,8 +38,15 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
35
38
  marginBottom: '20px',
36
39
  }}
37
40
  >
38
- <div
41
+ <button
42
+ type="button"
39
43
  onClick={() => setIsExpanded(!isExpanded)}
44
+ onKeyDown={(e) => {
45
+ if (e.key === 'Enter' || e.key === ' ') {
46
+ e.preventDefault();
47
+ setIsExpanded(!isExpanded);
48
+ }
49
+ }}
40
50
  style={{
41
51
  padding: '16px 20px',
42
52
  background: isExpanded ? colors.bgCard : colors.bgSecondary,
@@ -47,6 +57,9 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
47
57
  alignItems: 'center',
48
58
  justifyContent: 'space-between',
49
59
  transition: 'background-color 0.15s ease',
60
+ width: '100%',
61
+ border: 'none',
62
+ textAlign: 'left',
50
63
  }}
51
64
  onMouseEnter={(e) => {
52
65
  e.currentTarget.style.background = colors.bgHover;
@@ -71,7 +84,7 @@ function CollapsibleRequestResponse({ title, titleColor, children, defaultExpand
71
84
  <ChevronDown size={14} color={titleColor} rotated={!isExpanded} />
72
85
  {title}
73
86
  </div>
74
- </div>
87
+ </button>
75
88
  {isExpanded && <div style={{ padding: '20px' }}>{children}</div>}
76
89
  </div>
77
90
  );
@@ -100,7 +113,7 @@ function HexTab({ requestHexLines, responseHexLines, hasRequest, hasResponse })
100
113
  {requestHexLines.length > 0 ? (
101
114
  requestHexLines.map((line, i) => (
102
115
  <div
103
- key={i}
116
+ key={`request-${line.offset}-${i}`}
104
117
  style={{
105
118
  display: 'flex',
106
119
  gap: '16px',
@@ -150,7 +163,7 @@ function HexTab({ requestHexLines, responseHexLines, hasRequest, hasResponse })
150
163
  {responseHexLines.length > 0 ? (
151
164
  responseHexLines.map((line, i) => (
152
165
  <div
153
- key={i}
166
+ key={`response-${line.offset}-${i}`}
154
167
  style={{
155
168
  display: 'flex',
156
169
  gap: '16px',
@@ -1,5 +1,5 @@
1
+ import { IconDownload, IconSearch, IconTrash } from '@tabler/icons-react';
1
2
  import { colors, fonts } from '../theme';
2
- import { IconTrash, IconDownload, IconSearch } from '@tabler/icons-react';
3
3
 
4
4
  export default function LogsToolbar({
5
5
  filter,
@@ -117,6 +117,7 @@ export default function LogsToolbar({
117
117
  </label>
118
118
 
119
119
  <button
120
+ type="button"
120
121
  onClick={onClearLogs}
121
122
  style={{
122
123
  padding: '8px 14px',
@@ -148,6 +149,7 @@ export default function LogsToolbar({
148
149
  </button>
149
150
 
150
151
  <button
152
+ type="button"
151
153
  onClick={onExportLogs}
152
154
  style={{
153
155
  padding: '8px 14px',
@@ -1,6 +1,6 @@
1
+ import anime from 'animejs';
1
2
  import { useEffect, useRef } from 'react';
2
3
  import { colors, fonts } from '../../theme';
3
- import anime from 'animejs';
4
4
 
5
5
  export default function LoadingModal({ show }) {
6
6
  const loadingModalRef = useRef(null);
@@ -42,7 +42,9 @@ export default function LoadingModal({ show }) {
42
42
  }
43
43
  }, [show]);
44
44
 
45
- if (!show) return null;
45
+ if (!show) {
46
+ return null;
47
+ }
46
48
 
47
49
  return (
48
50
  <div
@@ -96,7 +98,9 @@ export default function LoadingModal({ show }) {
96
98
  <div
97
99
  key={i}
98
100
  ref={(el) => {
99
- if (el) dotsRef.current[i] = el;
101
+ if (el) {
102
+ dotsRef.current[i] = el;
103
+ }
100
104
  }}
101
105
  style={{
102
106
  width: '12px',
@@ -43,6 +43,7 @@ export default function PromptCallPanel({
43
43
  >
44
44
  <div>
45
45
  <label
46
+ htmlFor="prompt-args-textarea"
46
47
  style={{
47
48
  display: 'block',
48
49
  fontSize: '12px',
@@ -54,6 +55,7 @@ export default function PromptCallPanel({
54
55
  Arguments (JSON)
55
56
  </label>
56
57
  <textarea
58
+ id="prompt-args-textarea"
57
59
  value={promptArgs}
58
60
  onChange={(e) => onPromptArgsChange(e.target.value)}
59
61
  style={{
@@ -71,6 +73,7 @@ export default function PromptCallPanel({
71
73
  />
72
74
  </div>
73
75
  <button
76
+ type="button"
74
77
  onClick={onGetPrompt}
75
78
  disabled={loading}
76
79
  style={{
@@ -91,6 +94,7 @@ export default function PromptCallPanel({
91
94
  {promptResult && (
92
95
  <div>
93
96
  <label
97
+ htmlFor="prompt-result-pre"
94
98
  style={{
95
99
  display: 'block',
96
100
  fontSize: '12px',
@@ -102,6 +106,7 @@ export default function PromptCallPanel({
102
106
  Result
103
107
  </label>
104
108
  <pre
109
+ id="prompt-result-pre"
105
110
  style={{
106
111
  padding: '12px',
107
112
  background: colors.bgSecondary,
@@ -2,8 +2,10 @@ import { colors } from '../../../theme';
2
2
 
3
3
  export default function PromptItem({ prompt, isSelected, onClick }) {
4
4
  return (
5
- <div
5
+ <button
6
+ type="button"
6
7
  onClick={onClick}
8
+ aria-label={`Select prompt ${prompt.name}`}
7
9
  style={{
8
10
  padding: '16px 20px',
9
11
  margin: '4px 8px',
@@ -14,6 +16,8 @@ export default function PromptItem({ prompt, isSelected, onClick }) {
14
16
  boxShadow: isSelected ? `0 2px 4px ${colors.shadowSm}` : 'none',
15
17
  transition: 'all 0.2s ease',
16
18
  position: 'relative',
19
+ width: '100%',
20
+ textAlign: 'left',
17
21
  }}
18
22
  onMouseEnter={(e) => {
19
23
  if (!isSelected) {
@@ -68,6 +72,6 @@ export default function PromptItem({ prompt, isSelected, onClick }) {
68
72
  )}
69
73
  </div>
70
74
  </div>
71
- </div>
75
+ </button>
72
76
  );
73
77
  }
@@ -1,7 +1,7 @@
1
1
  import { colors } from '../../../theme';
2
- import LoadingState from '../common/LoadingState';
3
- import ErrorState from '../common/ErrorState';
4
2
  import EmptyState from '../common/EmptyState';
3
+ import ErrorState from '../common/ErrorState';
4
+ import LoadingState from '../common/LoadingState';
5
5
  import PromptItem from './PromptItem';
6
6
 
7
7
  export default function PromptsList({
@@ -26,7 +26,7 @@ export default function PromptsList({
26
26
  <LoadingState message="Waiting for MCP server to start..." />
27
27
  ) : promptsLoading || !promptsLoaded ? (
28
28
  <LoadingState message="Loading prompts..." />
29
- ) : error && error.includes('prompts:') ? (
29
+ ) : error?.includes('prompts:') ? (
30
30
  <ErrorState message={`Error loading prompts: ${error.replace('prompts: ', '')}`} />
31
31
  ) : prompts.length === 0 ? (
32
32
  <EmptyState message="No prompts available." />
@@ -34,7 +34,7 @@ export default function PromptsList({
34
34
  <div style={{ padding: '8px 0' }}>
35
35
  {prompts.map((prompt, idx) => (
36
36
  <PromptItem
37
- key={idx}
37
+ key={prompt.name || `prompt-${idx}`}
38
38
  prompt={prompt}
39
39
  isSelected={selectedPrompt?.name === prompt.name}
40
40
  onClick={() => onSelectPrompt(prompt)}
@@ -1,6 +1,6 @@
1
1
  import { colors, fonts } from '../../theme';
2
- import PromptsList from './PromptsSection/PromptsList';
3
2
  import PromptCallPanel from './PromptsSection/PromptCallPanel';
3
+ import PromptsList from './PromptsSection/PromptsList';
4
4
 
5
5
  export default function PromptsSection({
6
6
  prompts,
@@ -32,6 +32,7 @@ export default function PromptsSection({
32
32
  <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
33
33
  <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
34
34
  <button
35
+ type="button"
35
36
  onClick={onRefresh}
36
37
  disabled={loading || promptsLoading}
37
38
  style={{
@@ -35,6 +35,7 @@ export default function ResourceCallPanel({ resource, resourceResult, onReadReso
35
35
  }}
36
36
  >
37
37
  <button
38
+ type="button"
38
39
  onClick={onReadResource}
39
40
  disabled={loading}
40
41
  style={{
@@ -55,6 +56,7 @@ export default function ResourceCallPanel({ resource, resourceResult, onReadReso
55
56
  {resourceResult && (
56
57
  <div>
57
58
  <label
59
+ htmlFor="resource-result-pre"
58
60
  style={{
59
61
  display: 'block',
60
62
  fontSize: '12px',
@@ -66,6 +68,7 @@ export default function ResourceCallPanel({ resource, resourceResult, onReadReso
66
68
  Result
67
69
  </label>
68
70
  <pre
71
+ id="resource-result-pre"
69
72
  style={{
70
73
  padding: '12px',
71
74
  background: colors.bgSecondary,
@@ -2,8 +2,10 @@ import { colors } from '../../../theme';
2
2
 
3
3
  export default function ResourceItem({ resource, isSelected, onClick }) {
4
4
  return (
5
- <div
5
+ <button
6
+ type="button"
6
7
  onClick={onClick}
8
+ aria-label={`Select resource ${resource.uri}`}
7
9
  style={{
8
10
  padding: '16px 20px',
9
11
  margin: '4px 8px',
@@ -14,6 +16,8 @@ export default function ResourceItem({ resource, isSelected, onClick }) {
14
16
  boxShadow: isSelected ? `0 2px 4px ${colors.shadowSm}` : 'none',
15
17
  transition: 'all 0.2s ease',
16
18
  position: 'relative',
19
+ width: '100%',
20
+ textAlign: 'left',
17
21
  }}
18
22
  onMouseEnter={(e) => {
19
23
  if (!isSelected) {
@@ -82,6 +86,6 @@ export default function ResourceItem({ resource, isSelected, onClick }) {
82
86
  )}
83
87
  </div>
84
88
  </div>
85
- </div>
89
+ </button>
86
90
  );
87
91
  }
@@ -1,7 +1,7 @@
1
1
  import { colors } from '../../../theme';
2
- import LoadingState from '../common/LoadingState';
3
- import ErrorState from '../common/ErrorState';
4
2
  import EmptyState from '../common/EmptyState';
3
+ import ErrorState from '../common/ErrorState';
4
+ import LoadingState from '../common/LoadingState';
5
5
  import ResourceItem from './ResourceItem';
6
6
 
7
7
  export default function ResourcesList({
@@ -26,7 +26,7 @@ export default function ResourcesList({
26
26
  <LoadingState message="Waiting for MCP server to start..." />
27
27
  ) : resourcesLoading || !resourcesLoaded ? (
28
28
  <LoadingState message="Loading resources..." />
29
- ) : error && error.includes('resources:') ? (
29
+ ) : error?.includes('resources:') ? (
30
30
  <ErrorState message={`Error loading resources: ${error.replace('resources: ', '')}`} />
31
31
  ) : resources.length === 0 ? (
32
32
  <EmptyState message="No resources available." />
@@ -34,7 +34,7 @@ export default function ResourcesList({
34
34
  <div style={{ padding: '8px 0' }}>
35
35
  {resources.map((resource, idx) => (
36
36
  <ResourceItem
37
- key={idx}
37
+ key={resource.uri || `resource-${idx}`}
38
38
  resource={resource}
39
39
  isSelected={selectedResource?.uri === resource.uri}
40
40
  onClick={() => onSelectResource(resource)}
@@ -1,6 +1,6 @@
1
1
  import { colors, fonts } from '../../theme';
2
- import ResourcesList from './ResourcesSection/ResourcesList';
3
2
  import ResourceCallPanel from './ResourcesSection/ResourceCallPanel';
3
+ import ResourcesList from './ResourcesSection/ResourcesList';
4
4
 
5
5
  export default function ResourcesSection({
6
6
  resources,
@@ -19,6 +19,7 @@ export default function ResourcesSection({
19
19
  <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
20
20
  <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
21
21
  <button
22
+ type="button"
22
23
  onClick={onRefresh}
23
24
  disabled={loading || resourcesLoading}
24
25
  style={{