@mcp-shark/mcp-shark 1.5.4 → 1.5.5

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 (188) hide show
  1. package/README.md +32 -96
  2. package/bin/mcp-shark.js +1 -1
  3. package/core/configs/codex.js +68 -0
  4. package/core/configs/environment.js +51 -0
  5. package/{lib/common → core}/configs/index.js +16 -1
  6. package/core/constants/Defaults.js +15 -0
  7. package/core/constants/HttpStatus.js +14 -0
  8. package/core/constants/Server.js +20 -0
  9. package/core/constants/StatusCodes.js +25 -0
  10. package/core/constants/index.js +7 -0
  11. package/core/container/DependencyContainer.js +179 -0
  12. package/core/db/init.js +33 -0
  13. package/core/index.js +10 -0
  14. package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
  15. package/core/libraries/LoggerLibrary.js +91 -0
  16. package/core/libraries/SerializationLibrary.js +32 -0
  17. package/core/libraries/bootstrap-logger.js +19 -0
  18. package/core/libraries/errors/ApplicationError.js +97 -0
  19. package/core/libraries/index.js +17 -0
  20. package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
  21. package/core/mcp-server/index.js +192 -0
  22. package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
  23. package/core/mcp-server/server/external/config.js +75 -0
  24. package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
  25. package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
  26. package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
  27. package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
  28. package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
  29. package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
  30. package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
  31. package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
  32. package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
  33. package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
  34. package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
  35. package/core/mcp-server/server/internal/run.js +53 -0
  36. package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
  37. package/core/models/ConversationFilters.js +31 -0
  38. package/core/models/ExportFormat.js +8 -0
  39. package/core/models/RequestFilters.js +43 -0
  40. package/core/models/SessionFilters.js +23 -0
  41. package/core/models/index.js +8 -0
  42. package/core/repositories/AuditRepository.js +233 -0
  43. package/core/repositories/ConversationRepository.js +182 -0
  44. package/core/repositories/PacketRepository.js +237 -0
  45. package/core/repositories/SchemaRepository.js +107 -0
  46. package/core/repositories/SessionRepository.js +59 -0
  47. package/core/repositories/StatisticsRepository.js +54 -0
  48. package/core/repositories/index.js +10 -0
  49. package/core/services/AuditService.js +144 -0
  50. package/core/services/BackupService.js +222 -0
  51. package/core/services/ConfigDetectionService.js +89 -0
  52. package/core/services/ConfigFileService.js +210 -0
  53. package/core/services/ConfigPatchingService.js +137 -0
  54. package/core/services/ConfigService.js +250 -0
  55. package/core/services/ConfigTransformService.js +178 -0
  56. package/core/services/ConversationService.js +19 -0
  57. package/core/services/ExportService.js +117 -0
  58. package/core/services/LogService.js +64 -0
  59. package/core/services/McpClientService.js +235 -0
  60. package/core/services/McpDiscoveryService.js +107 -0
  61. package/core/services/RequestService.js +56 -0
  62. package/core/services/ScanCacheService.js +242 -0
  63. package/core/services/ScanService.js +167 -0
  64. package/core/services/ServerManagementService.js +206 -0
  65. package/core/services/SessionService.js +34 -0
  66. package/core/services/SettingsService.js +163 -0
  67. package/core/services/StatisticsService.js +64 -0
  68. package/core/services/TokenService.js +94 -0
  69. package/core/services/index.js +25 -0
  70. package/core/services/parsers/ConfigParserFactory.js +113 -0
  71. package/core/services/parsers/JsonConfigParser.js +66 -0
  72. package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
  73. package/core/services/parsers/TomlConfigParser.js +87 -0
  74. package/core/services/parsers/index.js +4 -0
  75. package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
  76. package/core/utils/validation.js +77 -0
  77. package/package.json +14 -11
  78. package/ui/dist/assets/index-CArYxKxS.js +35 -0
  79. package/ui/dist/index.html +1 -1
  80. package/ui/server/controllers/BackupController.js +129 -0
  81. package/ui/server/controllers/ConfigController.js +92 -0
  82. package/ui/server/controllers/ConversationController.js +41 -0
  83. package/ui/server/controllers/LogController.js +44 -0
  84. package/ui/server/controllers/McpClientController.js +60 -0
  85. package/ui/server/controllers/McpDiscoveryController.js +44 -0
  86. package/ui/server/controllers/RequestController.js +129 -0
  87. package/ui/server/controllers/ScanController.js +122 -0
  88. package/ui/server/controllers/ServerManagementController.js +134 -0
  89. package/ui/server/controllers/SessionController.js +57 -0
  90. package/ui/server/controllers/SettingsController.js +24 -0
  91. package/ui/server/controllers/StatisticsController.js +54 -0
  92. package/ui/server/controllers/TokenController.js +58 -0
  93. package/ui/server/controllers/index.js +17 -0
  94. package/ui/server/routes/backups/index.js +15 -9
  95. package/ui/server/routes/composite/index.js +62 -32
  96. package/ui/server/routes/composite/servers.js +20 -15
  97. package/ui/server/routes/config.js +13 -172
  98. package/ui/server/routes/conversations.js +9 -19
  99. package/ui/server/routes/help.js +4 -3
  100. package/ui/server/routes/logs.js +14 -26
  101. package/ui/server/routes/playground.js +11 -174
  102. package/ui/server/routes/requests.js +12 -232
  103. package/ui/server/routes/sessions.js +10 -21
  104. package/ui/server/routes/settings.js +10 -192
  105. package/ui/server/routes/smartscan.js +26 -15
  106. package/ui/server/routes/statistics.js +8 -79
  107. package/ui/server/setup.js +162 -0
  108. package/ui/server/swagger/paths/backups.js +151 -0
  109. package/ui/server/swagger/paths/components.js +76 -0
  110. package/ui/server/swagger/paths/config.js +117 -0
  111. package/ui/server/swagger/paths/conversations.js +29 -0
  112. package/ui/server/swagger/paths/help.js +82 -0
  113. package/ui/server/swagger/paths/logs.js +87 -0
  114. package/ui/server/swagger/paths/playground.js +49 -0
  115. package/ui/server/swagger/paths/requests.js +178 -0
  116. package/ui/server/swagger/paths/serverManagement.js +169 -0
  117. package/ui/server/swagger/paths/sessions.js +61 -0
  118. package/ui/server/swagger/paths/settings.js +31 -0
  119. package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
  120. package/ui/server/swagger/paths/smartScan/index.js +13 -0
  121. package/ui/server/swagger/paths/smartScan/scans.js +151 -0
  122. package/ui/server/swagger/paths/smartScan/token.js +71 -0
  123. package/ui/server/swagger/paths/statistics.js +40 -0
  124. package/ui/server/swagger/paths.js +38 -0
  125. package/ui/server/swagger/swagger.js +37 -0
  126. package/ui/server/utils/cleanup.js +99 -0
  127. package/ui/server/utils/config.js +18 -96
  128. package/ui/server/utils/errorHandler.js +43 -0
  129. package/ui/server/utils/logger.js +2 -2
  130. package/ui/server/utils/paths.js +27 -30
  131. package/ui/server/utils/port.js +21 -21
  132. package/ui/server/utils/process.js +18 -10
  133. package/ui/server/utils/processState.js +17 -0
  134. package/ui/server/utils/signals.js +34 -0
  135. package/ui/server/websocket/broadcast.js +33 -0
  136. package/ui/server/websocket/handler.js +52 -0
  137. package/ui/server.js +51 -230
  138. package/ui/src/App.jsx +2 -0
  139. package/ui/src/CompositeSetup.jsx +23 -9
  140. package/ui/src/PacketFilters.jsx +17 -3
  141. package/ui/src/components/AlertModal.jsx +116 -0
  142. package/ui/src/components/App/ApiDocsButton.jsx +57 -0
  143. package/ui/src/components/App/useAppState.js +43 -1
  144. package/ui/src/components/BackupList.jsx +27 -3
  145. package/ui/src/utils/requestPairing.js +35 -36
  146. package/ui/src/utils/requestUtils.js +1 -0
  147. package/lib/common/db/init.js +0 -132
  148. package/lib/common/db/logger.js +0 -349
  149. package/lib/common/db/query.js +0 -403
  150. package/lib/common/logger.js +0 -90
  151. package/mcp-server/index.js +0 -138
  152. package/mcp-server/lib/server/external/config.js +0 -57
  153. package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
  154. package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
  155. package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
  156. package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
  157. package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
  158. package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
  159. package/mcp-server/lib/server/internal/run.js +0 -37
  160. package/mcp-server/mcp-shark.js +0 -22
  161. package/ui/dist/assets/index-CFHeMNwd.js +0 -35
  162. package/ui/server/routes/backups/deleteBackup.js +0 -54
  163. package/ui/server/routes/backups/listBackups.js +0 -75
  164. package/ui/server/routes/backups/restoreBackup.js +0 -83
  165. package/ui/server/routes/backups/viewBackup.js +0 -47
  166. package/ui/server/routes/composite/setup.js +0 -129
  167. package/ui/server/routes/composite/status.js +0 -7
  168. package/ui/server/routes/composite/stop.js +0 -39
  169. package/ui/server/routes/composite/utils.js +0 -45
  170. package/ui/server/routes/smartscan/discover.js +0 -118
  171. package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
  172. package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
  173. package/ui/server/routes/smartscan/scans/createScan.js +0 -43
  174. package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
  175. package/ui/server/routes/smartscan/scans/getScan.js +0 -42
  176. package/ui/server/routes/smartscan/scans/listScans.js +0 -25
  177. package/ui/server/routes/smartscan/scans.js +0 -13
  178. package/ui/server/routes/smartscan/token.js +0 -57
  179. package/ui/server/utils/config-update.js +0 -240
  180. package/ui/server/utils/scan-cache/all-results.js +0 -197
  181. package/ui/server/utils/scan-cache/file-operations.js +0 -107
  182. package/ui/server/utils/scan-cache/hash.js +0 -47
  183. package/ui/server/utils/scan-cache/server-operations.js +0 -85
  184. package/ui/server/utils/scan-cache.js +0 -12
  185. package/ui/server/utils/smartscan-token.js +0 -43
  186. /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
  187. /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
  188. /package/{mcp-server/lib → core/mcp-server}/server/internal/session.js +0 -0
