mcp-config-manager 1.0.2
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 +235 -0
- package/docs/images/kanban-view.png +0 -0
- package/docs/images/list-view.png +0 -0
- package/docs/images/remote-mcp-modal.png +0 -0
- package/docs/images/server-view.png +0 -0
- package/package.json +52 -0
- package/public/index.html +325 -0
- package/public/js/api.js +108 -0
- package/public/js/clientView.js +211 -0
- package/public/js/kanbanView.js +224 -0
- package/public/js/main.js +126 -0
- package/public/js/modals.js +822 -0
- package/public/js/serverView.js +299 -0
- package/public/js/utils.js +185 -0
- package/public/style.css +640 -0
- package/src/cli.js +394 -0
- package/src/clients.js +113 -0
- package/src/config-manager.js +520 -0
- package/src/server.js +214 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
import { CLIENTS as PROD_CLIENTS } from './clients.js';
|
|
6
|
+
import { MOCK_CLIENTS, MOCK_GLOBAL_SERVERS_PATH } from '../test/mock-clients.js';
|
|
7
|
+
|
|
8
|
+
const USE_MOCK_CLIENTS = process.env.MCP_USE_MOCK_CLIENTS === 'true';
|
|
9
|
+
const CLIENTS = USE_MOCK_CLIENTS ? MOCK_CLIENTS : PROD_CLIENTS;
|
|
10
|
+
const GLOBAL_SERVERS_PATH = USE_MOCK_CLIENTS ? MOCK_GLOBAL_SERVERS_PATH : path.join(os.homedir(), '.mcp-global-servers.json');
|
|
11
|
+
|
|
12
|
+
export class MCPConfigManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.platform = os.platform();
|
|
15
|
+
this.availableClients = {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async readGlobalServers() {
|
|
19
|
+
try {
|
|
20
|
+
const content = await fs.readFile(GLOBAL_SERVERS_PATH, 'utf-8');
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.code !== 'ENOENT') {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
// If file doesn't exist, return empty object
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async writeGlobalServers(globalServers) {
|
|
32
|
+
await fs.writeFile(GLOBAL_SERVERS_PATH, JSON.stringify(globalServers, null, 2));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async addGlobalServer(serverName, serverConfig) {
|
|
36
|
+
const globalServers = await this.readGlobalServers();
|
|
37
|
+
globalServers[serverName] = serverConfig;
|
|
38
|
+
await this.writeGlobalServers(globalServers);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async removeGlobalServer(serverName) {
|
|
42
|
+
const globalServers = await this.readGlobalServers();
|
|
43
|
+
delete globalServers[serverName];
|
|
44
|
+
await this.writeGlobalServers(globalServers);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async updateGlobalServerEnv(serverName, envKey, envValue) {
|
|
48
|
+
const globalServers = await this.readGlobalServers();
|
|
49
|
+
if (!globalServers[serverName]) {
|
|
50
|
+
throw new Error(`Global server ${serverName} not found`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!globalServers[serverName].env) {
|
|
54
|
+
globalServers[serverName].env = {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (envValue === null || envValue === undefined) {
|
|
58
|
+
delete globalServers[serverName].env[envKey];
|
|
59
|
+
} else {
|
|
60
|
+
globalServers[serverName].env[envKey] = envValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await this.writeGlobalServers(globalServers);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getAllServers() {
|
|
67
|
+
return this.readGlobalServers();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Helper to generate a consistent hash for a server config
|
|
71
|
+
getServerConfigHash(config) {
|
|
72
|
+
// Exclude env for now, or handle it specially if exact env matching is needed
|
|
73
|
+
const { env, ...rest } = config;
|
|
74
|
+
return JSON.stringify(rest);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getServersInClients() {
|
|
78
|
+
const allServers = {}; // serverName: { clients: [{id, name, configPath}], global: boolean, config: {}, configHash: string }
|
|
79
|
+
const globalServers = await this.readGlobalServers();
|
|
80
|
+
|
|
81
|
+
// Add global servers first
|
|
82
|
+
for (const [serverName, serverConfig] of Object.entries(globalServers)) {
|
|
83
|
+
allServers[serverName] = {
|
|
84
|
+
clients: [],
|
|
85
|
+
global: true,
|
|
86
|
+
config: serverConfig,
|
|
87
|
+
configHash: this.getServerConfigHash(serverConfig)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const availableClients = await this.getAvailableClients();
|
|
92
|
+
|
|
93
|
+
for (const [clientId, clientInfo] of Object.entries(availableClients)) {
|
|
94
|
+
try {
|
|
95
|
+
const clientConfig = await this.readConfig(clientId);
|
|
96
|
+
|
|
97
|
+
for (const [serverName, serverConfig] of Object.entries(clientConfig.servers)) {
|
|
98
|
+
if (!allServers[serverName]) {
|
|
99
|
+
allServers[serverName] = {
|
|
100
|
+
clients: [],
|
|
101
|
+
global: false, // Will be true if it's also in globalServers
|
|
102
|
+
config: serverConfig || {}, // Ensure config is an object
|
|
103
|
+
configHash: this.getServerConfigHash(serverConfig || {})
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
allServers[serverName].clients.push({
|
|
107
|
+
id: clientId,
|
|
108
|
+
name: clientInfo.name,
|
|
109
|
+
configPath: this.getConfigPath(clientId)
|
|
110
|
+
});
|
|
111
|
+
// If a server exists globally and in a client, mark it as global
|
|
112
|
+
if (globalServers[serverName]) {
|
|
113
|
+
allServers[serverName].global = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(`Error in getServersInClients for client ${clientId}:`, error);
|
|
118
|
+
// Client config might not exist, or other read error, skip
|
|
119
|
+
console.warn(`Could not read config for client ${clientId}: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return allServers;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async detectClients() {
|
|
126
|
+
const detectedClients = {};
|
|
127
|
+
for (const [id, client] of Object.entries(CLIENTS)) {
|
|
128
|
+
const configPath = client.configPaths[os.platform()];
|
|
129
|
+
const absoluteConfigPath = path.isAbsolute(configPath) ? configPath : path.join(process.cwd(), configPath);
|
|
130
|
+
try {
|
|
131
|
+
await fs.access(absoluteConfigPath, fs.constants.F_OK);
|
|
132
|
+
detectedClients[id] = client;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// File does not exist or is not accessible, skip this client
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.availableClients = detectedClients;
|
|
138
|
+
return detectedClients;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getAvailableClients() {
|
|
142
|
+
if (Object.keys(this.availableClients).length === 0) {
|
|
143
|
+
await this.detectClients();
|
|
144
|
+
}
|
|
145
|
+
return this.availableClients;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async listClients() {
|
|
149
|
+
const clientsWithConfigs = [];
|
|
150
|
+
const availableClients = await this.getAvailableClients();
|
|
151
|
+
|
|
152
|
+
for (const [key, client] of Object.entries(availableClients)) {
|
|
153
|
+
try {
|
|
154
|
+
const config = await this.readConfig(key);
|
|
155
|
+
const serverCount = Object.keys(config.servers).length;
|
|
156
|
+
clientsWithConfigs.push({
|
|
157
|
+
id: key,
|
|
158
|
+
name: client.name,
|
|
159
|
+
configPath: this.getConfigPath(key),
|
|
160
|
+
serverCount,
|
|
161
|
+
exists: true
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`Error processing client ${key}:`, error.message);
|
|
165
|
+
clientsWithConfigs.push({
|
|
166
|
+
id: key,
|
|
167
|
+
name: client.name,
|
|
168
|
+
configPath: this.getConfigPath(key),
|
|
169
|
+
serverCount: 0,
|
|
170
|
+
exists: false
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return clientsWithConfigs;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getConfigPath(client) {
|
|
179
|
+
const clientConfig = CLIENTS[client];
|
|
180
|
+
if (!clientConfig) {
|
|
181
|
+
throw new Error(`Unknown client: ${client}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const platformKey = this.platform === 'darwin' ? 'darwin' :
|
|
185
|
+
this.platform === 'win32' ? 'win32' : 'linux';
|
|
186
|
+
|
|
187
|
+
const configPath = clientConfig.configPaths[platformKey];
|
|
188
|
+
return configPath;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async readConfig(client) {
|
|
192
|
+
const configPath = this.getConfigPath(client);
|
|
193
|
+
let clientConfig = { servers: {} };
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
197
|
+
const parsedContent = JSON.parse(content);
|
|
198
|
+
clientConfig = this.normalizeConfig(parsedContent, CLIENTS[client].format);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error.code !== 'ENOENT') {
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
// If file doesn't exist, clientConfig remains { servers: {} }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const globalServers = await this.readGlobalServers();
|
|
207
|
+
|
|
208
|
+
// Merge global servers into clientConfig, client-specific overrides global
|
|
209
|
+
const mergedServers = { ...globalServers };
|
|
210
|
+
for (const [serverName, serverDetails] of Object.entries(clientConfig.servers)) {
|
|
211
|
+
mergedServers[serverName] = { ...mergedServers[serverName], ...serverDetails };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { servers: mergedServers };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
normalizeConfig(config, format) {
|
|
218
|
+
if (format === 'mcpServers') {
|
|
219
|
+
return { servers: config.mcpServers || {} };
|
|
220
|
+
} else if (format === 'mcp.servers') {
|
|
221
|
+
return { servers: config.mcp?.servers || {} };
|
|
222
|
+
}
|
|
223
|
+
return { servers: {} };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
denormalizeConfig(normalizedConfig, format, originalConfig = {}) {
|
|
227
|
+
if (format === 'mcpServers') {
|
|
228
|
+
return { ...originalConfig, mcpServers: normalizedConfig.servers };
|
|
229
|
+
} else if (format === 'mcp.servers') {
|
|
230
|
+
return {
|
|
231
|
+
...originalConfig,
|
|
232
|
+
mcp: {
|
|
233
|
+
...(originalConfig.mcp || {}),
|
|
234
|
+
servers: normalizedConfig.servers
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return originalConfig;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async writeConfig(client, config) {
|
|
242
|
+
const configPath = this.getConfigPath(client);
|
|
243
|
+
const clientConfig = CLIENTS[client];
|
|
244
|
+
|
|
245
|
+
let originalConfig = {};
|
|
246
|
+
try {
|
|
247
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
248
|
+
originalConfig = JSON.parse(content);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// File doesn't exist, that's OK
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const finalConfig = this.denormalizeConfig(config, clientConfig.format, originalConfig);
|
|
254
|
+
|
|
255
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
256
|
+
await fs.writeFile(configPath, JSON.stringify(finalConfig, null, 2));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
async addServer(client, serverName, serverConfig) {
|
|
262
|
+
const config = await this.readConfig(client);
|
|
263
|
+
config.servers[serverName] = serverConfig;
|
|
264
|
+
await this.writeConfig(client, config);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async addServerToMultipleClients(serverName, serverConfig, clientIds) {
|
|
268
|
+
const results = [];
|
|
269
|
+
for (const clientId of clientIds) {
|
|
270
|
+
try {
|
|
271
|
+
await this.addServer(clientId, serverName, serverConfig);
|
|
272
|
+
results.push({ client: clientId, server: serverName, success: true });
|
|
273
|
+
} catch (error) {
|
|
274
|
+
results.push({ client: clientId, server: serverName, success: false, error: error.message });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return results;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async updateServerInMultipleClients(serverName, serverConfig, clientIds) {
|
|
281
|
+
const results = [];
|
|
282
|
+
for (const clientId of clientIds) {
|
|
283
|
+
try {
|
|
284
|
+
const config = await this.readConfig(clientId);
|
|
285
|
+
config.servers[serverName] = serverConfig;
|
|
286
|
+
await this.writeConfig(clientId, config);
|
|
287
|
+
results.push({ client: clientId, server: serverName, success: true });
|
|
288
|
+
} catch (error) {
|
|
289
|
+
results.push({ client: clientId, server: serverName, success: false, error: error.message });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async removeServer(client, serverName) {
|
|
296
|
+
const config = await this.readConfig(client);
|
|
297
|
+
delete config.servers[serverName];
|
|
298
|
+
await this.writeConfig(client, config);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async updateServerEnv(client, serverName, envKey, envValue) {
|
|
302
|
+
const config = await this.readConfig(client);
|
|
303
|
+
if (!config.servers[serverName]) {
|
|
304
|
+
throw new Error(`Server ${serverName} not found in ${client}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!config.servers[serverName].env) {
|
|
308
|
+
config.servers[serverName].env = {};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (envValue === null || envValue === undefined) {
|
|
312
|
+
delete config.servers[serverName].env[envKey];
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
config.servers[serverName].env[envKey] = envValue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await this.writeConfig(client, config);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async copyServer(fromClient, fromServerName, toClient, toServerName = null) {
|
|
322
|
+
const fromConfig = await this.readConfig(fromClient);
|
|
323
|
+
const serverConfig = fromConfig.servers[fromServerName];
|
|
324
|
+
|
|
325
|
+
if (!serverConfig) {
|
|
326
|
+
throw new Error(`Server ${fromServerName} not found in ${fromClient}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const targetName = toServerName || fromServerName;
|
|
330
|
+
await this.addServer(toClient, targetName, serverConfig);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async exportConfig(client, outputPath = null) {
|
|
334
|
+
const config = await this.readConfig(client);
|
|
335
|
+
const exportData = {
|
|
336
|
+
client,
|
|
337
|
+
timestamp: new Date().toISOString(),
|
|
338
|
+
servers: config.servers
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
if (outputPath) {
|
|
342
|
+
await fs.writeFile(outputPath, JSON.stringify(exportData, null, 2));
|
|
343
|
+
return outputPath;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
return exportData;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async exportServer(client, serverName, outputPath = null) {
|
|
351
|
+
const config = await this.readConfig(client);
|
|
352
|
+
const serverConfig = config.servers[serverName];
|
|
353
|
+
|
|
354
|
+
if (!serverConfig) {
|
|
355
|
+
throw new Error(`Server ${serverName} not found in ${client}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const exportData = {
|
|
359
|
+
client,
|
|
360
|
+
serverName,
|
|
361
|
+
timestamp: new Date().toISOString(),
|
|
362
|
+
config: serverConfig
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
if (outputPath) {
|
|
366
|
+
await fs.writeFile(outputPath, JSON.stringify(exportData, null, 2));
|
|
367
|
+
return outputPath;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
return exportData;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async importConfig(client, importPath) {
|
|
375
|
+
const content = await fs.readFile(importPath, 'utf-8');
|
|
376
|
+
const importData = JSON.parse(content);
|
|
377
|
+
|
|
378
|
+
if (importData.servers) {
|
|
379
|
+
await this.writeConfig(client, { servers: importData.servers });
|
|
380
|
+
}
|
|
381
|
+
else if (importData.config) {
|
|
382
|
+
await this.addServer(client, importData.serverName, importData.config);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
throw new Error('Invalid import file format');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async getSupportedClients() {
|
|
390
|
+
const availableClients = await this.getAvailableClients();
|
|
391
|
+
return Object.entries(availableClients).map(([id, client]) => ({
|
|
392
|
+
id,
|
|
393
|
+
name: client.name,
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async getAllEnvironmentVariables() {
|
|
398
|
+
const envVarMap = new Map();
|
|
399
|
+
|
|
400
|
+
for (const [clientId, clientInfo] of Object.entries(CLIENTS)) {
|
|
401
|
+
try {
|
|
402
|
+
const config = await this.readConfig(clientId);
|
|
403
|
+
|
|
404
|
+
for (const [serverName, serverConfig] of Object.entries(config.servers)) {
|
|
405
|
+
if (serverConfig.env) {
|
|
406
|
+
for (const [envKey, envValue] of Object.entries(serverConfig.env)) {
|
|
407
|
+
if (!envVarMap.has(envKey)) {
|
|
408
|
+
envVarMap.set(envKey, {
|
|
409
|
+
key: envKey,
|
|
410
|
+
locations: []
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
envVarMap.get(envKey).locations.push({
|
|
415
|
+
client: clientId,
|
|
416
|
+
clientName: clientInfo.name,
|
|
417
|
+
server: serverName,
|
|
418
|
+
value: envValue
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
// Skip clients without configs
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return Array.from(envVarMap.values()).sort((a, b) => a.key.localeCompare(b.key));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async updateEnvironmentVariableAcrossConfigs(envKey, newValue, targetServers = null) {
|
|
432
|
+
const results = [];
|
|
433
|
+
|
|
434
|
+
for (const [clientId, clientInfo] of Object.entries(CLIENTS)) {
|
|
435
|
+
try {
|
|
436
|
+
const config = await this.readConfig(clientId);
|
|
437
|
+
let configModified = false;
|
|
438
|
+
|
|
439
|
+
for (const [serverName, serverConfig] of Object.entries(config.servers)) {
|
|
440
|
+
if (serverConfig.env && serverConfig.env.hasOwnProperty(envKey)) {
|
|
441
|
+
// If targetServers is specified, only update those
|
|
442
|
+
if (targetServers && !targetServers.some(t =>
|
|
443
|
+
t.client === clientId && t.server === serverName)) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const oldValue = serverConfig.env[envKey];
|
|
448
|
+
|
|
449
|
+
if (newValue === null || newValue === undefined) {
|
|
450
|
+
delete serverConfig.env[envKey];
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
serverConfig.env[envKey] = newValue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
configModified = true;
|
|
457
|
+
|
|
458
|
+
results.push({
|
|
459
|
+
client: clientId,
|
|
460
|
+
clientName: clientInfo.name,
|
|
461
|
+
server: serverName,
|
|
462
|
+
oldValue,
|
|
463
|
+
newValue,
|
|
464
|
+
success: true
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (configModified) {
|
|
470
|
+
await this.writeConfig(clientId, config);
|
|
471
|
+
}
|
|
472
|
+
} catch (error) {
|
|
473
|
+
results.push({
|
|
474
|
+
client: clientId,
|
|
475
|
+
clientName: clientInfo.name,
|
|
476
|
+
error: error.message,
|
|
477
|
+
success: false
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return results;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async renameServerAcrossClients(oldName, newName) {
|
|
486
|
+
if (oldName === newName) {
|
|
487
|
+
return { success: true, message: "Server name is the same, no action taken." };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const results = { global: false, clients: [] };
|
|
491
|
+
|
|
492
|
+
// Handle global servers
|
|
493
|
+
const globalServers = await this.readGlobalServers();
|
|
494
|
+
if (globalServers[oldName]) {
|
|
495
|
+
globalServers[newName] = globalServers[oldName];
|
|
496
|
+
delete globalServers[oldName];
|
|
497
|
+
await this.writeGlobalServers(globalServers);
|
|
498
|
+
results.global = true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Handle clients
|
|
502
|
+
const availableClients = await this.getAvailableClients();
|
|
503
|
+
for (const [clientId, clientInfo] of Object.entries(availableClients)) {
|
|
504
|
+
try {
|
|
505
|
+
const config = await this.readConfig(clientId);
|
|
506
|
+
if (config.servers[oldName]) {
|
|
507
|
+
config.servers[newName] = config.servers[oldName];
|
|
508
|
+
delete config.servers[oldName];
|
|
509
|
+
await this.writeConfig(clientId, config);
|
|
510
|
+
results.clients.push({ id: clientId, name: clientInfo.name, success: true });
|
|
511
|
+
} else {
|
|
512
|
+
results.clients.push({ id: clientId, name: clientInfo.name, success: false, message: "Server not found in client config." });
|
|
513
|
+
}
|
|
514
|
+
} catch (error) {
|
|
515
|
+
results.clients.push({ id: clientId, name: clientInfo.name, success: false, error: error.message });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return results;
|
|
519
|
+
}
|
|
520
|
+
}
|