@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,210 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { getMcpConfigPath } from '#core/configs/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service for configuration file operations
|
|
8
|
+
* Handles file I/O, path resolution, and backup/restore
|
|
9
|
+
*/
|
|
10
|
+
export class ConfigFileService {
|
|
11
|
+
constructor(logger, configDetectionService, configParserFactory) {
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
this.originalConfigData = null;
|
|
14
|
+
this.detectionService = configDetectionService;
|
|
15
|
+
this.parserFactory = configParserFactory;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve file path (expand ~ to home directory)
|
|
20
|
+
*/
|
|
21
|
+
resolveFilePath(filePath) {
|
|
22
|
+
if (!filePath) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return filePath.startsWith('~') ? path.join(homedir(), filePath.slice(1)) : filePath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read file content from path or use provided content
|
|
30
|
+
*/
|
|
31
|
+
resolveFileData(filePath, fileContent) {
|
|
32
|
+
if (fileContent) {
|
|
33
|
+
const resolvedFilePath = filePath ? this.resolveFilePath(filePath) : null;
|
|
34
|
+
return { content: fileContent, resolvedFilePath };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!filePath) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resolvedFilePath = this.resolveFilePath(filePath);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(resolvedFilePath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
content: fs.readFileSync(resolvedFilePath, 'utf-8'),
|
|
49
|
+
resolvedFilePath,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse JSON or TOML content safely using appropriate parser
|
|
55
|
+
*/
|
|
56
|
+
parseJsonConfig(content, filePath = null) {
|
|
57
|
+
try {
|
|
58
|
+
const config = this.parserFactory.parse(content, filePath);
|
|
59
|
+
return { config, error: null };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return { config: null, error };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Try to parse JSON or TOML, return null on error
|
|
67
|
+
*/
|
|
68
|
+
tryParseJson(content, filePath = null) {
|
|
69
|
+
return this.parserFactory.parse(content, filePath);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read config file content
|
|
74
|
+
*/
|
|
75
|
+
readConfigFile(filePath) {
|
|
76
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return fs.readFileSync(resolvedPath, 'utf-8');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Write config file
|
|
87
|
+
*/
|
|
88
|
+
writeConfigFile(filePath, content) {
|
|
89
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
90
|
+
fs.writeFileSync(resolvedPath, content);
|
|
91
|
+
this.logger?.info({ path: resolvedPath }, 'Wrote config file');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if file exists
|
|
96
|
+
*/
|
|
97
|
+
fileExists(filePath) {
|
|
98
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
99
|
+
return fs.existsSync(resolvedPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get file type from path
|
|
104
|
+
*/
|
|
105
|
+
getFileType(filePath) {
|
|
106
|
+
if (!filePath) {
|
|
107
|
+
return 'JSON';
|
|
108
|
+
}
|
|
109
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
110
|
+
return ext === '.toml' ? 'TOML' : 'JSON';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get display path (replace home directory with ~)
|
|
115
|
+
*/
|
|
116
|
+
getDisplayPath(filePath) {
|
|
117
|
+
if (!filePath) {
|
|
118
|
+
return filePath;
|
|
119
|
+
}
|
|
120
|
+
const homeDir = homedir();
|
|
121
|
+
return filePath.replace(homeDir, '~');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get home directory
|
|
126
|
+
*/
|
|
127
|
+
getHomeDir() {
|
|
128
|
+
return homedir();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Detect config files on the system
|
|
133
|
+
*/
|
|
134
|
+
detectConfigFiles() {
|
|
135
|
+
return this.detectionService.detectConfigFiles();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get MCP config path
|
|
140
|
+
*/
|
|
141
|
+
getMcpConfigPath() {
|
|
142
|
+
return getMcpConfigPath();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Read MCP config file (supports both JSON and TOML)
|
|
147
|
+
* Uses appropriate parser based on file extension
|
|
148
|
+
*/
|
|
149
|
+
readMcpConfig() {
|
|
150
|
+
const configPath = this.getMcpConfigPath();
|
|
151
|
+
if (!fs.existsSync(configPath)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
156
|
+
return this.parserFactory.parse(content, configPath);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get servers from MCP config
|
|
161
|
+
*/
|
|
162
|
+
getServersFromConfig() {
|
|
163
|
+
const config = this.readMcpConfig();
|
|
164
|
+
if (!config) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return config.servers ? Object.keys(config.servers) : [];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Store original config for restoration
|
|
173
|
+
*/
|
|
174
|
+
storeOriginalConfig(filePath, originalContent, backupPath) {
|
|
175
|
+
this.originalConfigData = { filePath, originalContent, backupPath };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Restore original config
|
|
180
|
+
*/
|
|
181
|
+
restoreOriginalConfig() {
|
|
182
|
+
if (this.originalConfigData?.filePath) {
|
|
183
|
+
try {
|
|
184
|
+
if (fs.existsSync(this.originalConfigData.filePath)) {
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
this.originalConfigData.filePath,
|
|
187
|
+
this.originalConfigData.originalContent
|
|
188
|
+
);
|
|
189
|
+
this.logger?.info({ path: this.originalConfigData.filePath }, 'Restored original config');
|
|
190
|
+
this.originalConfigData = null;
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
this.originalConfigData = null;
|
|
194
|
+
return false;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.logger?.error({ error: error.message }, 'Failed to restore original config');
|
|
197
|
+
this.originalConfigData = null;
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clear original config reference
|
|
206
|
+
*/
|
|
207
|
+
clearOriginalConfig() {
|
|
208
|
+
this.originalConfigData = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for handling config patching operations
|
|
3
|
+
* Handles checking if config is patched, restoring original, and repatching
|
|
4
|
+
*/
|
|
5
|
+
export class ConfigPatchingService {
|
|
6
|
+
/**
|
|
7
|
+
* @param {ConfigService} configService - Config service instance
|
|
8
|
+
* @param {BackupService} backupService - Backup service instance
|
|
9
|
+
* @param {object} logger - Logger instance
|
|
10
|
+
*/
|
|
11
|
+
constructor(configService, backupService, logger) {
|
|
12
|
+
this.configService = configService;
|
|
13
|
+
this.backupService = backupService;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Restore original config if file is already patched
|
|
19
|
+
* Should be called before processing setup to ensure original config is read
|
|
20
|
+
* @param {string} filePath - Path to config file
|
|
21
|
+
* @returns {{wasPatched: boolean, restored: boolean, warning?: string}}
|
|
22
|
+
*/
|
|
23
|
+
restoreIfPatched(filePath) {
|
|
24
|
+
const resolvedPath = this.configService.fileService.resolveFilePath(filePath);
|
|
25
|
+
const isPatched = this.configService.isFilePatched(resolvedPath);
|
|
26
|
+
|
|
27
|
+
if (!isPatched) {
|
|
28
|
+
return { wasPatched: false, restored: false };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Config is already patched - restore original first
|
|
32
|
+
this.logger?.warn(
|
|
33
|
+
{ filePath: resolvedPath },
|
|
34
|
+
'Config file is already patched, restoring original before processing'
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Try to restore from in-memory backup first
|
|
38
|
+
const inMemoryRestored = this.configService.restoreOriginalConfig();
|
|
39
|
+
|
|
40
|
+
// If that didn't work, use BackupService to find and restore from backup file
|
|
41
|
+
const restoreResult = inMemoryRestored
|
|
42
|
+
? { success: true }
|
|
43
|
+
: this._restoreFromBackupFile(resolvedPath);
|
|
44
|
+
|
|
45
|
+
const restored = restoreResult.success;
|
|
46
|
+
const warning = restored
|
|
47
|
+
? 'Config was already patched. Restored original before processing.'
|
|
48
|
+
: 'Config was patched but could not restore original. Proceeding anyway.';
|
|
49
|
+
|
|
50
|
+
if (!restored) {
|
|
51
|
+
this.logger?.warn(
|
|
52
|
+
{ filePath: resolvedPath },
|
|
53
|
+
'Could not restore original config - may cause issues'
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
this.logger?.info({ filePath: resolvedPath }, 'Restored original config successfully');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { wasPatched: true, restored, warning };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Patch config file, restoring original first if already patched
|
|
64
|
+
* @param {string} filePath - Path to config file
|
|
65
|
+
* @param {object} patchedConfig - Config object to write (already patched)
|
|
66
|
+
* @returns {{wasPatched: boolean, restored: boolean, warning?: string}}
|
|
67
|
+
*/
|
|
68
|
+
patchConfigFile(filePath, patchedConfig) {
|
|
69
|
+
const resolvedPath = this.configService.fileService.resolveFilePath(filePath);
|
|
70
|
+
const isPatched = this.configService.isFilePatched(resolvedPath);
|
|
71
|
+
|
|
72
|
+
if (!isPatched) {
|
|
73
|
+
// Not patched, just write the patched config
|
|
74
|
+
this.configService.writeConfigAsJson(resolvedPath, patchedConfig);
|
|
75
|
+
return { wasPatched: false, restored: false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Config is already patched - restore original first
|
|
79
|
+
this.logger?.warn(
|
|
80
|
+
{ filePath: resolvedPath },
|
|
81
|
+
'Config file is already patched, restoring original before repatching'
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Try to restore from in-memory backup first
|
|
85
|
+
const inMemoryRestored = this.configService.restoreOriginalConfig();
|
|
86
|
+
|
|
87
|
+
// If that didn't work, use BackupService to find and restore from backup file
|
|
88
|
+
const restoreResult = inMemoryRestored
|
|
89
|
+
? { success: true }
|
|
90
|
+
: this._restoreFromBackupFile(resolvedPath);
|
|
91
|
+
|
|
92
|
+
const restored = restoreResult.success;
|
|
93
|
+
const warning = restored
|
|
94
|
+
? 'Config was already patched. Restored original and repatched.'
|
|
95
|
+
: 'Config was patched but could not restore original. Proceeding anyway.';
|
|
96
|
+
|
|
97
|
+
if (!restored) {
|
|
98
|
+
this.logger?.warn(
|
|
99
|
+
{ filePath: resolvedPath },
|
|
100
|
+
'Could not restore original config - may cause issues'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Now write the patched config
|
|
105
|
+
this.configService.writeConfigAsJson(resolvedPath, patchedConfig);
|
|
106
|
+
|
|
107
|
+
return { wasPatched: true, restored, warning };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Restore config from backup file using BackupService
|
|
112
|
+
* @private
|
|
113
|
+
* @param {string} filePath - Path to config file to restore
|
|
114
|
+
* @returns {{success: boolean}}
|
|
115
|
+
*/
|
|
116
|
+
_restoreFromBackupFile(filePath) {
|
|
117
|
+
const backups = this.backupService.listBackups();
|
|
118
|
+
const matchingBackup = backups.find((backup) => backup.originalPath === filePath);
|
|
119
|
+
|
|
120
|
+
if (!matchingBackup) {
|
|
121
|
+
return { success: false };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.logger?.info(
|
|
125
|
+
{ backupPath: matchingBackup.backupPath },
|
|
126
|
+
'Found backup file, restoring from backup'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const restoreResult = this.backupService.restoreBackup(
|
|
130
|
+
matchingBackup.backupPath,
|
|
131
|
+
filePath,
|
|
132
|
+
false
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return { success: restoreResult.success };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for configuration file operations
|
|
3
|
+
* Composes ConfigFileService and ConfigTransformService
|
|
4
|
+
* Uses dependency injection for all dependencies
|
|
5
|
+
*/
|
|
6
|
+
export class ConfigService {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} logger - Logger instance
|
|
9
|
+
* @param {ConfigFileService} configFileService - File service instance
|
|
10
|
+
* @param {ConfigTransformService} configTransformService - Transform service instance
|
|
11
|
+
* @param {ConfigDetectionService} configDetectionService - Detection service instance
|
|
12
|
+
*/
|
|
13
|
+
constructor(logger, configFileService, configTransformService, configDetectionService) {
|
|
14
|
+
this.fileService = configFileService;
|
|
15
|
+
this.transformService = configTransformService;
|
|
16
|
+
this.detectionService = configDetectionService;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
resolveFilePath(filePath) {
|
|
21
|
+
return this.fileService.resolveFilePath(filePath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
resolveFileData(filePath, fileContent) {
|
|
25
|
+
return this.fileService.resolveFileData(filePath, fileContent);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
parseJsonConfig(content) {
|
|
29
|
+
return this.fileService.parseJsonConfig(content);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
tryParseJson(content) {
|
|
33
|
+
return this.fileService.tryParseJson(content);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
readConfigFile(filePath) {
|
|
37
|
+
return this.fileService.readConfigFile(filePath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
writeConfigFile(filePath, content) {
|
|
41
|
+
return this.fileService.writeConfigFile(filePath, content);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fileExists(filePath) {
|
|
45
|
+
return this.fileService.fileExists(filePath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
convertMcpServersToServers(config) {
|
|
49
|
+
return this.transformService.convertMcpServersToServers(config);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
extractServices(config) {
|
|
53
|
+
return this.transformService.extractServices(config);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
filterServers(config, selectedServices) {
|
|
57
|
+
return this.transformService.filterServers(config, selectedServices);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
detectConfigFiles() {
|
|
61
|
+
return this.fileService.detectConfigFiles();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getMcpConfigPath() {
|
|
65
|
+
return this.fileService.getMcpConfigPath();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
readMcpConfig() {
|
|
69
|
+
return this.fileService.readMcpConfig();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getServersFromConfig() {
|
|
73
|
+
return this.fileService.getServersFromConfig();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
updateConfigForMcpShark(originalConfig) {
|
|
77
|
+
return this.transformService.updateConfigForMcpShark(originalConfig);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getSelectedServiceNames(originalConfig, selectedServices) {
|
|
81
|
+
return this.transformService.getSelectedServiceNames(originalConfig, selectedServices);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
storeOriginalConfig(filePath, originalContent, backupPath) {
|
|
85
|
+
return this.fileService.storeOriginalConfig(filePath, originalContent, backupPath);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
restoreOriginalConfig() {
|
|
89
|
+
return this.fileService.restoreOriginalConfig();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
clearOriginalConfig() {
|
|
93
|
+
return this.fileService.clearOriginalConfig();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getFileType(filePath) {
|
|
97
|
+
return this.fileService.getFileType(filePath);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getDisplayPath(filePath) {
|
|
101
|
+
return this.fileService.getDisplayPath(filePath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getHomeDir() {
|
|
105
|
+
return this.fileService.getHomeDir();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extract services from file (handles full flow)
|
|
110
|
+
*/
|
|
111
|
+
extractServicesFromFile(filePath, fileContent) {
|
|
112
|
+
const fileData = this.fileService.resolveFileData(filePath, fileContent);
|
|
113
|
+
if (!fileData) {
|
|
114
|
+
const resolvedFilePath = filePath ? this.fileService.resolveFilePath(filePath) : null;
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: 'File not found',
|
|
118
|
+
path: resolvedFilePath,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const parseResult = this.fileService.parseJsonConfig(
|
|
123
|
+
fileData.content,
|
|
124
|
+
fileData.resolvedFilePath
|
|
125
|
+
);
|
|
126
|
+
if (!parseResult.config) {
|
|
127
|
+
const fileType = this.fileService.getFileType(fileData.resolvedFilePath);
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: `Invalid ${fileType} file`,
|
|
131
|
+
details: parseResult.error ? parseResult.error.message : `Failed to parse ${fileType}`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const services = this.transformService.extractServices(parseResult.config);
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
services,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Read config file with metadata
|
|
144
|
+
*/
|
|
145
|
+
readConfigFileWithMetadata(filePath) {
|
|
146
|
+
const resolvedPath = this.fileService.resolveFilePath(filePath);
|
|
147
|
+
|
|
148
|
+
if (!this.fileService.fileExists(resolvedPath)) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: 'File not found',
|
|
152
|
+
path: resolvedPath,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const content = this.fileService.readConfigFile(resolvedPath);
|
|
157
|
+
const parsed = this.fileService.tryParseJson(content, resolvedPath);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
filePath: resolvedPath,
|
|
162
|
+
displayPath: this.fileService.getDisplayPath(resolvedPath),
|
|
163
|
+
content,
|
|
164
|
+
parsed,
|
|
165
|
+
exists: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Process setup: parse, convert, filter, and prepare config
|
|
171
|
+
*/
|
|
172
|
+
processSetup(filePath, fileContent, selectedServices) {
|
|
173
|
+
const fileData = this.fileService.resolveFileData(filePath, fileContent);
|
|
174
|
+
if (!fileData) {
|
|
175
|
+
const resolvedFilePath = filePath ? this.fileService.resolveFilePath(filePath) : null;
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
error: 'File not found',
|
|
179
|
+
path: resolvedFilePath,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const parseResult = this.fileService.parseJsonConfig(
|
|
184
|
+
fileData.content,
|
|
185
|
+
fileData.resolvedFilePath
|
|
186
|
+
);
|
|
187
|
+
if (!parseResult.config) {
|
|
188
|
+
const fileType = this.fileService.getFileType(fileData.resolvedFilePath);
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: `Invalid ${fileType} file`,
|
|
192
|
+
details: parseResult.error ? parseResult.error.message : `Failed to parse ${fileType}`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const originalConfig = parseResult.config;
|
|
197
|
+
const baseConvertedConfig = this.transformService.convertMcpServersToServers(originalConfig);
|
|
198
|
+
|
|
199
|
+
const convertedConfig =
|
|
200
|
+
selectedServices && Array.isArray(selectedServices) && selectedServices.length > 0
|
|
201
|
+
? this.transformService.filterServers(baseConvertedConfig, selectedServices)
|
|
202
|
+
: baseConvertedConfig;
|
|
203
|
+
|
|
204
|
+
if (Object.keys(convertedConfig.servers || {}).length === 0) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: 'No servers found in config',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const updatedConfig = this.transformService.updateConfigForMcpShark(originalConfig);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
fileData,
|
|
216
|
+
originalConfig,
|
|
217
|
+
convertedConfig,
|
|
218
|
+
updatedConfig,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Write config as JSON string
|
|
224
|
+
* @param {string} filePath - Path to config file
|
|
225
|
+
* @param {object} config - Config object to write
|
|
226
|
+
*/
|
|
227
|
+
writeConfigAsJson(filePath, config) {
|
|
228
|
+
const jsonContent = JSON.stringify(config, null, 2);
|
|
229
|
+
this.fileService.writeConfigFile(filePath, jsonContent);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if config file is patched by mcp-shark
|
|
234
|
+
*/
|
|
235
|
+
isConfigPatched(config) {
|
|
236
|
+
return this.transformService.isConfigPatched(config);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check if a file path contains a patched config
|
|
241
|
+
*/
|
|
242
|
+
isFilePatched(filePath) {
|
|
243
|
+
if (!this.fileService.fileExists(filePath)) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const content = this.fileService.readConfigFile(filePath);
|
|
247
|
+
const parseResult = this.fileService.parseJsonConfig(content, filePath);
|
|
248
|
+
return parseResult.config ? this.isConfigPatched(parseResult.config) : false;
|
|
249
|
+
}
|
|
250
|
+
}
|