@@ -0,0 +1,116 @@
1
+ import { colors, fonts } from '../theme';
2
+
3
+ /**
4
+ * Alert modal for displaying error or info messages
5
+ * Replaces browser alert() function with a proper modal
6
+ */
7
+ function AlertModal({ isOpen, onClose, title, message, type = 'error' }) {
8
+ if (!isOpen) {
9
+ return null;
10
+ }
11
+
12
+ const isError = type === 'error';
13
+
14
+ return (
15
+ <dialog
16
+ open
17
+ aria-modal="true"
18
+ style={{
19
+ position: 'fixed',
20
+ top: 0,
21
+ left: 0,
22
+ right: 0,
23
+ bottom: 0,
24
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
25
+ display: 'flex',
26
+ alignItems: 'center',
27
+ justifyContent: 'center',
28
+ zIndex: 1000,
29
+ border: 'none',
30
+ margin: 0,
31
+ width: '100%',
32
+ height: '100%',
33
+ }}
34
+ onClick={onClose}
35
+ onKeyDown={(e) => {
36
+ if (e.key === 'Escape' || e.key === 'Enter') {
37
+ onClose();
38
+ }
39
+ }}
40
+ >
41
+ <div
42
+ role="document"
43
+ style={{
44
+ background: colors.bgCard,
45
+ borderRadius: '12px',
46
+ padding: '24px',
47
+ maxWidth: '500px',
48
+ width: '90%',
49
+ boxShadow: `0 4px 20px ${colors.shadowLg}`,
50
+ fontFamily: fonts.body,
51
+ }}
52
+ onClick={(e) => e.stopPropagation()}
53
+ onKeyDown={(e) => e.stopPropagation()}
54
+ >
55
+ <h3
56
+ style={{
57
+ margin: '0 0 12px 0',
58
+ fontSize: '18px',
59
+ fontWeight: '600',
60
+ color: isError ? colors.error : colors.textPrimary,
61
+ }}
62
+ >
63
+ {title || (isError ? 'Error' : 'Information')}
64
+ </h3>
65
+ <p
66
+ style={{
67
+ margin: '0 0 24px 0',
68
+ fontSize: '14px',
69
+ color: colors.textSecondary,
70
+ lineHeight: '1.5',
71
+ }}
72
+ >
73
+ {message}
74
+ </p>
75
+ <div
76
+ style={{
77
+ display: 'flex',
78
+ gap: '12px',
79
+ justifyContent: 'flex-end',
80
+ }}
81
+ >
82
+ <button
83
+ type="button"
84
+ onClick={onClose}
85
+ style={{
86
+ padding: '10px 20px',
87
+ background: isError ? colors.buttonDanger : colors.buttonPrimary,
88
+ border: 'none',
89
+ borderRadius: '8px',
90
+ color: colors.textInverse,
91
+ fontSize: '14px',
92
+ fontWeight: '500',
93
+ fontFamily: fonts.body,
94
+ cursor: 'pointer',
95
+ transition: 'all 0.2s',
96
+ }}
97
+ onMouseEnter={(e) => {
98
+ e.currentTarget.style.background = isError
99
+ ? colors.buttonDangerHover
100
+ : colors.buttonPrimaryHover;
101
+ }}
102
+ onMouseLeave={(e) => {
103
+ e.currentTarget.style.background = isError
104
+ ? colors.buttonDanger
105
+ : colors.buttonPrimary;
106
+ }}
107
+ >
108
+ OK
109
+ </button>
110
+ </div>
111
+ </div>
112
+ </dialog>
113
+ );
114
+ }
115
+
116
+ export default AlertModal;
@@ -0,0 +1,57 @@
1
+ import { IconApi } from '@tabler/icons-react';
2
+ import { colors, fonts } from '../../theme';
3
+
4
+ /**
5
+ * API Documentation button component
6
+ * Opens Swagger/OpenAPI documentation in a new tab
7
+ */
8
+ export default function ApiDocsButton() {
9
+ const handleClick = () => {
10
+ window.open('/api-docs', '_blank');
11
+ };
12
+
13
+ return (
14
+ <button
15
+ type="button"
16
+ onClick={handleClick}
17
+ data-tour="api-docs-button"
18
+ style={{
19
+ position: 'fixed',
20
+ bottom: '20px',
21
+ right: '80px',
22
+ background: colors.bgCard,
23
+ border: `1px solid ${colors.borderLight}`,
24
+ borderRadius: '50%',
25
+ width: '48px',
26
+ height: '48px',
27
+ padding: 0,
28
+ color: colors.textSecondary,
29
+ cursor: 'pointer',
30
+ fontFamily: fonts.body,
31
+ boxShadow: `0 4px 12px ${colors.shadowMd}`,
32
+ display: 'flex',
33
+ alignItems: 'center',
34
+ justifyContent: 'center',
35
+ zIndex: 9999,
36
+ transition: 'all 0.2s ease',
37
+ }}
38
+ onMouseEnter={(e) => {
39
+ e.currentTarget.style.background = colors.bgHover;
40
+ e.currentTarget.style.color = colors.textPrimary;
41
+ e.currentTarget.style.borderColor = colors.borderMedium;
42
+ e.currentTarget.style.transform = 'scale(1.1)';
43
+ e.currentTarget.style.boxShadow = `0 6px 16px ${colors.shadowLg}`;
44
+ }}
45
+ onMouseLeave={(e) => {
46
+ e.currentTarget.style.background = colors.bgCard;
47
+ e.currentTarget.style.color = colors.textSecondary;
48
+ e.currentTarget.style.borderColor = colors.borderLight;
49
+ e.currentTarget.style.transform = 'scale(1)';
50
+ e.currentTarget.style.boxShadow = `0 4px 12px ${colors.shadowMd}`;
51
+ }}
52
+ title="View API Documentation"
53
+ >
54
+ <IconApi size={20} stroke={1.5} />
55
+ </button>
56
+ );
57
+ }
@@ -24,8 +24,24 @@ function appendFilterParams(queryParams, filters) {
24
24
  }
