@mcp-shark/mcp-shark 1.5.4 → 1.5.6
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 +154 -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 +63 -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 +163 -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 +205 -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,206 @@
|
|
|
1
|
+
import { initAuditLogger, startMcpSharkServer } from '#core/mcp-server/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service for managing MCP Shark server lifecycle
|
|
5
|
+
* Handles server startup, shutdown, and status
|
|
6
|
+
*/
|
|
7
|
+
export class ServerManagementService {
|
|
8
|
+
constructor(configService, configPatchingService, logger) {
|
|
9
|
+
this.configService = configService;
|
|
10
|
+
this.configPatchingService = configPatchingService;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.serverInstance = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Setup and start MCP Shark server
|
|
17
|
+
* Orchestrates the entire setup process: config processing, patching, and server startup
|
|
18
|
+
* @param {Object} options - Setup options
|
|
19
|
+
* @param {string} [options.filePath] - Path to config file
|
|
20
|
+
* @param {string} [options.fileContent] - Config file content
|
|
21
|
+
* @param {Array} [options.selectedServices] - Selected services to include
|
|
22
|
+
* @param {number} [options.port=9851] - Server port
|
|
23
|
+
* @param {Function} [options.onError] - Error callback
|
|
24
|
+
* @param {Function} [options.onReady] - Ready callback
|
|
25
|
+
* @returns {Promise<Object>} Setup result with convertedConfig, updatedConfig, filePath
|
|
26
|
+
*/
|
|
27
|
+
async setup(options = {}) {
|
|
28
|
+
const { filePath, fileContent, selectedServices, port = 9851, onError, onReady } = options;
|
|
29
|
+
|
|
30
|
+
if (!filePath && !fileContent) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: 'Either filePath or fileContent is required',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If filePath is provided, restore original config if already patched
|
|
38
|
+
// This ensures processSetup reads the original config, not the patched one
|
|
39
|
+
let restoreWarning = null;
|
|
40
|
+
if (filePath && !fileContent) {
|
|
41
|
+
const restoreResult = this.configPatchingService.restoreIfPatched(filePath);
|
|
42
|
+
if (restoreResult.warning) {
|
|
43
|
+
restoreWarning = restoreResult.warning;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Process setup
|
|
48
|
+
const setupResult = this.configService.processSetup(filePath, fileContent, selectedServices);
|
|
49
|
+
|
|
50
|
+
if (!setupResult.success) {
|
|
51
|
+
return setupResult;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { fileData, convertedConfig, updatedConfig } = setupResult;
|
|
55
|
+
const mcpsJsonPath = this.configService.getMcpConfigPath();
|
|
56
|
+
|
|
57
|
+
// Write converted config to MCP Shark config path
|
|
58
|
+
this.configService.writeConfigAsJson(mcpsJsonPath, convertedConfig);
|
|
59
|
+
|
|
60
|
+
// Stop existing server if running
|
|
61
|
+
if (this.serverInstance?.stop) {
|
|
62
|
+
await this.stopServer();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Start server
|
|
66
|
+
await this.startServer({
|
|
67
|
+
configPath: mcpsJsonPath,
|
|
68
|
+
port,
|
|
69
|
+
onError: (err) => {
|
|
70
|
+
if (onError) {
|
|
71
|
+
onError(err);
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
},
|
|
75
|
+
onReady: () => {
|
|
76
|
+
if (onReady) {
|
|
77
|
+
onReady();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Patch the original config file if it exists
|
|
83
|
+
let patchWarning = null;
|
|
84
|
+
if (fileData.resolvedFilePath && this.configService.fileExists(fileData.resolvedFilePath)) {
|
|
85
|
+
const patchResult = this.configPatchingService.patchConfigFile(
|
|
86
|
+
fileData.resolvedFilePath,
|
|
87
|
+
updatedConfig
|
|
88
|
+
);
|
|
89
|
+
if (patchResult.warning) {
|
|
90
|
+
patchWarning = patchResult.warning;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
message: 'MCP Shark server started successfully and config file updated',
|
|
97
|
+
convertedConfig,
|
|
98
|
+
updatedConfig,
|
|
99
|
+
filePath: fileData.resolvedFilePath || null,
|
|
100
|
+
backupPath: null, // TODO: Implement backup creation in BackupService
|
|
101
|
+
warning: restoreWarning || patchWarning || undefined,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Start MCP Shark server
|
|
107
|
+
*/
|
|
108
|
+
async startServer(options = {}) {
|
|
109
|
+
const { configPath, port = 9851, onError, onReady } = options;
|
|
110
|
+
|
|
111
|
+
const mcpsJsonPath = configPath || this.configService.getMcpConfigPath();
|
|
112
|
+
|
|
113
|
+
if (this.serverInstance?.stop) {
|
|
114
|
+
await this.stopServer();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.logger?.info({ path: mcpsJsonPath }, 'Starting MCP-Shark server as library...');
|
|
118
|
+
|
|
119
|
+
const auditLogger = initAuditLogger(this.logger);
|
|
120
|
+
|
|
121
|
+
const serverInstance = await startMcpSharkServer({
|
|
122
|
+
configPath: mcpsJsonPath,
|
|
123
|
+
port,
|
|
124
|
+
auditLogger,
|
|
125
|
+
onError: (err) => {
|
|
126
|
+
this.logger?.error({ error: err.message }, 'Failed to start mcp-shark server');
|
|
127
|
+
this.serverInstance = null;
|
|
128
|
+
if (onError) {
|
|
129
|
+
onError(err);
|
|
130
|
+
}
|
|
131
|
+
throw err;
|
|
132
|
+
},
|
|
133
|
+
onReady: () => {
|
|
134
|
+
this.logger?.info('MCP Shark server is ready!');
|
|
135
|
+
if (onReady) {
|
|
136
|
+
onReady();
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this.serverInstance = serverInstance;
|
|
142
|
+
this.logger?.info('MCP Shark server started successfully');
|
|
143
|
+
|
|
144
|
+
return serverInstance;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Stop MCP Shark server
|
|
149
|
+
*/
|
|
150
|
+
async stopServer() {
|
|
151
|
+
if (this.serverInstance?.stop) {
|
|
152
|
+
await this.serverInstance.stop();
|
|
153
|
+
this.serverInstance = null;
|
|
154
|
+
this.logger?.info('MCP Shark server stopped');
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get server status
|
|
162
|
+
*/
|
|
163
|
+
getServerStatus() {
|
|
164
|
+
return {
|
|
165
|
+
running: this.serverInstance !== null,
|
|
166
|
+
pid: null, // No process PID when using library
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get current server instance
|
|
172
|
+
*/
|
|
173
|
+
getServerInstance() {
|
|
174
|
+
return this.serverInstance;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Set server instance (for external management)
|
|
179
|
+
*/
|
|
180
|
+
setServerInstance(instance) {
|
|
181
|
+
this.serverInstance = instance;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Shutdown the entire application
|
|
186
|
+
* @param {Function} cleanup - Cleanup function to execute
|
|
187
|
+
*/
|
|
188
|
+
async shutdown(cleanup) {
|
|
189
|
+
if (!cleanup || typeof cleanup !== 'function') {
|
|
190
|
+
throw new Error('Cleanup function is required');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.logger?.info('Initiating application shutdown...');
|
|
194
|
+
|
|
195
|
+
// Stop MCP Shark server if running
|
|
196
|
+
if (this.serverInstance?.stop) {
|
|
197
|
+
await this.stopServer();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Execute cleanup
|
|
201
|
+
await cleanup();
|
|
202
|
+
|
|
203
|
+
this.logger?.info('Application shutdown complete');
|
|
204
|
+
return { success: true, message: 'Application shutdown initiated' };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for session-related business logic
|
|
3
|
+
* HTTP-agnostic: accepts models, returns models
|
|
4
|
+
*/
|
|
5
|
+
import { Defaults } from '../constants/Defaults.js';
|
|
6
|
+
|
|
7
|
+
export class SessionService {
|
|
8
|
+
constructor(sessionRepository, packetRepository) {
|
|
9
|
+
this.sessionRepository = sessionRepository;
|
|
10
|
+
this.packetRepository = packetRepository;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get sessions with filters
|
|
15
|
+
* @param {SessionFilters} filters - Typed filter model
|
|
16
|
+
* @returns {Array} Array of session objects (raw from repository)
|
|
17
|
+
*/
|
|
18
|
+
getSessions(filters) {
|
|
19
|
+
const repoFilters = filters.toRepositoryFilters();
|
|
20
|
+
return this.sessionRepository.getSessions(repoFilters);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get requests for a specific session
|
|
25
|
+
* @param {string} sessionId - Session ID
|
|
26
|
+
* @param {number} limit - Maximum number of requests to return
|
|
27
|
+
* @returns {Array} Array of packet objects (raw from repository)
|
|
28
|
+
*/
|
|
29
|
+
getSessionRequests(sessionId, limit) {
|
|
30
|
+
const parsedLimit =
|
|
31
|
+
limit !== undefined ? Number.parseInt(limit) : Defaults.DEFAULT_SESSION_LIMIT;
|
|
32
|
+
return this.packetRepository.getSessionRequests(sessionId, parsedLimit);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getDatabaseFile, getWorkingDirectory } from '#core/configs/index.js';
|
|
5
|
+
import { getScanResultsDirectory } from '#core/utils/scan-cache/directory.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Service for application settings
|
|
9
|
+
* Handles retrieving application configuration and paths
|
|
10
|
+
*/
|
|
11
|
+
export class SettingsService {
|
|
12
|
+
constructor(tokenService, backupService, logger) {
|
|
13
|
+
this.tokenService = tokenService;
|
|
14
|
+
this.backupService = backupService;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert absolute path to display path (replace home with ~)
|
|
20
|
+
*/
|
|
21
|
+
_toDisplayPath(homeDir, absolutePath) {
|
|
22
|
+
return absolutePath.replace(homeDir, '~');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get backup count from backup directories
|
|
27
|
+
*/
|
|
28
|
+
_getBackupCount(cursorBackupDir, windsurfBackupDir, cursorConfigPath, windsurfConfigPath) {
|
|
29
|
+
try {
|
|
30
|
+
const backupDirs = [cursorBackupDir, windsurfBackupDir];
|
|
31
|
+
const newFormatCount = backupDirs.reduce((count, dir) => {
|
|
32
|
+
if (existsSync(dir)) {
|
|
33
|
+
const files = readdirSync(dir);
|
|
34
|
+
const matchingFiles = files.filter((file) => {
|
|
35
|
+
return /^\.(.+)-mcpshark\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.json$/.test(file);
|
|
36
|
+
});
|
|
37
|
+
return count + matchingFiles.length;
|
|
38
|
+
}
|
|
39
|
+
return count;
|
|
40
|
+
}, 0);
|
|
41
|
+
|
|
42
|
+
const commonPaths = [cursorConfigPath, windsurfConfigPath];
|
|
43
|
+
const oldFormatCount = commonPaths.reduce((count, configPath) => {
|
|
44
|
+
if (existsSync(`${configPath}.backup`)) {
|
|
45
|
+
return count + 1;
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}, 0);
|
|
49
|
+
|
|
50
|
+
return newFormatCount + oldFormatCount;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.logger?.error({ error: error.message }, 'Error counting backups');
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get all settings
|
|
59
|
+
*/
|
|
60
|
+
getSettings() {
|
|
61
|
+
const homeDir = homedir();
|
|
62
|
+
const workingDir = getWorkingDirectory();
|
|
63
|
+
const databasePath = getDatabaseFile();
|
|
64
|
+
const scanResultsDir = getScanResultsDirectory();
|
|
65
|
+
const tokenMetadata = this.tokenService.getTokenMetadata();
|
|
66
|
+
|
|
67
|
+
const cursorConfigPath = join(homeDir, '.cursor', 'mcp.json');
|
|
68
|
+
const windsurfConfigPath = join(homeDir, '.codeium', 'windsurf', 'mcp_config.json');
|
|
69
|
+
|
|
70
|
+
const cursorBackupDir = join(homeDir, '.cursor');
|
|
71
|
+
const windsurfBackupDir = join(homeDir, '.codeium', 'windsurf');
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
paths: {
|
|
75
|
+
workingDirectory: {
|
|
76
|
+
absolute: workingDir,
|
|
77
|
+
display: this._toDisplayPath(homeDir, workingDir),
|
|
78
|
+
exists: existsSync(workingDir),
|
|
79
|
+
},
|
|
80
|
+
database: {
|
|
81
|
+
absolute: databasePath,
|
|
82
|
+
display: this._toDisplayPath(homeDir, databasePath),
|
|
83
|
+
exists: existsSync(databasePath),
|
|
84
|
+
},
|
|
85
|
+
smartScanResults: {
|
|
86
|
+
absolute: scanResultsDir,
|
|
87
|
+
display: this._toDisplayPath(homeDir, scanResultsDir),
|
|
88
|
+
exists: existsSync(scanResultsDir),
|
|
89
|
+
},
|
|
90
|
+
smartScanToken: {
|
|
91
|
+
absolute: tokenMetadata.path,
|
|
92
|
+
display: this._toDisplayPath(homeDir, tokenMetadata.path),
|
|
93
|
+
exists: tokenMetadata.exists,
|
|
94
|
+
},
|
|
95
|
+
backupDirectories: {
|
|
96
|
+
cursor: {
|
|
97
|
+
absolute: cursorBackupDir,
|
|
98
|
+
display: this._toDisplayPath(homeDir, cursorBackupDir),
|
|
99
|
+
exists: existsSync(cursorBackupDir),
|
|
100
|
+
},
|
|
101
|
+
windsurf: {
|
|
102
|
+
absolute: windsurfBackupDir,
|
|
103
|
+
display: this._toDisplayPath(homeDir, windsurfBackupDir),
|
|
104
|
+
exists: existsSync(windsurfBackupDir),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
configFiles: {
|
|
108
|
+
cursor: {
|
|
109
|
+
absolute: cursorConfigPath,
|
|
110
|
+
display: this._toDisplayPath(homeDir, cursorConfigPath),
|
|
111
|
+
exists: existsSync(cursorConfigPath),
|
|
112
|
+
},
|
|
113
|
+
windsurf: {
|
|
114
|
+
absolute: windsurfConfigPath,
|
|
115
|
+
display: this._toDisplayPath(homeDir, windsurfConfigPath),
|
|
116
|
+
exists: existsSync(windsurfConfigPath),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
smartScan: {
|
|
121
|
+
token: tokenMetadata.token,
|
|
122
|
+
tokenPath: {
|
|
123
|
+
absolute: tokenMetadata.path,
|
|
124
|
+
display: this._toDisplayPath(homeDir, tokenMetadata.path),
|
|
125
|
+
},
|
|
126
|
+
tokenUpdatedAt: tokenMetadata.updatedAt,
|
|
127
|
+
tokenExists: tokenMetadata.exists,
|
|
128
|
+
},
|
|
129
|
+
database: {
|
|
130
|
+
path: {
|
|
131
|
+
absolute: databasePath,
|
|
132
|
+
display: this._toDisplayPath(homeDir, databasePath),
|
|
133
|
+
},
|
|
134
|
+
exists: existsSync(databasePath),
|
|
135
|
+
},
|
|
136
|
+
system: {
|
|
137
|
+
platform: process.platform,
|
|
138
|
+
homeDirectory: {
|
|
139
|
+
absolute: homeDir,
|
|
140
|
+
display: '~',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
backups: {
|
|
144
|
+
directories: [
|
|
145
|
+
{
|
|
146
|
+
absolute: cursorBackupDir,
|
|
147
|
+
display: this._toDisplayPath(homeDir, cursorBackupDir),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
absolute: windsurfBackupDir,
|
|
151
|
+
display: this._toDisplayPath(homeDir, windsurfBackupDir),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
count: this._getBackupCount(
|
|
155
|
+
cursorBackupDir,
|
|
156
|
+
windsurfBackupDir,
|
|
157
|
+
cursorConfigPath,
|
|
158
|
+
windsurfConfigPath
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Defaults } from '../constants/Defaults.js';
|
|
2
|
+
import { StatusCodeRanges } from '../constants/StatusCodes.js';
|
|
3
|
+
/**
|
|
4
|
+
* Service for statistics-related business logic
|
|
5
|
+
* HTTP-agnostic: accepts models, returns models
|
|
6
|
+
*/
|
|
7
|
+
import { RequestFilters } from '../models/RequestFilters.js';
|
|
8
|
+
|
|
9
|
+
export class StatisticsService {
|
|
10
|
+
constructor(statisticsRepository, packetRepository, conversationRepository) {
|
|
11
|
+
this.statisticsRepository = statisticsRepository;
|
|
12
|
+
this.packetRepository = packetRepository;
|
|
13
|
+
this.conversationRepository = conversationRepository;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get statistics with filters
|
|
18
|
+
* @param {RequestFilters} filters - Typed filter model
|
|
19
|
+
* @returns {Object} Statistics object
|
|
20
|
+
*/
|
|
21
|
+
getStatistics(filters) {
|
|
22
|
+
// Get all filtered requests for accurate statistics
|
|
23
|
+
const statsFilters = new RequestFilters({
|
|
24
|
+
...filters,
|
|
25
|
+
limit: Defaults.STATISTICS_LIMIT,
|
|
26
|
+
offset: Defaults.DEFAULT_OFFSET,
|
|
27
|
+
});
|
|
28
|
+
const repoFilters = statsFilters.toRepositoryFilters();
|
|
29
|
+
const allRequests = this.packetRepository.queryRequests(repoFilters);
|
|
30
|
+
|
|
31
|
+
// Calculate statistics from filtered requests
|
|
32
|
+
const totalPackets = allRequests.length;
|
|
33
|
+
const totalRequests = allRequests.filter((r) => r.direction === 'request').length;
|
|
34
|
+
const totalResponses = allRequests.filter((r) => r.direction === 'response').length;
|
|
35
|
+
const totalErrors = allRequests.filter((r) => {
|
|
36
|
+
if (r.direction === 'response') {
|
|
37
|
+
const statusCode = r.status_code || r.status;
|
|
38
|
+
return (
|
|
39
|
+
statusCode >= StatusCodeRanges.CLIENT_ERROR_MIN ||
|
|
40
|
+
(r.body_json && typeof r.body_json === 'object' && r.body_json.error)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}).length;
|
|
45
|
+
|
|
46
|
+
// Get unique sessions
|
|
47
|
+
const uniqueSessions = new Set();
|
|
48
|
+
for (const r of allRequests) {
|
|
49
|
+
if (r.session_id) {
|
|
50
|
+
uniqueSessions.add(r.session_id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stats = {
|
|
55
|
+
total_packets: totalPackets,
|
|
56
|
+
total_requests: totalRequests,
|
|
57
|
+
total_responses: totalResponses,
|
|
58
|
+
total_errors: totalErrors,
|
|
59
|
+
unique_sessions: uniqueSessions.size,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return stats;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getWorkingDirectory, prepareAppDataSpaces } from '#core/configs/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Service for Smart Scan token management
|
|
7
|
+
* Handles reading and writing Smart Scan API tokens
|
|
8
|
+
*/
|
|
9
|
+
export class TokenService {
|
|
10
|
+
constructor(logger) {
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.tokenFileName = 'smart-scan-token.json';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get token file path
|
|
17
|
+
*/
|
|
18
|
+
getTokenPath() {
|
|
19
|
+
return join(getWorkingDirectory(), this.tokenFileName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read Smart Scan token
|
|
24
|
+
*/
|
|
25
|
+
readToken() {
|
|
26
|
+
try {
|
|
27
|
+
const tokenPath = this.getTokenPath();
|
|
28
|
+
if (existsSync(tokenPath)) {
|
|
29
|
+
const content = readFileSync(tokenPath, 'utf8');
|
|
30
|
+
const data = JSON.parse(content);
|
|
31
|
+
return data.token || null;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
this.logger?.error({ error: error.message }, 'Error reading Smart Scan token');
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Write Smart Scan token
|
|
42
|
+
*/
|
|
43
|
+
writeToken(token) {
|
|
44
|
+
try {
|
|
45
|
+
const tokenPath = this.getTokenPath();
|
|
46
|
+
prepareAppDataSpaces();
|
|
47
|
+
|
|
48
|
+
const data = {
|
|
49
|
+
token: token || null,
|
|
50
|
+
updatedAt: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
writeFileSync(tokenPath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.logger?.error({ error: error.message }, 'Error writing Smart Scan token');
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get token metadata
|
|
63
|
+
*/
|
|
64
|
+
getTokenMetadata() {
|
|
65
|
+
try {
|
|
66
|
+
const tokenPath = this.getTokenPath();
|
|
67
|
+
if (existsSync(tokenPath)) {
|
|
68
|
+
const content = readFileSync(tokenPath, 'utf8');
|
|
69
|
+
const data = JSON.parse(content);
|
|
70
|
+
const stats = statSync(tokenPath);
|
|
71
|
+
return {
|
|
72
|
+
token: data.token || null,
|
|
73
|
+
updatedAt: data.updatedAt || stats.mtime.toISOString(),
|
|
74
|
+
path: tokenPath,
|
|
75
|
+
exists: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
token: null,
|
|
80
|
+
updatedAt: null,
|
|
81
|
+
path: tokenPath,
|
|
82
|
+
exists: false,
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.logger?.error({ error: error.message }, 'Error reading token metadata');
|
|
86
|
+
return {
|
|
87
|
+
token: null,
|
|
88
|
+
updatedAt: null,
|
|
89
|
+
path: this.getTokenPath(),
|
|
90
|
+
exists: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service layer exports
|
|
3
|
+
* All business logic should go through services
|
|
4
|
+
*/
|
|
5
|
+
export { RequestService } from './RequestService.js';
|
|
6
|
+
export { SessionService } from './SessionService.js';
|
|
7
|
+
export { ConversationService } from './ConversationService.js';
|
|
8
|
+
export { StatisticsService } from './StatisticsService.js';
|
|
9
|
+
export { AuditService } from './AuditService.js';
|
|
10
|
+
export { ConfigService } from './ConfigService.js';
|
|
11
|
+
export { ConfigFileService } from './ConfigFileService.js';
|
|
12
|
+
export { ConfigTransformService } from './ConfigTransformService.js';
|
|
13
|
+
export { ConfigDetectionService } from './ConfigDetectionService.js';
|
|
14
|
+
export { ServerManagementService } from './ServerManagementService.js';
|
|
15
|
+
export { BackupService } from './BackupService.js';
|
|
16
|
+
export { LogService } from './LogService.js';
|
|
17
|
+
export { ScanCacheService } from './ScanCacheService.js';
|
|
18
|
+
export { ScanService } from './ScanService.js';
|
|
19
|
+
export { McpDiscoveryService } from './McpDiscoveryService.js';
|
|
20
|
+
export { McpClientService } from './McpClientService.js';
|
|
21
|
+
export { TokenService } from './TokenService.js';
|
|
22
|
+
export { SettingsService } from './SettingsService.js';
|
|
23
|
+
export { ConfigPatchingService } from './ConfigPatchingService.js';
|
|
24
|
+
export { ExportService } from './ExportService.js';
|
|
25
|
+
export * from './parsers/index.js';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { JsonConfigParser } from './JsonConfigParser.js';
|
|
3
|
+
import { LegacyJsonConfigParser } from './LegacyJsonConfigParser.js';
|
|
4
|
+
import { TomlConfigParser } from './TomlConfigParser.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Factory for creating appropriate config parsers based on file type
|
|
8
|
+
* Provides unified interface for all config format parsing
|
|
9
|
+
*/
|
|
10
|
+
export class ConfigParserFactory {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.tomlParser = new TomlConfigParser();
|
|
13
|
+
this.jsonParser = new JsonConfigParser();
|
|
14
|
+
this.legacyParser = new LegacyJsonConfigParser();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detect file format from path
|
|
19
|
+
* @param {string} filePath - File path
|
|
20
|
+
* @returns {string} Format type: 'toml', 'json', or null
|
|
21
|
+
*/
|
|
22
|
+
detectFormat(filePath) {
|
|
23
|
+
if (!filePath) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
27
|
+
return ext === '.toml' ? 'toml' : ext === '.json' ? 'json' : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get appropriate parser for file format
|
|
32
|
+
* @param {string} filePath - File path
|
|
33
|
+
* @returns {TomlConfigParser|JsonConfigParser|LegacyJsonConfigParser} Parser instance
|
|
34
|
+
*/
|
|
35
|
+
getParser(filePath) {
|
|
36
|
+
const format = this.detectFormat(filePath);
|
|
37
|
+
if (format === 'toml') {
|
|
38
|
+
return this.tomlParser;
|
|
39
|
+
}
|
|
40
|
+
return this.jsonParser;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse config content and detect format
|
|
45
|
+
* @param {string} content - Config file content
|
|
46
|
+
* @param {string} filePath - File path (optional, for format detection)
|
|
47
|
+
* @returns {Object|null} Parsed config or null on error
|
|
48
|
+
*/
|
|
49
|
+
parse(content, filePath = null) {
|
|
50
|
+
const format = filePath ? this.detectFormat(filePath) : null;
|
|
51
|
+
|
|
52
|
+
if (format === 'toml') {
|
|
53
|
+
return this.tomlParser.parse(content);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return this.jsonParser.parse(content);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Normalize config to internal format
|
|
61
|
+
* Handles all formats: TOML (Codex), JSON (standard), JSON (legacy)
|
|
62
|
+
* @param {Object} config - Parsed config object
|
|
63
|
+
* @param {string} filePath - Original file path (optional)
|
|
64
|
+
* @returns {Object|null} Normalized config in internal format
|
|
65
|
+
*/
|
|
66
|
+
normalizeToInternalFormat(config, _filePath = null) {
|
|
67
|
+
if (!config || typeof config !== 'object') {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Try TOML format first (Codex)
|
|
72
|
+
if (this.tomlParser.isCodexFormat(config)) {
|
|
73
|
+
return this.tomlParser.convertToMcpSharkFormat(config);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try standard JSON format
|
|
77
|
+
if (this.jsonParser.isMcpServersFormat(config)) {
|
|
78
|
+
return this.jsonParser.normalizeToInternalFormat(config);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Try legacy format
|
|
82
|
+
if (this.legacyParser.isLegacyFormat(config)) {
|
|
83
|
+
return this.legacyParser.convertToInternalFormat(config);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If config has both mcpServers and servers, prefer mcpServers
|
|
87
|
+
if (config.mcpServers && typeof config.mcpServers === 'object') {
|
|
88
|
+
return this.jsonParser.normalizeToInternalFormat(config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If config has servers, treat as legacy
|
|
92
|
+
if (config.servers && typeof config.servers === 'object') {
|
|
93
|
+
return this.legacyParser.convertToInternalFormat(config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parse and normalize config in one step
|
|
101
|
+
* @param {string} content - Config file content
|
|
102
|
+
* @param {string} filePath - File path
|
|
103
|
+
* @returns {Object|null} Normalized config in internal format or null
|
|
104
|
+
*/
|
|
105
|
+
parseAndNormalize(content, filePath) {
|
|
106
|
+
const parsed = this.parse(content, filePath);
|
|
107
|
+
if (!parsed) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return this.normalizeToInternalFormat(parsed, filePath);
|
|
112
|
+
}
|
|
113
|
+
}
|