@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.
- package/README.md +32 -96
- package/bin/mcp-shark.js +1 -1
- package/core/configs/codex.js +68 -0
- package/core/configs/environment.js +51 -0
- package/{lib/common → core}/configs/index.js +16 -1
- package/core/constants/Defaults.js +15 -0
- package/core/constants/HttpStatus.js +14 -0
- package/core/constants/Server.js +20 -0
- package/core/constants/StatusCodes.js +25 -0
- package/core/constants/index.js +7 -0
- package/core/container/DependencyContainer.js +179 -0
- package/core/db/init.js +33 -0
- package/core/index.js +10 -0
- package/{mcp-server/lib/common/error.js → core/libraries/ErrorLibrary.js} +4 -0
- package/core/libraries/LoggerLibrary.js +91 -0
- package/core/libraries/SerializationLibrary.js +32 -0
- package/core/libraries/bootstrap-logger.js +19 -0
- package/core/libraries/errors/ApplicationError.js +97 -0
- package/core/libraries/index.js +17 -0
- package/{mcp-server/lib → core/mcp-server}/auditor/audit.js +77 -53
- package/core/mcp-server/index.js +192 -0
- package/{mcp-server/lib → core/mcp-server}/server/external/all.js +1 -1
- package/core/mcp-server/server/external/config.js +75 -0
- package/{mcp-server/lib → core/mcp-server}/server/external/single/client.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/external/single/request.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/external/single/run.js +20 -11
- package/{mcp-server/lib → core/mcp-server}/server/external/single/transport.js +1 -1
- package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/error.js +1 -1
- package/core/mcp-server/server/internal/handlers/prompts-get.js +28 -0
- package/core/mcp-server/server/internal/handlers/prompts-list.js +21 -0
- package/core/mcp-server/server/internal/handlers/resources-list.js +21 -0
- package/core/mcp-server/server/internal/handlers/resources-read.js +28 -0
- package/core/mcp-server/server/internal/handlers/tools-call.js +44 -0
- package/core/mcp-server/server/internal/handlers/tools-list.js +23 -0
- package/core/mcp-server/server/internal/run.js +53 -0
- package/{mcp-server/lib → core/mcp-server}/server/internal/server.js +11 -1
- package/core/models/ConversationFilters.js +31 -0
- package/core/models/ExportFormat.js +8 -0
- package/core/models/RequestFilters.js +43 -0
- package/core/models/SessionFilters.js +23 -0
- package/core/models/index.js +8 -0
- package/core/repositories/AuditRepository.js +233 -0
- package/core/repositories/ConversationRepository.js +182 -0
- package/core/repositories/PacketRepository.js +237 -0
- package/core/repositories/SchemaRepository.js +107 -0
- package/core/repositories/SessionRepository.js +59 -0
- package/core/repositories/StatisticsRepository.js +54 -0
- package/core/repositories/index.js +10 -0
- package/core/services/AuditService.js +144 -0
- package/core/services/BackupService.js +222 -0
- package/core/services/ConfigDetectionService.js +89 -0
- package/core/services/ConfigFileService.js +210 -0
- package/core/services/ConfigPatchingService.js +137 -0
- package/core/services/ConfigService.js +250 -0
- package/core/services/ConfigTransformService.js +178 -0
- package/core/services/ConversationService.js +19 -0
- package/core/services/ExportService.js +117 -0
- package/core/services/LogService.js +64 -0
- package/core/services/McpClientService.js +235 -0
- package/core/services/McpDiscoveryService.js +107 -0
- package/core/services/RequestService.js +56 -0
- package/core/services/ScanCacheService.js +242 -0
- package/core/services/ScanService.js +167 -0
- package/core/services/ServerManagementService.js +206 -0
- package/core/services/SessionService.js +34 -0
- package/core/services/SettingsService.js +163 -0
- package/core/services/StatisticsService.js +64 -0
- package/core/services/TokenService.js +94 -0
- package/core/services/index.js +25 -0
- package/core/services/parsers/ConfigParserFactory.js +113 -0
- package/core/services/parsers/JsonConfigParser.js +66 -0
- package/core/services/parsers/LegacyJsonConfigParser.js +71 -0
- package/core/services/parsers/TomlConfigParser.js +87 -0
- package/core/services/parsers/index.js +4 -0
- package/{ui/server → core}/utils/scan-cache/directory.js +1 -1
- package/core/utils/validation.js +77 -0
- package/package.json +14 -11
- package/ui/dist/assets/index-CArYxKxS.js +35 -0
- package/ui/dist/index.html +1 -1
- package/ui/server/controllers/BackupController.js +129 -0
- package/ui/server/controllers/ConfigController.js +92 -0
- package/ui/server/controllers/ConversationController.js +41 -0
- package/ui/server/controllers/LogController.js +44 -0
- package/ui/server/controllers/McpClientController.js +60 -0
- package/ui/server/controllers/McpDiscoveryController.js +44 -0
- package/ui/server/controllers/RequestController.js +129 -0
- package/ui/server/controllers/ScanController.js +122 -0
- package/ui/server/controllers/ServerManagementController.js +134 -0
- package/ui/server/controllers/SessionController.js +57 -0
- package/ui/server/controllers/SettingsController.js +24 -0
- package/ui/server/controllers/StatisticsController.js +54 -0
- package/ui/server/controllers/TokenController.js +58 -0
- package/ui/server/controllers/index.js +17 -0
- package/ui/server/routes/backups/index.js +15 -9
- package/ui/server/routes/composite/index.js +62 -32
- package/ui/server/routes/composite/servers.js +20 -15
- package/ui/server/routes/config.js +13 -172
- package/ui/server/routes/conversations.js +9 -19
- package/ui/server/routes/help.js +4 -3
- package/ui/server/routes/logs.js +14 -26
- package/ui/server/routes/playground.js +11 -174
- package/ui/server/routes/requests.js +12 -232
- package/ui/server/routes/sessions.js +10 -21
- package/ui/server/routes/settings.js +10 -192
- package/ui/server/routes/smartscan.js +26 -15
- package/ui/server/routes/statistics.js +8 -79
- package/ui/server/setup.js +162 -0
- package/ui/server/swagger/paths/backups.js +151 -0
- package/ui/server/swagger/paths/components.js +76 -0
- package/ui/server/swagger/paths/config.js +117 -0
- package/ui/server/swagger/paths/conversations.js +29 -0
- package/ui/server/swagger/paths/help.js +82 -0
- package/ui/server/swagger/paths/logs.js +87 -0
- package/ui/server/swagger/paths/playground.js +49 -0
- package/ui/server/swagger/paths/requests.js +178 -0
- package/ui/server/swagger/paths/serverManagement.js +169 -0
- package/ui/server/swagger/paths/sessions.js +61 -0
- package/ui/server/swagger/paths/settings.js +31 -0
- package/ui/server/swagger/paths/smartScan/discovery.js +97 -0
- package/ui/server/swagger/paths/smartScan/index.js +13 -0
- package/ui/server/swagger/paths/smartScan/scans.js +151 -0
- package/ui/server/swagger/paths/smartScan/token.js +71 -0
- package/ui/server/swagger/paths/statistics.js +40 -0
- package/ui/server/swagger/paths.js +38 -0
- package/ui/server/swagger/swagger.js +37 -0
- package/ui/server/utils/cleanup.js +99 -0
- package/ui/server/utils/config.js +18 -96
- package/ui/server/utils/errorHandler.js +43 -0
- package/ui/server/utils/logger.js +2 -2
- package/ui/server/utils/paths.js +27 -30
- package/ui/server/utils/port.js +21 -21
- package/ui/server/utils/process.js +18 -10
- package/ui/server/utils/processState.js +17 -0
- package/ui/server/utils/signals.js +34 -0
- package/ui/server/websocket/broadcast.js +33 -0
- package/ui/server/websocket/handler.js +52 -0
- package/ui/server.js +51 -230
- package/ui/src/App.jsx +2 -0
- package/ui/src/CompositeSetup.jsx +23 -9
- package/ui/src/PacketFilters.jsx +17 -3
- package/ui/src/components/AlertModal.jsx +116 -0
- package/ui/src/components/App/ApiDocsButton.jsx +57 -0
- package/ui/src/components/App/useAppState.js +43 -1
- package/ui/src/components/BackupList.jsx +27 -3
- package/ui/src/utils/requestPairing.js +35 -36
- package/ui/src/utils/requestUtils.js +1 -0
- package/lib/common/db/init.js +0 -132
- package/lib/common/db/logger.js +0 -349
- package/lib/common/db/query.js +0 -403
- package/lib/common/logger.js +0 -90
- package/mcp-server/index.js +0 -138
- package/mcp-server/lib/server/external/config.js +0 -57
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +0 -20
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +0 -13
- package/mcp-server/lib/server/internal/handlers/resources-list.js +0 -13
- package/mcp-server/lib/server/internal/handlers/resources-read.js +0 -20
- package/mcp-server/lib/server/internal/handlers/tools-call.js +0 -35
- package/mcp-server/lib/server/internal/handlers/tools-list.js +0 -15
- package/mcp-server/lib/server/internal/run.js +0 -37
- package/mcp-server/mcp-shark.js +0 -22
- package/ui/dist/assets/index-CFHeMNwd.js +0 -35
- package/ui/server/routes/backups/deleteBackup.js +0 -54
- package/ui/server/routes/backups/listBackups.js +0 -75
- package/ui/server/routes/backups/restoreBackup.js +0 -83
- package/ui/server/routes/backups/viewBackup.js +0 -47
- package/ui/server/routes/composite/setup.js +0 -129
- package/ui/server/routes/composite/status.js +0 -7
- package/ui/server/routes/composite/stop.js +0 -39
- package/ui/server/routes/composite/utils.js +0 -45
- package/ui/server/routes/smartscan/discover.js +0 -118
- package/ui/server/routes/smartscan/scans/clearCache.js +0 -23
- package/ui/server/routes/smartscan/scans/createBatchScans.js +0 -124
- package/ui/server/routes/smartscan/scans/createScan.js +0 -43
- package/ui/server/routes/smartscan/scans/getCachedResults.js +0 -52
- package/ui/server/routes/smartscan/scans/getScan.js +0 -42
- package/ui/server/routes/smartscan/scans/listScans.js +0 -25
- package/ui/server/routes/smartscan/scans.js +0 -13
- package/ui/server/routes/smartscan/token.js +0 -57
- package/ui/server/utils/config-update.js +0 -240
- package/ui/server/utils/scan-cache/all-results.js +0 -197
- package/ui/server/utils/scan-cache/file-operations.js +0 -107
- package/ui/server/utils/scan-cache/hash.js +0 -47
- package/ui/server/utils/scan-cache/server-operations.js +0 -85
- package/ui/server/utils/scan-cache.js +0 -12
- package/ui/server/utils/smartscan-token.js +0 -43
- /package/{mcp-server/lib → core/mcp-server}/server/external/kv.js +0 -0
- /package/{mcp-server/lib → core/mcp-server}/server/internal/handlers/common.js +0 -0
- /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
|
+
}
|