25
25
  }
26
26
 
27
+ const VALID_TABS = ['traffic', 'logs', 'setup', 'playground', 'smart-scan'];
28
+ const DEFAULT_TAB = 'traffic';
29
+
30
+ function getTabFromHash() {
31
+ const hash = window.location.hash.slice(1); // Remove '#'
32
+ const tab = hash.startsWith('/') ? hash.slice(1) : hash; // Remove leading '/'
33
+ return VALID_TABS.includes(tab) ? tab : DEFAULT_TAB;
34
+ }
35
+
36
+ function updateUrlHash(tab) {
37
+ const newHash = `#/${tab}`;
38
+ if (window.location.hash !== newHash) {
39
+ window.history.replaceState(null, '', newHash);
40
+ }
41
+ }
42
+
27
43
  export function useAppState() {
28
- const [activeTab, setActiveTab] = useState('traffic');
44
+ const [activeTab, setActiveTab] = useState(() => getTabFromHash());
29
45
  const [requests, setRequests] = useState([]);
30
46
  const [selected, setSelected] = useState(null);
31
47
  const [filters, setFilters] = useState({});
@@ -37,6 +53,32 @@ export function useAppState() {
37
53
  const prevTabRef = useRef(activeTab);
38
54
  const filtersRef = useRef(filters);
39
55
 
56
+ // Initialize URL hash on mount if missing
57
+ useEffect(() => {
58
+ if (!window.location.hash || window.location.hash === '#') {
59
+ const initialTab = getTabFromHash();
60
+ updateUrlHash(initialTab);
61
+ }
62
+ }, []);
63
+
64
+ // Sync URL hash with activeTab
65
+ useEffect(() => {
66
+ updateUrlHash(activeTab);
67
+ }, [activeTab]);
68
+
69
+ // Listen for hash changes (back/forward buttons)
70
+ useEffect(() => {
71
+ const handleHashChange = () => {
72
+ const tabFromHash = getTabFromHash();
73
+ if (tabFromHash !== activeTab) {
74
+ setActiveTab(tabFromHash);
75
+ }
76
+ };
77
+
78
+ window.addEventListener('hashchange', handleHashChange);
79
+ return () => window.removeEventListener('hashchange', handleHashChange);
80
+ }, [activeTab]);
81
+
40
82
  const loadStatistics = async () => {
41
83
  try {
42
84
  const queryParams = new URLSearchParams();
@@ -1,7 +1,12 @@
1
1
  import { IconEye, IconRefresh, IconRestore, IconTrash } from '@tabler/icons-react';
2
+ import { useState } from 'react';
2
3
  import { colors, fonts, withOpacity } from '../theme';
4
+ import ConfirmationModal from './ConfirmationModal';
3
5
 
4
6
  function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onDelete }) {
7
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
8
+ const [pendingDelete, setPendingDelete] = useState(null);
9
+
5
10
  if (backups.length === 0) {
6
11
  return null;
7
12
  }
@@ -126,9 +131,8 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
126
131
  <button
127
132
  type="button"
128
133
  onClick={() => {
129
- if (confirm('Are you sure you want to delete this backup?')) {
130
- onDelete(backup.backupPath);
131
- }
134
+ setPendingDelete(backup.backupPath);
135
+ setShowDeleteModal(true);
132
136
  }}
133
137
  style={{
134
138
  padding: '6px 12px',
@@ -189,6 +193,26 @@ function BackupList({ backups, loadingBackups, onRefresh, onRestore, onView, onD
189
193
  </div>
190
194
  ))}
191
195
  </div>
196
+
197
+ <ConfirmationModal
198
+ isOpen={showDeleteModal}
199
+ onClose={() => {
200
+ setShowDeleteModal(false);
201
+ setPendingDelete(null);
202
+ }}
203
+ onConfirm={() => {
204
+ if (pendingDelete) {
205
+ onDelete(pendingDelete);
206
+ }
207
+ setShowDeleteModal(false);
208
+ setPendingDelete(null);
209
+ }}
210
+ title="Delete Backup"
211
+ message="Are you sure you want to delete this backup? This action cannot be undone."
212
+ confirmText="Delete"
213
+ cancelText="Cancel"
214
+ danger={true}
215
+ />
192
216
  </div>
193
217
  );
194
218
  }
@@ -1,45 +1,44 @@
1
1
  import { getJsonRpcMethod } from './requestUtils.js';
2
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
- }
3
+ function matchesRequests(req, resp) {
4
+ // Session ID must match (or both null for initiation)
5
+ const sessionMatch = req.session_id === resp.session_id;
6
+ if (!sessionMatch) {
7
+ return false;
8
+ }
14
9
 
15
- // JSON-RPC Method must match
16
- const reqMethod = getJsonRpcMethod(req);
17
- const respMethod = getJsonRpcMethod(resp);
10
+ // JSON-RPC Method must match
11
+ const reqMethod = getJsonRpcMethod(req);
12
+ const respMethod = getJsonRpcMethod(resp);
18
13
 
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)
14
+ // Both must have a method, and they must match
15
+ if (!reqMethod || !respMethod) {
16
+ // If either doesn't have a method, we can't match by method
17
+ // Fall back to JSON-RPC ID matching only
36
18
  if (req.jsonrpc_id && resp.jsonrpc_id) {
37
19
  return req.jsonrpc_id === resp.jsonrpc_id;
38
20
  }
21
+ // If no method and no ID, we can't match reliably
22
+ return false;
23
+ }
24
+
25
+ const methodMatch = reqMethod === respMethod;
26
+ if (!methodMatch) {
27
+ return false;
28
+ }
29
+
30
+ // If JSON-RPC ID exists, it must match (for more precise pairing)
31
+ if (req.jsonrpc_id && resp.jsonrpc_id) {
32
+ return req.jsonrpc_id === resp.jsonrpc_id;
33
+ }
34
+
35
+ // If no JSON-RPC ID, match by session and method only
36
+ return true;
37
+ }
39
38
 
