imcp 0.0.10 → 0.0.12
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 +4 -0
- package/dist/cli/commands/serve.js +2 -1
- package/dist/cli/commands/uninstall.js +14 -6
- package/dist/core/ConfigurationLoader.d.ts +1 -1
- package/dist/core/ConfigurationLoader.js +25 -4
- package/dist/core/ConfigurationProvider.d.ts +2 -1
- package/dist/core/ConfigurationProvider.js +69 -5
- package/dist/core/MCPManager.d.ts +1 -1
- package/dist/core/MCPManager.js +28 -6
- package/dist/core/ServerSchemaLoader.d.ts +11 -0
- package/dist/core/ServerSchemaLoader.js +43 -0
- package/dist/core/ServerSchemaProvider.d.ts +17 -0
- package/dist/core/ServerSchemaProvider.js +115 -0
- package/dist/core/constants.d.ts +11 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/services/ServerService.d.ts +7 -1
- package/dist/services/ServerService.js +24 -9
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/public/css/detailsWidget.css +110 -0
- package/dist/web/public/css/modal.css +42 -0
- package/dist/web/public/css/serverDetails.css +42 -30
- package/dist/web/public/js/detailsWidget.js +225 -12
- package/dist/web/public/js/modal.js +93 -29
- package/dist/web/public/js/notifications.js +34 -35
- package/dist/web/server.js +13 -2
- package/package.json +1 -1
- package/src/cli/commands/serve.ts +4 -3
- package/src/cli/commands/uninstall.ts +16 -6
- package/src/core/ConfigurationLoader.ts +25 -4
- package/src/core/ConfigurationProvider.ts +75 -6
- package/src/core/MCPManager.ts +34 -9
- package/src/core/constants.ts +12 -2
- package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
- package/src/core/types.ts +7 -1
- package/src/services/ServerService.ts +26 -8
- package/src/utils/osUtils.ts +1 -1
- package/src/web/public/css/modal.css +42 -0
- package/src/web/public/js/modal.js +93 -29
- package/src/web/public/js/notifications.js +34 -35
- package/src/web/server.ts +21 -3
package/README.md
CHANGED
|
@@ -40,11 +40,15 @@ imcp serve [options]
|
|
|
40
40
|
|
|
41
41
|
Options:
|
|
42
42
|
- `-p, --port <port>`: Port to run the server on (default: 3000)
|
|
43
|
+
- `-f, --feed-file <filepath>`: Path to a custom feed configuration file
|
|
43
44
|
|
|
44
45
|
Example:
|
|
45
46
|
```bash
|
|
46
47
|
# Start the web interface on port 8080
|
|
47
48
|
imcp serve --port 8080
|
|
49
|
+
|
|
50
|
+
# Start with a custom feed configuration file
|
|
51
|
+
imcp serve --feed-file ./custom-feed.json
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
## License
|
|
@@ -5,12 +5,13 @@ export function createServeCommand() {
|
|
|
5
5
|
return new Command('serve')
|
|
6
6
|
.description('Serve local web interface')
|
|
7
7
|
.option('-p, --port <port>', 'Port to run the server on', '3000')
|
|
8
|
+
.option('-f, --feed-file <filepath>', 'Path to a custom feed configuration file')
|
|
8
9
|
.action(async (options) => {
|
|
9
10
|
try {
|
|
10
11
|
// Sync feeds before start the local UI
|
|
11
12
|
await mcpManager.syncFeeds();
|
|
12
13
|
// Ensure MCP manager is initialized before starting the web server
|
|
13
|
-
await mcpManager.initialize();
|
|
14
|
+
await mcpManager.initialize(options.feedFile);
|
|
14
15
|
const port = parseInt(options.port, 10);
|
|
15
16
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
16
17
|
throw new Error('Invalid port number');
|
|
@@ -3,19 +3,27 @@ import { serverService } from '../../services/ServerService.js';
|
|
|
3
3
|
export function createUninstallCommand() {
|
|
4
4
|
return new Command('uninstall')
|
|
5
5
|
.description('Uninstall specific MCP servers')
|
|
6
|
-
.requiredOption('--category <category>', '
|
|
7
|
-
.
|
|
6
|
+
.requiredOption('--category <category>', 'Server category')
|
|
7
|
+
.requiredOption('--names <names>', 'Server names (semicolon separated)')
|
|
8
|
+
.requiredOption('--targets <targets>', 'Target clients to uninstall from (semicolon separated)')
|
|
9
|
+
.option('--remove-data', 'Remove all associated data', true // Change default to true to ensure cleanup happens
|
|
10
|
+
)
|
|
8
11
|
.action(async (options) => {
|
|
9
12
|
try {
|
|
10
13
|
const serverNames = options.names.split(';').map((name) => name.trim());
|
|
11
|
-
|
|
14
|
+
const validNames = await serverService.validateServerName(options.category, serverNames);
|
|
15
|
+
if (!validNames) {
|
|
12
16
|
console.error('Invalid server names provided');
|
|
13
17
|
process.exit(1);
|
|
14
18
|
}
|
|
15
19
|
console.log(`Uninstalling servers: ${serverNames.join(', ')}`);
|
|
16
|
-
const results = await Promise.all(serverNames.map((serverName) =>
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
const results = await Promise.all(serverNames.map((serverName) => {
|
|
21
|
+
const targets = options.targets ? options.targets.split(';').map((t) => t.trim()) : [];
|
|
22
|
+
return serverService.uninstallMcpServer(options.category, serverName, {
|
|
23
|
+
removeData: options.removeData,
|
|
24
|
+
targets: targets
|
|
25
|
+
});
|
|
26
|
+
}));
|
|
19
27
|
const { success, messages } = serverService.formatOperationResults(results);
|
|
20
28
|
messages.forEach(message => {
|
|
21
29
|
if (success) {
|
|
@@ -24,7 +24,7 @@ export declare class ConfigurationLoader {
|
|
|
24
24
|
/**
|
|
25
25
|
* Loads feed configurations into the MCP configuration
|
|
26
26
|
*/
|
|
27
|
-
static loadFeedsIntoConfiguration(configuration: MCPConfiguration): Promise<MCPConfiguration>;
|
|
27
|
+
static loadFeedsIntoConfiguration(configuration: MCPConfiguration, feedFile?: string): Promise<MCPConfiguration>;
|
|
28
28
|
/**
|
|
29
29
|
* Loads MCP client settings into the configuration
|
|
30
30
|
*/
|
|
@@ -140,23 +140,44 @@ export class ConfigurationLoader {
|
|
|
140
140
|
/**
|
|
141
141
|
* Loads feed configurations into the MCP configuration
|
|
142
142
|
*/
|
|
143
|
-
static async loadFeedsIntoConfiguration(configuration) {
|
|
143
|
+
static async loadFeedsIntoConfiguration(configuration, feedFile) {
|
|
144
144
|
try {
|
|
145
145
|
await fs.mkdir(LOCAL_FEEDS_DIR, { recursive: true });
|
|
146
|
+
const feeds = {};
|
|
147
|
+
// Load provided feed file if specified
|
|
148
|
+
if (feedFile) {
|
|
149
|
+
try {
|
|
150
|
+
const content = await fs.readFile(feedFile, 'utf8');
|
|
151
|
+
const config = JSON.parse(content);
|
|
152
|
+
if (config && config.name) {
|
|
153
|
+
feeds[config.name] = config;
|
|
154
|
+
console.log(`Loaded feed configuration from provided file: ${feedFile}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.log(`Error loading feed configuration from provided file ${feedFile}:`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Load feeds from LOCAL_FEEDS_DIR
|
|
146
162
|
const files = await fs.readdir(LOCAL_FEEDS_DIR);
|
|
147
163
|
const jsonFiles = files.filter(file => file.endsWith('.json'));
|
|
148
|
-
if (jsonFiles.length === 0) {
|
|
164
|
+
if (jsonFiles.length === 0 && !feedFile) {
|
|
149
165
|
console.log(`No feed configuration files found in ${LOCAL_FEEDS_DIR}`);
|
|
150
166
|
return configuration;
|
|
151
167
|
}
|
|
152
|
-
const feeds = {};
|
|
153
168
|
for (const file of jsonFiles) {
|
|
154
169
|
try {
|
|
155
170
|
const filePath = path.join(LOCAL_FEEDS_DIR, file);
|
|
156
171
|
const content = await fs.readFile(filePath, 'utf8');
|
|
157
172
|
const config = JSON.parse(content);
|
|
158
173
|
if (config && config.name) {
|
|
159
|
-
|
|
174
|
+
// If feed exists from provided file, skip the local one
|
|
175
|
+
if (!feeds[config.name]) {
|
|
176
|
+
feeds[config.name] = config;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.log(`Skipping local feed ${config.name} as it was provided via --feed-file`);
|
|
180
|
+
}
|
|
160
181
|
}
|
|
161
182
|
}
|
|
162
183
|
catch (error) {
|
|
@@ -8,7 +8,7 @@ export declare class ConfigurationProvider {
|
|
|
8
8
|
private constructor();
|
|
9
9
|
static getInstance(): ConfigurationProvider;
|
|
10
10
|
private withLock;
|
|
11
|
-
initialize(): Promise<void>;
|
|
11
|
+
initialize(feedFile?: string): Promise<void>;
|
|
12
12
|
private saveConfiguration;
|
|
13
13
|
getServerCategories(): Promise<MCPServerCategory[]>;
|
|
14
14
|
getServerCategory(categoryName: string): Promise<MCPServerCategory | undefined>;
|
|
@@ -28,5 +28,6 @@ export declare class ConfigurationProvider {
|
|
|
28
28
|
private loadFeedsIntoConfiguration;
|
|
29
29
|
private loadClientMCPSettings;
|
|
30
30
|
reloadClientMCPSettings(): Promise<void>;
|
|
31
|
+
removeServerFromClientMCPSettings(serverName: string, target?: string): Promise<void>;
|
|
31
32
|
}
|
|
32
33
|
export declare const configProvider: ConfigurationProvider;
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { exec } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR } from './constants.js';
|
|
6
|
+
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR, SUPPORTED_CLIENTS } from './constants.js';
|
|
7
7
|
import { Logger } from '../utils/logger.js';
|
|
8
8
|
import { checkGithubAuth } from '../utils/githubAuth.js';
|
|
9
9
|
import { ConfigurationLoader } from './ConfigurationLoader.js';
|
|
@@ -43,7 +43,7 @@ export class ConfigurationProvider {
|
|
|
43
43
|
resolve();
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
async initialize() {
|
|
46
|
+
async initialize(feedFile) {
|
|
47
47
|
await this.withLock(async () => {
|
|
48
48
|
const configDir = path.dirname(this.configPath);
|
|
49
49
|
await fs.mkdir(configDir, { recursive: true });
|
|
@@ -60,7 +60,7 @@ export class ConfigurationProvider {
|
|
|
60
60
|
await this.saveConfiguration();
|
|
61
61
|
}
|
|
62
62
|
// Always load feeds and client settings, whether file existed or not
|
|
63
|
-
await this.loadFeedsIntoConfiguration();
|
|
63
|
+
await this.loadFeedsIntoConfiguration(feedFile);
|
|
64
64
|
await this.loadClientMCPSettings();
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
@@ -280,8 +280,8 @@ export class ConfigurationProvider {
|
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
|
-
async loadFeedsIntoConfiguration() {
|
|
284
|
-
this.configuration = await ConfigurationLoader.loadFeedsIntoConfiguration(this.configuration);
|
|
283
|
+
async loadFeedsIntoConfiguration(feedFile) {
|
|
284
|
+
this.configuration = await ConfigurationLoader.loadFeedsIntoConfiguration(this.configuration, feedFile);
|
|
285
285
|
await this.saveConfiguration();
|
|
286
286
|
}
|
|
287
287
|
async loadClientMCPSettings() {
|
|
@@ -294,6 +294,70 @@ export class ConfigurationProvider {
|
|
|
294
294
|
await this.loadClientMCPSettings();
|
|
295
295
|
});
|
|
296
296
|
}
|
|
297
|
+
async removeServerFromClientMCPSettings(serverName, target) {
|
|
298
|
+
return await this.withLock(async () => {
|
|
299
|
+
// Load utils in async context to avoid circular dependencies
|
|
300
|
+
const { readJsonFile, writeJsonFile } = await import('../utils/clientUtils.js');
|
|
301
|
+
// Filter clients if target is specified
|
|
302
|
+
const clientEntries = Object.entries(SUPPORTED_CLIENTS);
|
|
303
|
+
const targetClients = target
|
|
304
|
+
? clientEntries.filter(([clientName]) => clientName === target)
|
|
305
|
+
: clientEntries;
|
|
306
|
+
for (const [clientName, clientSettings] of targetClients) {
|
|
307
|
+
const settingPath = process.env.CODE_INSIDERS
|
|
308
|
+
? clientSettings.codeInsiderSettingPath
|
|
309
|
+
: clientSettings.codeSettingPath;
|
|
310
|
+
try {
|
|
311
|
+
const content = await readJsonFile(settingPath, true);
|
|
312
|
+
let modified = false;
|
|
313
|
+
// Handle GitHub Copilot's different structure
|
|
314
|
+
if (clientName === 'GithubCopilot' && content.mcp?.servers?.[serverName]) {
|
|
315
|
+
delete content.mcp.servers[serverName];
|
|
316
|
+
modified = true;
|
|
317
|
+
}
|
|
318
|
+
else if (content.mcpServers?.[serverName]) {
|
|
319
|
+
delete content.mcpServers[serverName];
|
|
320
|
+
modified = true;
|
|
321
|
+
}
|
|
322
|
+
// Only write if we actually modified the content
|
|
323
|
+
if (modified) {
|
|
324
|
+
await writeJsonFile(settingPath, content);
|
|
325
|
+
Logger.debug(`Removed server ${serverName} from client ${clientName} settings`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
Logger.error(`Failed to remove server ${serverName} from client ${clientName} settings:`, error);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Also update our in-memory configuration
|
|
333
|
+
if (this.configuration.clientMCPSettings) {
|
|
334
|
+
if (target) {
|
|
335
|
+
// Only update settings for the target client
|
|
336
|
+
const clientSettings = this.configuration.clientMCPSettings[target];
|
|
337
|
+
if (clientSettings) {
|
|
338
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
339
|
+
delete clientSettings.mcpServers[serverName];
|
|
340
|
+
}
|
|
341
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
342
|
+
delete clientSettings.servers[serverName];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
// Update all clients if no target specified
|
|
348
|
+
for (const clientSettings of Object.values(this.configuration.clientMCPSettings)) {
|
|
349
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
350
|
+
delete clientSettings.mcpServers[serverName];
|
|
351
|
+
}
|
|
352
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
353
|
+
delete clientSettings.servers[serverName];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
await this.saveConfiguration();
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
297
361
|
}
|
|
298
362
|
// Export a singleton instance
|
|
299
363
|
export const configProvider = ConfigurationProvider.getInstance();
|
|
@@ -5,7 +5,7 @@ export declare class MCPManager extends EventEmitter {
|
|
|
5
5
|
private configProvider;
|
|
6
6
|
constructor();
|
|
7
7
|
syncFeeds(): Promise<void>;
|
|
8
|
-
initialize(): Promise<void>;
|
|
8
|
+
initialize(feedFile?: string): Promise<void>;
|
|
9
9
|
listServerCategories(options?: ServerCategoryListOptions): Promise<MCPServerCategory[]>;
|
|
10
10
|
getFeedConfiguration(categoryName: string): Promise<import("./types.js").FeedConfiguration | undefined>;
|
|
11
11
|
installServer(categoryName: string, serverName: string, requestOptions?: ServerInstallOptions): Promise<ServerOperationResult>;
|
package/dist/core/MCPManager.js
CHANGED
|
@@ -13,9 +13,9 @@ export class MCPManager extends EventEmitter {
|
|
|
13
13
|
async syncFeeds() {
|
|
14
14
|
await this.configProvider.syncFeeds();
|
|
15
15
|
}
|
|
16
|
-
async initialize() {
|
|
16
|
+
async initialize(feedFile) {
|
|
17
17
|
try {
|
|
18
|
-
await this.configProvider.initialize();
|
|
18
|
+
await this.configProvider.initialize(feedFile);
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
21
21
|
console.error("Error during MCPManager initialization:", error);
|
|
@@ -66,12 +66,34 @@ export class MCPManager extends EventEmitter {
|
|
|
66
66
|
message: `Server category ${categoryName} is not onboarded`,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const { targets = [], removeData = false } = options;
|
|
70
|
+
// Clear installation status for specified targets
|
|
71
|
+
const currentStatus = serverCategory.installationStatus || {
|
|
72
|
+
requirementsStatus: {},
|
|
73
|
+
serversStatus: {},
|
|
74
|
+
lastUpdated: new Date().toISOString()
|
|
75
|
+
};
|
|
76
|
+
const serversStatus = currentStatus.serversStatus || {};
|
|
77
|
+
const serverStatus = serversStatus[serverName] || { installedStatus: {}, name: serverName };
|
|
78
|
+
// Only reset installedStatus for specified targets
|
|
79
|
+
for (const target of targets) {
|
|
80
|
+
if (serverStatus.installedStatus) {
|
|
81
|
+
delete serverStatus.installedStatus[target];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (removeData) {
|
|
85
|
+
for (const target of targets) {
|
|
86
|
+
await this.configProvider.removeServerFromClientMCPSettings(serverName, target);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Update server status
|
|
90
|
+
serversStatus[serverName] = serverStatus;
|
|
91
|
+
// Update status keeping requirements
|
|
92
|
+
await this.configProvider.updateInstallationStatus(categoryName, currentStatus.requirementsStatus || {}, serversStatus);
|
|
93
|
+
this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName, targets });
|
|
72
94
|
return {
|
|
73
95
|
success: true,
|
|
74
|
-
message: `Successfully uninstalled ${serverName}`,
|
|
96
|
+
message: `Successfully uninstalled ${serverName} from ${targets.join(', ')}`,
|
|
75
97
|
};
|
|
76
98
|
}
|
|
77
99
|
catch (error) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ServerSchema } from './ServerSchemaProvider.js';
|
|
2
|
+
export declare class ServerSchemaLoader {
|
|
3
|
+
/**
|
|
4
|
+
* Load schema for a specific server in a category
|
|
5
|
+
*/
|
|
6
|
+
static loadSchema(categoryName: string, serverName: string): Promise<ServerSchema | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Validate schema content against expected format
|
|
9
|
+
*/
|
|
10
|
+
static validateSchema(schema: any): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { LOCAL_FEEDS_SCHEMA_DIR } from './constants.js';
|
|
4
|
+
import { Logger } from '../utils/logger.js';
|
|
5
|
+
export class ServerSchemaLoader {
|
|
6
|
+
/**
|
|
7
|
+
* Load schema for a specific server in a category
|
|
8
|
+
*/
|
|
9
|
+
static async loadSchema(categoryName, serverName) {
|
|
10
|
+
try {
|
|
11
|
+
const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, `${serverName}.json`);
|
|
12
|
+
const content = await fs.readFile(schemaPath, 'utf8');
|
|
13
|
+
const schema = JSON.parse(content);
|
|
14
|
+
// Validate schema structure
|
|
15
|
+
if (!schema.version || !schema.schema) {
|
|
16
|
+
Logger.debug(`Invalid schema format for server ${serverName} in category ${categoryName}`);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
schema: schema
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error.code === 'ENOENT') {
|
|
25
|
+
Logger.debug(`No schema file found for server ${serverName} in category ${categoryName}`);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
Logger.error(`Error loading schema for server ${serverName} in category ${categoryName}:`, error);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate schema content against expected format
|
|
34
|
+
*/
|
|
35
|
+
static validateSchema(schema) {
|
|
36
|
+
return (typeof schema === 'object' &&
|
|
37
|
+
schema !== null &&
|
|
38
|
+
typeof schema.version === 'string' &&
|
|
39
|
+
typeof schema.schema === 'object' &&
|
|
40
|
+
schema.schema !== null);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ServerSchemaLoader.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ServerSchema {
|
|
2
|
+
schema: Record<string, any>;
|
|
3
|
+
}
|
|
4
|
+
export declare class ServerSchemaProvider {
|
|
5
|
+
private static instance;
|
|
6
|
+
private schemaMap;
|
|
7
|
+
private schemaLock;
|
|
8
|
+
private constructor();
|
|
9
|
+
static getInstance(): Promise<ServerSchemaProvider>;
|
|
10
|
+
private withLock;
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
private loadSchema;
|
|
13
|
+
private loadAllSchemas;
|
|
14
|
+
getSchema(categoryName: string, serverName: string): Promise<ServerSchema | undefined>;
|
|
15
|
+
reloadSchemas(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function getServerSchemaProvider(): Promise<ServerSchemaProvider>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { LOCAL_FEEDS_SCHEMA_DIR } from './constants.js';
|
|
4
|
+
import { Logger } from '../utils/logger.js';
|
|
5
|
+
export class ServerSchemaProvider {
|
|
6
|
+
static instance;
|
|
7
|
+
schemaMap;
|
|
8
|
+
schemaLock = Promise.resolve();
|
|
9
|
+
constructor() {
|
|
10
|
+
this.schemaMap = new Map();
|
|
11
|
+
}
|
|
12
|
+
static async getInstance() {
|
|
13
|
+
if (!ServerSchemaProvider.instance) {
|
|
14
|
+
ServerSchemaProvider.instance = new ServerSchemaProvider();
|
|
15
|
+
await ServerSchemaProvider.instance.initialize();
|
|
16
|
+
}
|
|
17
|
+
return ServerSchemaProvider.instance;
|
|
18
|
+
}
|
|
19
|
+
async withLock(operation) {
|
|
20
|
+
const current = this.schemaLock;
|
|
21
|
+
let resolve;
|
|
22
|
+
this.schemaLock = new Promise(r => resolve = r);
|
|
23
|
+
try {
|
|
24
|
+
await current;
|
|
25
|
+
return await operation();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
resolve();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async initialize() {
|
|
32
|
+
await this.withLock(async () => {
|
|
33
|
+
try {
|
|
34
|
+
// Create feeds directory if it doesn't exist
|
|
35
|
+
await fs.mkdir(LOCAL_FEEDS_SCHEMA_DIR, { recursive: true });
|
|
36
|
+
// Load all schemas from the feeds directory
|
|
37
|
+
await this.loadAllSchemas();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
Logger.error('Error during schema initialization:', error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async loadSchema(categoryName, serverName) {
|
|
46
|
+
try {
|
|
47
|
+
const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, `${serverName}.json`);
|
|
48
|
+
const content = await fs.readFile(schemaPath, 'utf8');
|
|
49
|
+
const schema = JSON.parse(content);
|
|
50
|
+
return {
|
|
51
|
+
schema: schema
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (error.code === 'ENOENT') {
|
|
56
|
+
Logger.debug(`No schema file found for server ${serverName} in category ${categoryName}`);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
Logger.error(`Error loading schema for server ${serverName} in category ${categoryName}:`, error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async loadAllSchemas() {
|
|
64
|
+
this.schemaMap.clear();
|
|
65
|
+
// Read server category directories
|
|
66
|
+
const categoryDirs = await fs.readdir(LOCAL_FEEDS_SCHEMA_DIR, { withFileTypes: true });
|
|
67
|
+
for (const categoryDir of categoryDirs) {
|
|
68
|
+
if (categoryDir.isDirectory()) {
|
|
69
|
+
const categoryPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryDir.name);
|
|
70
|
+
const serverFiles = await fs.readdir(categoryPath);
|
|
71
|
+
const serverSchemas = new Map();
|
|
72
|
+
for (const file of serverFiles) {
|
|
73
|
+
if (file.endsWith('.json')) {
|
|
74
|
+
const serverName = path.basename(file, '.json');
|
|
75
|
+
try {
|
|
76
|
+
const schema = await this.loadSchema(categoryDir.name, serverName);
|
|
77
|
+
if (schema) {
|
|
78
|
+
serverSchemas.set(serverName, schema);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
Logger.error(`Error loading schema for server ${serverName} in category ${categoryDir.name}:`, error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (serverSchemas.size > 0) {
|
|
87
|
+
this.schemaMap.set(categoryDir.name, serverSchemas);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async getSchema(categoryName, serverName) {
|
|
93
|
+
return await this.withLock(async () => {
|
|
94
|
+
const categorySchemas = this.schemaMap.get(categoryName);
|
|
95
|
+
if (!categorySchemas) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return categorySchemas.get(serverName);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async reloadSchemas() {
|
|
102
|
+
return await this.withLock(async () => {
|
|
103
|
+
await this.loadAllSchemas();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Export a lazy initialized singleton instance getter
|
|
108
|
+
let initPromise = null;
|
|
109
|
+
export function getServerSchemaProvider() {
|
|
110
|
+
if (!initPromise) {
|
|
111
|
+
initPromise = ServerSchemaProvider.getInstance();
|
|
112
|
+
}
|
|
113
|
+
return initPromise;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=ServerSchemaProvider.js.map
|
package/dist/core/constants.d.ts
CHANGED
|
@@ -22,7 +22,17 @@ export declare const LOCAL_FEEDS_DIR: string;
|
|
|
22
22
|
* Value: Client-specific settings or configuration details.
|
|
23
23
|
* TODO: Define actual client settings structure.
|
|
24
24
|
*/
|
|
25
|
-
export declare const SUPPORTED_CLIENTS: Record<string,
|
|
25
|
+
export declare const SUPPORTED_CLIENTS: Record<string, {
|
|
26
|
+
extension: {
|
|
27
|
+
extensionId: string;
|
|
28
|
+
leastVersion?: string;
|
|
29
|
+
repository?: string;
|
|
30
|
+
assetName?: string;
|
|
31
|
+
private?: boolean;
|
|
32
|
+
};
|
|
33
|
+
codeSettingPath: string;
|
|
34
|
+
codeInsiderSettingPath: string;
|
|
35
|
+
}>;
|
|
26
36
|
/**
|
|
27
37
|
* List of supported client names.
|
|
28
38
|
*/
|
|
@@ -128,6 +128,9 @@ export class ExtensionInstaller {
|
|
|
128
128
|
else {
|
|
129
129
|
// Install private extension from GitHub release using latest version
|
|
130
130
|
try {
|
|
131
|
+
if (!repository || !assetName) {
|
|
132
|
+
throw new Error(`Missing repository or assetName for private extension ${extensionId}`);
|
|
133
|
+
}
|
|
131
134
|
const { resolvedPath } = await handleGitHubRelease({ name: extensionId, version: 'latest', type: 'extension' }, { repository, assetName });
|
|
132
135
|
success = await this.installPrivateExtension(resolvedPath, isInsiders);
|
|
133
136
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ export interface UpdateRequirementOptions {
|
|
|
73
73
|
}
|
|
74
74
|
export interface ServerUninstallOptions {
|
|
75
75
|
removeData?: boolean;
|
|
76
|
+
targets?: string[];
|
|
76
77
|
}
|
|
77
78
|
export interface EnvVariableConfig {
|
|
78
79
|
Required: boolean;
|
|
@@ -130,6 +131,10 @@ export interface FeedConfiguration {
|
|
|
130
131
|
requirements: RequirementConfig[];
|
|
131
132
|
mcpServers: McpConfig[];
|
|
132
133
|
}
|
|
134
|
+
export interface ClientSettings {
|
|
135
|
+
codeSettingPath: string;
|
|
136
|
+
codeInsiderSettingPath: string;
|
|
137
|
+
}
|
|
133
138
|
export declare enum MCPEvent {
|
|
134
139
|
SERVER_INSTALLED = "server:installed",
|
|
135
140
|
SERVER_UNINSTALLED = "server:uninstalled",
|
|
@@ -143,6 +148,7 @@ export interface MCPEventData {
|
|
|
143
148
|
};
|
|
144
149
|
[MCPEvent.SERVER_UNINSTALLED]: {
|
|
145
150
|
serverName: string;
|
|
151
|
+
targets?: string[];
|
|
146
152
|
};
|
|
147
153
|
[MCPEvent.SERVER_STARTED]: {
|
|
148
154
|
server: MCPServerCategory;
|
|
@@ -27,11 +27,17 @@ export declare class ServerService {
|
|
|
27
27
|
* Installs a specific mcp tool for a server.
|
|
28
28
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
29
29
|
*/
|
|
30
|
+
/**
|
|
31
|
+
* Uninstall MCP server from specified client targets
|
|
32
|
+
* @param category The server category
|
|
33
|
+
* @param serverName The server name to uninstall
|
|
34
|
+
* @param options Uninstall options including target clients and data removal flags
|
|
35
|
+
*/
|
|
30
36
|
uninstallMcpServer(category: string, serverName: string, options?: ServerUninstallOptions): Promise<ServerOperationResult>;
|
|
31
37
|
/**
|
|
32
38
|
* Validates server names
|
|
33
39
|
*/
|
|
34
|
-
validateServerName(category: string,
|
|
40
|
+
validateServerName(category: string, names: string | string[]): Promise<boolean>;
|
|
35
41
|
/**
|
|
36
42
|
* Formats success/error messages for operations
|
|
37
43
|
*/
|
|
@@ -117,25 +117,40 @@ export class ServerService {
|
|
|
117
117
|
* Installs a specific mcp tool for a server.
|
|
118
118
|
* TODO: This might require enhancing MCPManager to handle category-specific installs.
|
|
119
119
|
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Uninstall MCP server from specified client targets
|
|
122
|
+
* @param category The server category
|
|
123
|
+
* @param serverName The server name to uninstall
|
|
124
|
+
* @param options Uninstall options including target clients and data removal flags
|
|
125
|
+
*/
|
|
126
|
+
async uninstallMcpServer(category, serverName, options = {}) {
|
|
127
|
+
Logger.debug(`Uninstalling MCP server: ${JSON.stringify({ category, serverName, options })}`);
|
|
128
|
+
try {
|
|
129
|
+
const result = await mcpManager.uninstallServer(category, serverName, options);
|
|
130
|
+
Logger.debug(`Uninstallation result: ${JSON.stringify(result)}`);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
Logger.error(`Failed to uninstall MCP server: ${serverName}`, error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
123
137
|
}
|
|
124
138
|
/**
|
|
125
139
|
* Validates server names
|
|
126
140
|
*/
|
|
127
|
-
async validateServerName(category,
|
|
128
|
-
|
|
141
|
+
async validateServerName(category, names) {
|
|
142
|
+
const serverNames = Array.isArray(names) ? names : [names];
|
|
143
|
+
Logger.debug(`Validating server names: ${JSON.stringify({ category, names: serverNames })}`);
|
|
129
144
|
// Check if category exists in feeds
|
|
130
145
|
const feedConfig = await mcpManager.getFeedConfiguration(category);
|
|
131
146
|
if (!feedConfig) {
|
|
132
147
|
Logger.debug(`Validation failed: Category "${category}" not found in feeds`);
|
|
133
148
|
return false;
|
|
134
149
|
}
|
|
135
|
-
// Check if
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
Logger.debug(`Validation failed:
|
|
150
|
+
// Check if all servers exist in the category's mcpServers
|
|
151
|
+
const invalidServers = serverNames.filter(name => !feedConfig.mcpServers.some(server => server.name === name));
|
|
152
|
+
if (invalidServers.length > 0) {
|
|
153
|
+
Logger.debug(`Validation failed: Servers "${invalidServers.join(', ')}" not found in category "${category}"`);
|
|
139
154
|
return false;
|
|
140
155
|
}
|
|
141
156
|
return true;
|
package/dist/utils/osUtils.js
CHANGED
|
@@ -256,7 +256,7 @@ export function getPythonPackagePath(pythonExecutablePath) {
|
|
|
256
256
|
const venvRoot = path.dirname(dir);
|
|
257
257
|
return path.join(venvRoot, 'Lib', 'site-packages');
|
|
258
258
|
}
|
|
259
|
-
else
|
|
259
|
+
else {
|
|
260
260
|
// System Python or Conda on Windows
|
|
261
261
|
return path.join(dir, 'Lib', 'site-packages');
|
|
262
262
|
}
|