@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,107 @@
1
+ /**
2
+ * Repository for database schema operations
3
+ * Handles table and index creation
4
+ * All SQL schema statements are encapsulated here
5
+ */
6
+ export class SchemaRepository {
7
+ constructor(db) {
8
+ this.db = db;
9
+ }
10
+
11
+ /**
12
+ * Create all database tables and indexes
13
+ * This is called during database initialization
14
+ */
15
+ createSchema() {
16
+ this.db.exec(`
17
+ -- Packet capture table
18
+ -- Each HTTP request/response is stored as a packet for forensic analysis
19
+ CREATE TABLE IF NOT EXISTS packets (
20
+ frame_number INTEGER PRIMARY KEY AUTOINCREMENT,
21
+
22
+ -- Timestamps (nanosecond precision)
23
+ timestamp_ns INTEGER NOT NULL, -- Unix timestamp in nanoseconds
24
+ timestamp_iso TEXT NOT NULL, -- ISO 8601 formatted timestamp for readability
25
+
26
+ -- Packet direction and protocol
27
+ direction TEXT NOT NULL CHECK(direction IN ('request', 'response')),
28
+ protocol TEXT NOT NULL DEFAULT 'HTTP',
29
+
30
+ -- Session identification (normalized from various header formats)
31
+ session_id TEXT, -- Normalized session ID (from mcp-session-id, Mcp-Session-Id, or X-MCP-Session-Id)
32
+
33
+ -- HTTP metadata
34
+ method TEXT, -- HTTP method (GET, POST, etc.)
35
+ url TEXT, -- Request URL/path
36
+ status_code INTEGER, -- HTTP status code (for responses)
37
+
38
+ -- Headers and body
39
+ headers_json TEXT NOT NULL, -- Full HTTP headers as JSON
40
+ body_raw TEXT, -- Raw body content
41
+ body_json TEXT, -- Parsed JSON body (if applicable)
42
+
43
+ -- JSON-RPC metadata (for correlation)
44
+ jsonrpc_id TEXT, -- JSON-RPC request ID
45
+ jsonrpc_method TEXT, -- JSON-RPC method (e.g., 'tools/list', 'tools/call')
46
+ jsonrpc_result TEXT, -- JSON-RPC result (for responses, as JSON string)
47
+ jsonrpc_error TEXT, -- JSON-RPC error (for error responses, as JSON string)
48
+
49
+ -- Packet metadata
50
+ length INTEGER NOT NULL, -- Total packet size in bytes
51
+ info TEXT, -- Summary info for quick viewing
52
+
53
+ -- Network metadata
54
+ user_agent TEXT, -- User agent string
55
+ remote_address TEXT, -- Remote IP address
56
+ host TEXT -- Host header value
57
+ );
58
+
59
+ -- Conversations table - correlates request/response pairs
60
+ CREATE TABLE IF NOT EXISTS conversations (
61
+ conversation_id INTEGER PRIMARY KEY AUTOINCREMENT,
62
+ request_frame_number INTEGER NOT NULL,
63
+ response_frame_number INTEGER,
64
+ session_id TEXT,
65
+ jsonrpc_id TEXT,
66
+ method TEXT,
67
+ request_timestamp_ns INTEGER NOT NULL,
68
+ response_timestamp_ns INTEGER,
69
+ duration_ms REAL, -- Round-trip time in milliseconds
70
+ status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'completed', 'timeout', 'error')),
71
+
72
+ FOREIGN KEY (request_frame_number) REFERENCES packets(frame_number),
73
+ FOREIGN KEY (response_frame_number) REFERENCES packets(frame_number)
74
+ );
75
+
76
+ -- Sessions table - tracks session metadata
77
+ CREATE TABLE IF NOT EXISTS sessions (
78
+ session_id TEXT PRIMARY KEY,
79
+ first_seen_ns INTEGER NOT NULL,
80
+ last_seen_ns INTEGER NOT NULL,
81
+ packet_count INTEGER DEFAULT 0,
82
+ user_agent TEXT,
83
+ remote_address TEXT,
84
+ host TEXT
85
+ );
86
+
87
+ -- Create indexes for forensic analysis
88
+ CREATE INDEX IF NOT EXISTS idx_packets_timestamp ON packets(timestamp_ns);
89
+ CREATE INDEX IF NOT EXISTS idx_packets_session ON packets(session_id);
90
+ CREATE INDEX IF NOT EXISTS idx_packets_direction ON packets(direction);
91
+ CREATE INDEX IF NOT EXISTS idx_packets_jsonrpc_id ON packets(jsonrpc_id);
92
+ CREATE INDEX IF NOT EXISTS idx_packets_jsonrpc_method ON packets(jsonrpc_method);
93
+ CREATE INDEX IF NOT EXISTS idx_packets_method ON packets(method);
94
+ CREATE INDEX IF NOT EXISTS idx_packets_status_code ON packets(status_code);
95
+ CREATE INDEX IF NOT EXISTS idx_packets_session_timestamp ON packets(session_id, timestamp_ns);
96
+
97
+ CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id);
98
+ CREATE INDEX IF NOT EXISTS idx_conversations_jsonrpc_id ON conversations(jsonrpc_id);
99
+ CREATE INDEX IF NOT EXISTS idx_conversations_request_frame ON conversations(request_frame_number);
100
+ CREATE INDEX IF NOT EXISTS idx_conversations_response_frame ON conversations(response_frame_number);
101
+ CREATE INDEX IF NOT EXISTS idx_conversations_timestamp ON conversations(request_timestamp_ns);
102
+
103
+ CREATE INDEX IF NOT EXISTS idx_sessions_first_seen ON sessions(first_seen_ns);
104
+ CREATE INDEX IF NOT EXISTS idx_sessions_last_seen ON sessions(last_seen_ns);
105
+ `);
106
+ }
107
+ }
@@ -0,0 +1,59 @@
1
+ import { Defaults } from '#core/constants/Defaults.js';
2
+
3
+ /**
4
+ * Repository for session-related database operations
5
+ */
6
+ export class SessionRepository {
7
+ constructor(db) {
8
+ this.db = db;
9
+ }
10
+
11
+ /**
12
+ * Get session metadata
13
+ */
14
+ getSessions(filters = {}) {
15
+ const {
16
+ startTime = null,
17
+ endTime = null,
18
+ limit = Defaults.DEFAULT_LIMIT,
19
+ offset = Defaults.DEFAULT_OFFSET,
20
+ } = filters;
21
+
22
+ const queryParts = ['SELECT * FROM sessions WHERE 1=1'];
23
+ const params = [];
24
+
25
+ if (startTime) {
26
+ queryParts.push('AND first_seen_ns >= ?');
27
+ params.push(startTime);
28
+ }
29
+
30
+ if (endTime) {
31
+ queryParts.push('AND last_seen_ns <= ?');
32
+ params.push(endTime);
33
+ }
34
+
35
+ queryParts.push('ORDER BY first_seen_ns DESC LIMIT ? OFFSET ?');
36
+ params.push(limit, offset);
37
+
38
+ const query = queryParts.join(' ');
39
+ const stmt = this.db.prepare(query);
40
+ return stmt.all(...params);
41
+ }
42
+
43
+ /**
44
+ * Update or create session record
45
+ */
46
+ upsertSession(sessionId, timestampNs, userAgent, remoteAddress, host) {
47
+ const stmt = this.db.prepare(`
48
+ INSERT INTO sessions (session_id, first_seen_ns, last_seen_ns, packet_count, user_agent, remote_address, host)
49
+ VALUES (?, ?, ?, 1, ?, ?, ?)
50
+ ON CONFLICT(session_id) DO UPDATE SET
51
+ last_seen_ns = excluded.last_seen_ns,
52
+ packet_count = packet_count + 1,
53
+ user_agent = COALESCE(excluded.user_agent, user_agent),
54
+ remote_address = COALESCE(excluded.remote_address, remote_address),
55
+ host = COALESCE(excluded.host, host)
56
+ `);
57
+ stmt.run(sessionId, timestampNs, timestampNs, userAgent, remoteAddress, host);
58
+ }
59
+ }
@@ -0,0 +1,54 @@
1
+ import { StatusCodeRanges } from '#core/constants/StatusCodes.js';
2
+
3
+ /**
4
+ * Repository for statistics-related database operations
5
+ */
6
+ export class StatisticsRepository {
7
+ constructor(db) {
8
+ this.db = db;
9
+ }
10
+
11
+ /**
12
+ * Get statistics for forensic analysis
13
+ */
14
+ getStatistics(filters = {}) {
15
+ const { sessionId = null, startTime = null, endTime = null } = filters;
16
+
17
+ const whereParts = ['WHERE 1=1'];
18
+ const params = [];
19
+
20
+ if (sessionId) {
21
+ whereParts.push('AND session_id = ?');
22
+ params.push(sessionId);
23
+ }
24
+
25
+ if (startTime) {
26
+ whereParts.push('AND timestamp_ns >= ?');
27
+ params.push(startTime);
28
+ }
29
+
30
+ if (endTime) {
31
+ whereParts.push('AND timestamp_ns <= ?');
32
+ params.push(endTime);
33
+ }
34
+
35
+ const whereClause = whereParts.join(' ');
36
+ const statsQuery = `
37
+ SELECT
38
+ COUNT(*) as total_packets,
39
+ COUNT(CASE WHEN direction = 'request' THEN 1 END) as total_requests,
40
+ COUNT(CASE WHEN direction = 'response' THEN 1 END) as total_responses,
41
+ COUNT(CASE WHEN status_code >= ${StatusCodeRanges.CLIENT_ERROR_START} THEN 1 END) as total_errors,
42
+ COUNT(DISTINCT session_id) as unique_sessions,
43
+ AVG(length) as avg_packet_size,
44
+ SUM(length) as total_bytes,
45
+ MIN(timestamp_ns) as first_packet_ns,
46
+ MAX(timestamp_ns) as last_packet_ns
47
+ FROM packets
48
+ ${whereClause}
49
+ `;
50
+
51
+ const stmt = this.db.prepare(statsQuery);
52
+ return stmt.get(...params);
53
+ }
54
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Repository layer exports
3
+ * All database access should go through repositories
4
+ */
5
+ export { AuditRepository } from './AuditRepository.js';
6
+ export { ConversationRepository } from './ConversationRepository.js';
7
+ export { PacketRepository } from './PacketRepository.js';
8
+ export { SchemaRepository } from './SchemaRepository.js';
9
+ export { SessionRepository } from './SessionRepository.js';
10
+ export { StatisticsRepository } from './StatisticsRepository.js';
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Service for audit logging business logic
3
+ * Coordinates between audit repository and session repository
4
+ * HTTP-agnostic: accepts models, returns models
5
+ */
6
+ import { StatusCodeRanges } from '../constants/StatusCodes.js';
7
+
8
+ export class AuditService {
9
+ constructor(auditRepository, sessionRepository, conversationRepository) {
10
+ this.auditRepository = auditRepository;
11
+ this.sessionRepository = sessionRepository;
12
+ this.conversationRepository = conversationRepository;
13
+ }
14
+
15
+ /**
16
+ * Normalize session ID from various header formats
17
+ */
18
+ _normalizeSessionId(headers) {
19
+ if (!headers || typeof headers !== 'object') {
20
+ return null;
21
+ }
22
+
23
+ const sessionHeaderKeys = [
24
+ 'mcp-session-id',
25
+ 'Mcp-Session-Id',
26
+ 'X-MCP-Session-Id',
27
+ 'x-mcp-session-id',
28
+ 'MCP-Session-Id',
29
+ ];
30
+
31
+ for (const key of sessionHeaderKeys) {
32
+ if (headers[key]) {
33
+ return headers[key];
34
+ }
35
+ }
36
+
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Calculate duration in milliseconds
42
+ */
43
+ _calculateDurationMs(startNs, endNs) {
44
+ return (endNs - startNs) / 1_000_000;
45
+ }
46
+
47
+ /**
48
+ * Log request packet and create conversation entry
49
+ */
50
+ logRequestPacket(options) {
51
+ // Normalize session ID before passing to repository
52
+ const sessionId = this._normalizeSessionId(options.headers) || options.sessionId || null;
53
+ const result = this.auditRepository.logRequestPacket({
54
+ ...options,
55
+ sessionId,
56
+ });
57
+
58
+ // Update or create session record
59
+ if (result.sessionId) {
60
+ this.sessionRepository.upsertSession(
61
+ result.sessionId,
62
+ result.timestampNs,
63
+ options.userAgent || null,
64
+ options.remoteAddress || null,
65
+ options.headers?.host || options.headers?.Host || null
66
+ );
67
+ }
68
+
69
+ // Create conversation entry for request
70
+ if (result.jsonrpcId) {
71
+ const method = options.body?.method || options.method || null;
72
+ this.conversationRepository.createConversation(
73
+ result.frameNumber,
74
+ result.sessionId,
75
+ result.jsonrpcId,
76
+ method,
77
+ result.timestampNs
78
+ );
79
+ }
80
+
81
+ return result;
82
+ }
83
+
84
+ /**
85
+ * Log response packet and update conversation entry
86
+ */
87
+ logResponsePacket(options) {
88
+ // Normalize session ID before passing to repository
89
+ const sessionId = this._normalizeSessionId(options.headers) || options.sessionId || null;
90
+ const result = this.auditRepository.logResponsePacket({
91
+ ...options,
92
+ sessionId,
93
+ });
94
+
95
+ // Update session record
96
+ if (result.sessionId) {
97
+ this.sessionRepository.upsertSession(
98
+ result.sessionId,
99
+ result.timestampNs,
100
+ options.userAgent || null,
101
+ options.remoteAddress || null,
102
+ options.headers?.host || options.headers?.Host || null
103
+ );
104
+ }
105
+
106
+ // Update conversation entry with response
107
+ if (result.jsonrpcId || options.requestFrameNumber) {
108
+ const durationMs = options.requestTimestampNs
109
+ ? this._calculateDurationMs(options.requestTimestampNs, result.timestampNs)
110
+ : null;
111
+
112
+ const statusCode = options.statusCode || StatusCodeRanges.SUCCESS_MIN;
113
+ const status =
114
+ statusCode >= StatusCodeRanges.SUCCESS_MIN && statusCode <= StatusCodeRanges.SUCCESS_MAX
115
+ ? 'completed'
116
+ : 'error';
117
+
118
+ if (options.requestFrameNumber) {
119
+ // Update existing conversation
120
+ this.conversationRepository.updateConversationWithResponse(
121
+ options.requestFrameNumber,
122
+ result.frameNumber,
123
+ result.timestampNs,
124
+ durationMs,
125
+ status
126
+ );
127
+ } else if (result.jsonrpcId) {
128
+ // Try to find conversation by JSON-RPC ID
129
+ const conv = this.conversationRepository.findConversationByJsonRpcId(result.jsonrpcId);
130
+ if (conv) {
131
+ this.conversationRepository.updateConversationWithResponse(
132
+ conv.request_frame_number,
133
+ result.frameNumber,
134
+ result.timestampNs,
135
+ durationMs,
136
+ status
137
+ );
138
+ }
139
+ }
140
+ }
141
+
142
+ return result;
143
+ }
144
+ }
@@ -0,0 +1,222 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ /**
6
+ * Service for backup operations
7
+ * Handles creating, restoring, listing, and deleting backups
8
+ */
9
+ export class BackupService {
10
+ constructor(configService, logger) {
11
+ this.configService = configService;
12
+ this.logger = logger;
13
+ }
14
+
15
+ /**
16
+ * List all backups
17
+ */
18
+ listBackups() {
19
+ const backups = [];
20
+ const homeDir = homedir();
21
+
22
+ const commonPaths = [
23
+ path.join(homeDir, '.cursor', 'mcp.json'),
24
+ path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
25
+ ];
26
+
27
+ const backupDirs = [path.join(homeDir, '.cursor'), path.join(homeDir, '.codeium', 'windsurf')];
28
+
29
+ // Find backups with new format: .mcp.json-mcpshark.<datetime>.json
30
+ for (const dir of backupDirs) {
31
+ if (fs.existsSync(dir)) {
32
+ const files = fs.readdirSync(dir);
33
+ const matchingFiles = files.filter((file) => {
34
+ return /^\.(.+)-mcpshark\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.json$/.test(file);
35
+ });
36
+ for (const file of matchingFiles) {
37
+ const match = file.match(/^\.(.+)-mcpshark\./);
38
+ if (match) {
39
+ const originalBasename = match[1];
40
+ const originalPath = path.join(dir, originalBasename);
41
+ const backupPath = path.join(dir, file);
42
+ const stats = fs.statSync(backupPath);
43
+ backups.push({
44
+ originalPath: originalPath,
45
+ backupPath: backupPath,
46
+ createdAt: stats.birthtime.toISOString(),
47
+ modifiedAt: stats.mtime.toISOString(),
48
+ size: stats.size,
49
+ displayPath: originalPath.replace(homeDir, '~'),
50
+ backupFileName: file,
51
+ });
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ // Also check for old .backup format for backward compatibility
58
+ for (const configPath of commonPaths) {
59
+ const backupPath = `${configPath}.backup`;
60
+ if (fs.existsSync(backupPath)) {
61
+ const stats = fs.statSync(backupPath);
62
+ backups.push({
63
+ originalPath: configPath,
64
+ backupPath: backupPath,
65
+ createdAt: stats.birthtime.toISOString(),
66
+ modifiedAt: stats.mtime.toISOString(),
67
+ size: stats.size,
68
+ displayPath: configPath.replace(homeDir, '~'),
69
+ backupFileName: path.basename(backupPath),
70
+ });
71
+ }
72
+ }
73
+
74
+ // Sort by modifiedAt (latest first)
75
+ return backups.sort(
76
+ (a, b) => new Date(b.modifiedAt || b.createdAt) - new Date(a.modifiedAt || a.createdAt)
77
+ );
78
+ }
79
+
80
+ /**
81
+ * View backup content
82
+ */
83
+ viewBackup(backupPath) {
84
+ const resolvedBackupPath = this.configService.resolveFilePath(backupPath);
85
+
86
+ if (!fs.existsSync(resolvedBackupPath)) {
87
+ return null;
88
+ }
89
+
90
+ const content = fs.readFileSync(resolvedBackupPath, 'utf-8');
91
+ const parsed = this.configService.tryParseJson(content);
92
+ const stats = fs.statSync(resolvedBackupPath);
93
+ const homeDir = homedir();
94
+
95
+ return {
96
+ backupPath: resolvedBackupPath,
97
+ displayPath: resolvedBackupPath.replace(homeDir, '~'),
98
+ content: content,
99
+ parsed: parsed,
100
+ createdAt: stats.birthtime.toISOString(),
101
+ modifiedAt: stats.mtime.toISOString(),
102
+ size: stats.size,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Repatch config if it was patched and server is running
108
+ * @private
109
+ */
110
+ _repatchIfNeeded(wasPatched, serverIsRunning, config, targetPath) {
111
+ if (wasPatched && serverIsRunning && config) {
112
+ try {
113
+ // Repatch the restored config
114
+ const repatchedConfig = this.configService.updateConfigForMcpShark(config);
115
+ this.configService.writeConfigAsJson(targetPath, repatchedConfig);
116
+ this.logger?.info(
117
+ { path: targetPath },
118
+ 'Repatched config after restore (server is running)'
119
+ );
120
+ return true;
121
+ } catch (error) {
122
+ this.logger?.error(
123
+ { error: error.message, path: targetPath },
124
+ 'Failed to repatch config after restore'
125
+ );
126
+ // Continue anyway - at least the restore succeeded
127
+ return false;
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+
133
+ /**
134
+ * Determine target path for restore
135
+ */
136
+ _determineTargetPath(originalPath, backupPath) {
137
+ if (originalPath) {
138
+ return this.configService.resolveFilePath(originalPath);
139
+ }
140
+
141
+ // Try to extract from backup filename
142
+ if (backupPath.endsWith('.backup')) {
143
+ return backupPath.replace('.backup', '');
144
+ }
145
+
146
+ // New format: .mcp.json-mcpshark.<datetime>.json
147
+ const match = path.basename(backupPath).match(/^\.(.+)-mcpshark\./);
148
+ if (match) {
149
+ const originalBasename = match[1];
150
+ return path.join(path.dirname(backupPath), originalBasename);
151
+ }
152
+
153
+ return null;
154
+ }
155
+
156
+ /**
157
+ * Restore backup
158
+ * @param {string} backupPath - Path to backup file
159
+ * @param {string} originalPath - Path to original file (optional, will be determined from backup)
160
+ * @param {boolean} serverIsRunning - Whether MCP Shark server is currently running
161
+ * @returns {{success: boolean, error?: string, originalPath?: string, wasPatched?: boolean, repatched?: boolean}}
162
+ */
163
+ restoreBackup(backupPath, originalPath, serverIsRunning = false) {
164
+ const resolvedBackupPath = this.configService.resolveFilePath(backupPath);
165
+
166
+ if (!fs.existsSync(resolvedBackupPath)) {
167
+ return { success: false, error: 'Backup file not found' };
168
+ }
169
+
170
+ const targetPath = this._determineTargetPath(originalPath, resolvedBackupPath);
171
+ if (!targetPath) {
172
+ return { success: false, error: 'Could not determine original file path' };
173
+ }
174
+
175
+ // Read backup content and check if it was patched
176
+ const backupContent = fs.readFileSync(resolvedBackupPath, 'utf8');
177
+ const parseResult = this.configService.parseJsonConfig(backupContent, resolvedBackupPath);
178
+ const wasPatched = parseResult.config && this.configService.isConfigPatched(parseResult.config);
179
+
180
+ // Restore the backup
181
+ fs.writeFileSync(targetPath, backupContent);
182
+
183
+ this.logger?.info({ path: targetPath, wasPatched }, 'Restored config from backup');
184
+
185
+ // If the backup was patched and server is running, repatch it
186
+ const repatched = this._repatchIfNeeded(
187
+ wasPatched,
188
+ serverIsRunning,
189
+ parseResult.config,
190
+ targetPath
191
+ );
192
+
193
+ const homeDir = homedir();
194
+ return {
195
+ success: true,
196
+ originalPath: targetPath.replace(homeDir, '~'),
197
+ wasPatched,
198
+ repatched,
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Delete backup
204
+ */
205
+ deleteBackup(backupPath) {
206
+ const resolvedBackupPath = this.configService.resolveFilePath(backupPath);
207
+
208
+ if (!fs.existsSync(resolvedBackupPath)) {
209
+ return { success: false, error: 'Backup file not found' };
210
+ }
211
+
212
+ fs.unlinkSync(resolvedBackupPath);
213
+
214
+ this.logger?.info({ path: resolvedBackupPath }, 'Deleted backup');
215
+
216
+ const homeDir = homedir();
217
+ return {
218
+ success: true,
219
+ backupPath: resolvedBackupPath.replace(homeDir, '~'),
220
+ };
221
+ }
222
+ }
@@ -0,0 +1,89 @@
1
+ import * as fs from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { getCodexConfigPath } from '#core/configs/index.js';
5
+
6
+ /**
7
+ * Service for detecting configuration files on the system
8
+ */
9
+ export class ConfigDetectionService {
10
+ /**
11
+ * Detect config files on the system
12
+ */
13
+ detectConfigFiles() {
14
+ const detected = [];
15
+ const homeDir = homedir();
16
+ const platform = process.platform;
17
+
18
+ const cursorPaths = [
19
+ path.join(homeDir, '.cursor', 'mcp.json'),
20
+ ...(platform === 'win32'
21
+ ? [path.join(process.env.USERPROFILE || '', '.cursor', 'mcp.json')]
22
+ : []),
23
+ ];
24
+
25
+ const windsurfPaths = [
26
+ path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
27
+ ...(platform === 'win32'
28
+ ? [path.join(process.env.USERPROFILE || '', '.codeium', 'windsurf', 'mcp_config.json')]
29
+ : []),
30
+ ];
31
+
32
+ for (const cursorPath of cursorPaths) {
33
+ if (fs.existsSync(cursorPath)) {
34
+ detected.push({
35
+ editor: 'Cursor',
36
+ path: cursorPath,
37
+ displayPath: cursorPath.replace(homeDir, '~'),
38
+ exists: true,
39
+ });
40
+ break;
41
+ }
42
+ }
43
+
44
+ for (const windsurfPath of windsurfPaths) {
45
+ if (fs.existsSync(windsurfPath)) {
46
+ detected.push({
47
+ editor: 'Windsurf',
48
+ path: windsurfPath,
49
+ displayPath: windsurfPath.replace(homeDir, '~'),
50
+ exists: true,
51
+ });
52
+ break;
53
+ }
54
+ }
55
+
56
+ const codexPath = getCodexConfigPath();
57
+ if (fs.existsSync(codexPath)) {
58
+ detected.push({
59
+ editor: 'Codex',
60
+ path: codexPath,
61
+ displayPath: codexPath.replace(homeDir, '~'),
62
+ exists: true,
63
+ });
64
+ }
65
+
66
+ const defaultPaths = [
67
+ {
68
+ editor: 'Cursor',
69
+ path: path.join(homeDir, '.cursor', 'mcp.json'),
70
+ displayPath: '~/.cursor/mcp.json',
71
+ exists: fs.existsSync(path.join(homeDir, '.cursor', 'mcp.json')),
72
+ },
73
+ {
74
+ editor: 'Windsurf',
75
+ path: path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json'),
76
+ displayPath: '~/.codeium/windsurf/mcp_config.json',
77
+ exists: fs.existsSync(path.join(homeDir, '.codeium', 'windsurf', 'mcp_config.json')),
78
+ },
79
+ {
80
+ editor: 'Codex',
81
+ path: codexPath,
82
+ displayPath: codexPath.replace(homeDir, '~'),
83
+ exists: fs.existsSync(codexPath),
84
+ },
85
+ ];
86
+
87
+ return detected.length > 0 ? detected : defaultPaths;
88
+ }
89
+ }