40
- // If no JSON-RPC ID, match by session and method only
41
- return true;
42
- };
39
+ export function pairRequestsWithResponses(requests) {
40
+ const pairs = [];
41
+ const processed = new Set();
43
42
 
44
43
  requests.forEach((request) => {
45
44
  if (processed.has(request.frame_number)) {
@@ -52,7 +51,7 @@ export function pairRequestsWithResponses(requests) {
52
51
  (r) =>
53
52
  r.direction === 'response' &&
54
53
  !processed.has(r.frame_number) &&
55
- matches(request, r) &&
54
+ matchesRequests(request, r) &&
56
55
  r.frame_number > request.frame_number
57
56
  );
58
57
 
@@ -71,7 +70,7 @@ export function pairRequestsWithResponses(requests) {
71
70
  (r) =>
72
71
  r.direction === 'request' &&
73
72
  !processed.has(r.frame_number) &&
74
- matches(r, request) &&
73
+ matchesRequests(r, request) &&
75
74
  r.frame_number < request.frame_number
76
75
  );
77
76
 
@@ -1,4 +1,5 @@
1
1
  const LLM_SERVER = 'LLM Server';
2
+
2
3
  export function extractServerName(request) {
3
4
  if (request.body_json) {
4
5
  try {
@@ -1,132 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import Database from 'better-sqlite3';
4
-
5
- function createTables(db) {
6
- db.exec(`
7
- -- Packet capture table
8
- -- Each HTTP request/response is stored as a packet for forensic analysis
9
- CREATE TABLE IF NOT EXISTS packets (
10
- frame_number INTEGER PRIMARY KEY AUTOINCREMENT,
11
-
12
- -- Timestamps (nanosecond precision)
13
- timestamp_ns INTEGER NOT NULL, -- Unix timestamp in nanoseconds
14
- timestamp_iso TEXT NOT NULL, -- ISO 8601 formatted timestamp for readability
15
-
16
- -- Packet direction and protocol
17
- direction TEXT NOT NULL CHECK(direction IN ('request', 'response')),
18
- protocol TEXT NOT NULL DEFAULT 'HTTP',
19
-
20
- -- Session identification (normalized from various header formats)
21
- session_id TEXT, -- Normalized session ID (from mcp-session-id, Mcp-Session-Id, or X-MCP-Session-Id)
22
-
23
- -- HTTP metadata
24
- method TEXT, -- HTTP method (GET, POST, etc.)
25
- url TEXT, -- Request URL/path
26
- status_code INTEGER, -- HTTP status code (for responses)
27
-
28
- -- Headers and body
29
- headers_json TEXT NOT NULL, -- Full HTTP headers as JSON
30
- body_raw TEXT, -- Raw body content
31
- body_json TEXT, -- Parsed JSON body (if applicable)
32
-
33
- -- JSON-RPC metadata (for correlation)
34
- jsonrpc_id TEXT, -- JSON-RPC request ID
35
- jsonrpc_method TEXT, -- JSON-RPC method (e.g., 'tools/list', 'tools/call')
36
- jsonrpc_result TEXT, -- JSON-RPC result (for responses, as JSON string)
37
- jsonrpc_error TEXT, -- JSON-RPC error (for error responses, as JSON string)
38
-
39
- -- Packet metadata
40
- length INTEGER NOT NULL, -- Total packet size in bytes
41
- info TEXT, -- Summary info for quick viewing
42
-
43
- -- Network metadata
44
- user_agent TEXT, -- User agent string
45
- remote_address TEXT, -- Remote IP address
46
- host TEXT -- Host header value
47
- );
48
-
49
- -- Conversations table - correlates request/response pairs
50
- CREATE TABLE IF NOT EXISTS conversations (
51
- conversation_id INTEGER PRIMARY KEY AUTOINCREMENT,
52
- request_frame_number INTEGER NOT NULL,
53
- response_frame_number INTEGER,
54
- session_id TEXT,
55
- jsonrpc_id TEXT,
56
- method TEXT,
57
- request_timestamp_ns INTEGER NOT NULL,
58
- response_timestamp_ns INTEGER,
59
- duration_ms REAL, -- Round-trip time in milliseconds
60
- status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'completed', 'timeout', 'error')),
61
-
62
- FOREIGN KEY (request_frame_number) REFERENCES packets(frame_number),
63
- FOREIGN KEY (response_frame_number) REFERENCES packets(frame_number)
64
- );
65
-
66
- -- Sessions table - tracks session metadata
67
- CREATE TABLE IF NOT EXISTS sessions (
68
- session_id TEXT PRIMARY KEY,
69
- first_seen_ns INTEGER NOT NULL,
70
- last_seen_ns INTEGER NOT NULL,
71
- packet_count INTEGER DEFAULT 0,
72
- user_agent TEXT,
73
- remote_address TEXT,
74
- host TEXT
75
- );
76
-
77
- -- Create indexes for forensic analysis
78
- CREATE INDEX IF NOT EXISTS idx_packets_timestamp ON packets(timestamp_ns);
79
- CREATE INDEX IF NOT EXISTS idx_packets_session ON packets(session_id);
80
- CREATE INDEX IF NOT EXISTS idx_packets_direction ON packets(direction);
81
- CREATE INDEX IF NOT EXISTS idx_packets_jsonrpc_id ON packets(jsonrpc_id);
82
- CREATE INDEX IF NOT EXISTS idx_packets_jsonrpc_method ON packets(jsonrpc_method);
83
- CREATE INDEX IF NOT EXISTS idx_packets_method ON packets(method);
84
- CREATE INDEX IF NOT EXISTS idx_packets_status_code ON packets(status_code);
85
- CREATE INDEX IF NOT EXISTS idx_packets_session_timestamp ON packets(session_id, timestamp_ns);
86
-
87
- CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id);
88
- CREATE INDEX IF NOT EXISTS idx_conversations_jsonrpc_id ON conversations(jsonrpc_id);
89
- CREATE INDEX IF NOT EXISTS idx_conversations_request_frame ON conversations(request_frame_number);
90
- CREATE INDEX IF NOT EXISTS idx_conversations_response_frame ON conversations(response_frame_number);
91
- CREATE INDEX IF NOT EXISTS idx_conversations_timestamp ON conversations(request_timestamp_ns);
92
-
93
- CREATE INDEX IF NOT EXISTS idx_sessions_first_seen ON sessions(first_seen_ns);
94
- CREATE INDEX IF NOT EXISTS idx_sessions_last_seen ON sessions(last_seen_ns);
95
- `);
96
-
97
- return db;
98
- }
99
-
100
- export function initDb(dbConnectionString) {
101
- const db = new Database(dbConnectionString);
102
- db.pragma('journal_mode = WAL');
103
- db.pragma('foreign_keys = ON');
104
-
105
- return createTables(db);
106
- }
107
-
108
- /**
109
- * Open or create a database file, ensuring the directory exists
110
- * Creates tables if the database is new or ensures they exist
111
- */
112
- export function openDb(dbPath) {
113
- // Ensure the directory exists
114
- const dbDir = path.dirname(dbPath);
115
- if (!fs.existsSync(dbDir)) {
116
- fs.mkdirSync(dbDir, { recursive: true });
117
- }
118
-
119
- // Check if database file exists
120
- const _dbExists = fs.existsSync(dbPath);
121
-
122
- // Open or create the database
123
- const db = new Database(dbPath);
124
- db.pragma('journal_mode = WAL');
125
- db.pragma('foreign_keys = ON');
126
-
127
- // Create tables if database is new or tables don't exist
128
- // Even if database exists, ensure tables exist (in case schema changed)
129
- createTables(db);
130
-
131
- return db;
132
- }