imcp 0.0.1
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/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
- package/.github/acl/access.yml +20 -0
- package/.github/compliance/inventory.yml +5 -0
- package/.github/policies/jit.yml +19 -0
- package/README.md +137 -0
- package/dist/cli/commands/install.d.ts +2 -0
- package/dist/cli/commands/install.js +105 -0
- package/dist/cli/commands/list.d.ts +2 -0
- package/dist/cli/commands/list.js +90 -0
- package/dist/cli/commands/pull.d.ts +2 -0
- package/dist/cli/commands/pull.js +17 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +32 -0
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.js +32 -0
- package/dist/cli/commands/sync.d.ts +2 -0
- package/dist/cli/commands/sync.js +17 -0
- package/dist/cli/commands/uninstall.d.ts +2 -0
- package/dist/cli/commands/uninstall.js +39 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +114 -0
- package/dist/core/ConfigurationProvider.d.ts +31 -0
- package/dist/core/ConfigurationProvider.js +416 -0
- package/dist/core/InstallationService.d.ts +17 -0
- package/dist/core/InstallationService.js +144 -0
- package/dist/core/MCPManager.d.ts +17 -0
- package/dist/core/MCPManager.js +98 -0
- package/dist/core/RequirementService.d.ts +45 -0
- package/dist/core/RequirementService.js +123 -0
- package/dist/core/constants.d.ts +29 -0
- package/dist/core/constants.js +55 -0
- package/dist/core/installers/BaseInstaller.d.ts +73 -0
- package/dist/core/installers/BaseInstaller.js +247 -0
- package/dist/core/installers/ClientInstaller.d.ts +17 -0
- package/dist/core/installers/ClientInstaller.js +307 -0
- package/dist/core/installers/CommandInstaller.d.ts +36 -0
- package/dist/core/installers/CommandInstaller.js +170 -0
- package/dist/core/installers/GeneralInstaller.d.ts +32 -0
- package/dist/core/installers/GeneralInstaller.js +87 -0
- package/dist/core/installers/InstallerFactory.d.ts +52 -0
- package/dist/core/installers/InstallerFactory.js +95 -0
- package/dist/core/installers/NpmInstaller.d.ts +25 -0
- package/dist/core/installers/NpmInstaller.js +123 -0
- package/dist/core/installers/PipInstaller.d.ts +25 -0
- package/dist/core/installers/PipInstaller.js +114 -0
- package/dist/core/installers/RequirementInstaller.d.ts +32 -0
- package/dist/core/installers/RequirementInstaller.js +3 -0
- package/dist/core/installers/index.d.ts +6 -0
- package/dist/core/installers/index.js +7 -0
- package/dist/core/types.d.ts +152 -0
- package/dist/core/types.js +16 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +19 -0
- package/dist/services/InstallRequestValidator.d.ts +21 -0
- package/dist/services/InstallRequestValidator.js +99 -0
- package/dist/services/ServerService.d.ts +47 -0
- package/dist/services/ServerService.js +145 -0
- package/dist/utils/UpdateCheckTracker.d.ts +39 -0
- package/dist/utils/UpdateCheckTracker.js +80 -0
- package/dist/utils/clientUtils.d.ts +29 -0
- package/dist/utils/clientUtils.js +105 -0
- package/dist/utils/feedUtils.d.ts +5 -0
- package/dist/utils/feedUtils.js +29 -0
- package/dist/utils/githubAuth.d.ts +1 -0
- package/dist/utils/githubAuth.js +123 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +90 -0
- package/dist/utils/osUtils.d.ts +16 -0
- package/dist/utils/osUtils.js +235 -0
- package/dist/web/public/css/modal.css +250 -0
- package/dist/web/public/css/notifications.css +70 -0
- package/dist/web/public/index.html +157 -0
- package/dist/web/public/js/api.js +213 -0
- package/dist/web/public/js/modal.js +572 -0
- package/dist/web/public/js/notifications.js +99 -0
- package/dist/web/public/js/serverCategoryDetails.js +210 -0
- package/dist/web/public/js/serverCategoryList.js +82 -0
- package/dist/web/public/modal.html +61 -0
- package/dist/web/public/styles.css +155 -0
- package/dist/web/server.d.ts +5 -0
- package/dist/web/server.js +150 -0
- package/package.json +53 -0
- package/src/cli/commands/install.ts +140 -0
- package/src/cli/commands/list.ts +112 -0
- package/src/cli/commands/pull.ts +16 -0
- package/src/cli/commands/serve.ts +37 -0
- package/src/cli/commands/uninstall.ts +54 -0
- package/src/cli/index.ts +127 -0
- package/src/core/ConfigurationProvider.ts +489 -0
- package/src/core/InstallationService.ts +173 -0
- package/src/core/MCPManager.ts +134 -0
- package/src/core/RequirementService.ts +147 -0
- package/src/core/constants.ts +61 -0
- package/src/core/installers/BaseInstaller.ts +292 -0
- package/src/core/installers/ClientInstaller.ts +423 -0
- package/src/core/installers/CommandInstaller.ts +185 -0
- package/src/core/installers/GeneralInstaller.ts +89 -0
- package/src/core/installers/InstallerFactory.ts +109 -0
- package/src/core/installers/NpmInstaller.ts +128 -0
- package/src/core/installers/PipInstaller.ts +121 -0
- package/src/core/installers/RequirementInstaller.ts +38 -0
- package/src/core/installers/index.ts +9 -0
- package/src/core/types.ts +163 -0
- package/src/index.ts +44 -0
- package/src/services/InstallRequestValidator.ts +112 -0
- package/src/services/ServerService.ts +181 -0
- package/src/utils/UpdateCheckTracker.ts +86 -0
- package/src/utils/clientUtils.ts +112 -0
- package/src/utils/feedUtils.ts +31 -0
- package/src/utils/githubAuth.ts +142 -0
- package/src/utils/logger.ts +101 -0
- package/src/utils/osUtils.ts +250 -0
- package/src/web/public/css/modal.css +250 -0
- package/src/web/public/css/notifications.css +70 -0
- package/src/web/public/index.html +157 -0
- package/src/web/public/js/api.js +213 -0
- package/src/web/public/js/modal.js +572 -0
- package/src/web/public/js/notifications.js +99 -0
- package/src/web/public/js/serverCategoryDetails.js +210 -0
- package/src/web/public/js/serverCategoryList.js +82 -0
- package/src/web/public/modal.html +61 -0
- package/src/web/public/styles.css +155 -0
- package/src/web/server.ts +195 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createServeCommand } from './commands/serve.js';
|
|
4
|
+
import { createListCommand } from './commands/list.js';
|
|
5
|
+
import { createInstallCommand } from './commands/install.js';
|
|
6
|
+
import { createPullCommand } from './commands/pull.js';
|
|
7
|
+
import { mcpManager } from '../core/MCPManager.js';
|
|
8
|
+
import { Logger } from '../utils/logger.js';
|
|
9
|
+
import axios from 'axios';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
// Custom error interface for Commander.js errors
|
|
14
|
+
// ANSI color codes
|
|
15
|
+
const COLORS = {
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
yellow: '\x1b[33m'
|
|
18
|
+
};
|
|
19
|
+
async function main() {
|
|
20
|
+
// Initialize the MCP manager
|
|
21
|
+
await mcpManager.initialize();
|
|
22
|
+
const program = new Command();
|
|
23
|
+
program
|
|
24
|
+
.name('imcp')
|
|
25
|
+
.description('IMCP (Install Model Context Protocol) CLI')
|
|
26
|
+
.version('0.0.1')
|
|
27
|
+
.option('--verbose', 'Show detailed logs for all commands');
|
|
28
|
+
// Parse global options first
|
|
29
|
+
program.parseOptions(process.argv);
|
|
30
|
+
const opts = program.opts();
|
|
31
|
+
Logger.setVerbose(!!opts.verbose);
|
|
32
|
+
// Add all commands
|
|
33
|
+
program.addCommand(createServeCommand());
|
|
34
|
+
program.addCommand(createListCommand());
|
|
35
|
+
program.addCommand(createInstallCommand());
|
|
36
|
+
// program.addCommand(createUninstallCommand());
|
|
37
|
+
program.addCommand(createPullCommand());
|
|
38
|
+
// Error handling for the entire CLI
|
|
39
|
+
program.exitOverride();
|
|
40
|
+
// Check for updates
|
|
41
|
+
await checkForUpdates();
|
|
42
|
+
try {
|
|
43
|
+
await program.parseAsync(process.argv);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const commanderError = error;
|
|
47
|
+
if (commanderError.code === 'commander.help') {
|
|
48
|
+
// Help was displayed, exit normally
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
else if (commanderError.code === 'commander.version') {
|
|
52
|
+
// Version was displayed, exit normally
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.error('Error:', commanderError.message || 'An unknown error occurred');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Handle unhandled promise rejections
|
|
62
|
+
process.on('unhandledRejection', (error) => {
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
console.error('Unhandled promise rejection:', error.message);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.error('Unhandled promise rejection:', error);
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Check if there's a newer version of the package available
|
|
73
|
+
*/
|
|
74
|
+
async function checkForUpdates() {
|
|
75
|
+
try {
|
|
76
|
+
// Get the current package version
|
|
77
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
78
|
+
const __dirname = path.dirname(__filename);
|
|
79
|
+
const packagePath = path.resolve(__dirname, '../../package.json');
|
|
80
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
81
|
+
const currentVersion = packageJson.name && packageJson.version ? packageJson.version : '0.0.0';
|
|
82
|
+
const packageName = packageJson.name || 'imcp';
|
|
83
|
+
try {
|
|
84
|
+
// Get the latest version from npm registry (only for published packages)
|
|
85
|
+
const npmResponse = await axios.get(`https://registry.npmjs.org/${packageName}`);
|
|
86
|
+
if (npmResponse.data && npmResponse.data['dist-tags'] && npmResponse.data['dist-tags'].latest) {
|
|
87
|
+
const latestVersion = npmResponse.data['dist-tags'].latest;
|
|
88
|
+
if (latestVersion && latestVersion !== currentVersion) {
|
|
89
|
+
console.log(`${COLORS.yellow}Update available for ${packageName}: ${currentVersion} → ${latestVersion}${COLORS.reset}`);
|
|
90
|
+
console.log(`${COLORS.yellow}Run \`npm install -g ${packageName}@latest\` to update${COLORS.reset}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (npmError) {
|
|
95
|
+
// Log the npm error
|
|
96
|
+
Logger.debug(`Failed to check npm registry: ${npmError instanceof Error ? npmError.message : String(npmError)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Silently fail - don't interrupt the command if update check fails
|
|
101
|
+
Logger.debug(`Failed to check for updates: ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Start the CLI
|
|
105
|
+
main().catch((error) => {
|
|
106
|
+
if (error instanceof Error) {
|
|
107
|
+
console.error('Fatal error:', error.message);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.error('Fatal error:', error);
|
|
111
|
+
}
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MCPServerCategory, FeedConfiguration, InstallationStatus, RequirementStatus, MCPServerStatus, OperationStatus } from './types.js';
|
|
2
|
+
export declare class ConfigurationProvider {
|
|
3
|
+
private static instance;
|
|
4
|
+
private configPath;
|
|
5
|
+
private configuration;
|
|
6
|
+
private configLock;
|
|
7
|
+
private tempDir;
|
|
8
|
+
private constructor();
|
|
9
|
+
static getInstance(): ConfigurationProvider;
|
|
10
|
+
private withLock;
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
private saveConfiguration;
|
|
13
|
+
getServerCategories(): Promise<MCPServerCategory[]>;
|
|
14
|
+
getServerCategory(categoryName: string): Promise<MCPServerCategory | undefined>;
|
|
15
|
+
getFeedConfiguration(categoryName: string): Promise<FeedConfiguration | undefined>;
|
|
16
|
+
getInstallationStatus(categoryName: string): Promise<InstallationStatus | undefined>;
|
|
17
|
+
getServerStatus(categoryName: string, serverName: string): Promise<MCPServerStatus | undefined>;
|
|
18
|
+
getRequirementStatus(categoryName: string, requirementName: string): Promise<RequirementStatus | undefined>;
|
|
19
|
+
updateInstallationStatus(categoryName: string, requirementStatus: Record<string, RequirementStatus>, serverStatus: Record<string, MCPServerStatus>): Promise<boolean>;
|
|
20
|
+
updateRequirementStatus(categoryName: string, requirementName: string, status: RequirementStatus): Promise<boolean>;
|
|
21
|
+
updateRequirementOperationStatus(categoryName: string, requirementName: string, operationStatus: OperationStatus): Promise<boolean>;
|
|
22
|
+
updateServerStatus(categoryName: string, serverName: string, status: MCPServerStatus): Promise<boolean>;
|
|
23
|
+
updateServerOperationStatus(categoryName: string, serverName: string, clientName: string, operationStatus: OperationStatus): Promise<boolean>;
|
|
24
|
+
isRequirementsReady(categoryName: string, serverName: string): Promise<boolean>;
|
|
25
|
+
isServerReady(categoryName: string, serverName: string, clients: string[]): Promise<boolean>;
|
|
26
|
+
syncFeeds(): Promise<void>;
|
|
27
|
+
private loadFeedsIntoConfiguration;
|
|
28
|
+
private syncServerCategoriesWithFeeds;
|
|
29
|
+
syncWithFeed(feedConfiguration: Record<string, FeedConfiguration>): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare const configProvider: ConfigurationProvider;
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR } from './constants.js';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import { checkGithubAuth } from '../utils/githubAuth.js';
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
export class ConfigurationProvider {
|
|
12
|
+
static instance;
|
|
13
|
+
configPath;
|
|
14
|
+
configuration;
|
|
15
|
+
configLock = Promise.resolve();
|
|
16
|
+
tempDir;
|
|
17
|
+
constructor() {
|
|
18
|
+
// Initialize configuration in user's appdata/imcp directory
|
|
19
|
+
this.configPath = path.join(SETTINGS_DIR, 'configurations.json');
|
|
20
|
+
this.configuration = {
|
|
21
|
+
localServerCategories: [],
|
|
22
|
+
feeds: {},
|
|
23
|
+
};
|
|
24
|
+
this.tempDir = path.join(LOCAL_FEEDS_DIR, '../temp');
|
|
25
|
+
}
|
|
26
|
+
static getInstance() {
|
|
27
|
+
if (!ConfigurationProvider.instance) {
|
|
28
|
+
ConfigurationProvider.instance = new ConfigurationProvider();
|
|
29
|
+
}
|
|
30
|
+
return ConfigurationProvider.instance;
|
|
31
|
+
}
|
|
32
|
+
async withLock(operation) {
|
|
33
|
+
const current = this.configLock;
|
|
34
|
+
let resolve;
|
|
35
|
+
this.configLock = new Promise(r => resolve = r);
|
|
36
|
+
try {
|
|
37
|
+
await current;
|
|
38
|
+
return await operation();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async initialize() {
|
|
45
|
+
await this.withLock(async () => {
|
|
46
|
+
const configDir = path.dirname(this.configPath);
|
|
47
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
48
|
+
try {
|
|
49
|
+
const config = JSON.parse(await fs.readFile(this.configPath, 'utf8'));
|
|
50
|
+
this.configuration = config;
|
|
51
|
+
await this.loadFeedsIntoConfiguration(); // Load feeds into configuration
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code !== 'ENOENT') {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
// File doesn't exist, use default empty configuration
|
|
58
|
+
await this.saveConfiguration();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async saveConfiguration() {
|
|
63
|
+
const configDir = path.dirname(this.configPath);
|
|
64
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
65
|
+
await fs.writeFile(this.configPath, JSON.stringify(this.configuration, null, 2));
|
|
66
|
+
}
|
|
67
|
+
async getServerCategories() {
|
|
68
|
+
return await this.withLock(async () => {
|
|
69
|
+
return this.configuration.localServerCategories;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async getServerCategory(categoryName) {
|
|
73
|
+
return await this.withLock(async () => {
|
|
74
|
+
return this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async getFeedConfiguration(categoryName) {
|
|
78
|
+
return await this.withLock(async () => {
|
|
79
|
+
return this.configuration.feeds[categoryName];
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async getInstallationStatus(categoryName) {
|
|
83
|
+
return await this.withLock(async () => {
|
|
84
|
+
// Inline getServerCategory logic
|
|
85
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
86
|
+
return category?.installationStatus;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async getServerStatus(categoryName, serverName) {
|
|
90
|
+
return await this.withLock(async () => {
|
|
91
|
+
// Inline getInstallationStatus logic
|
|
92
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
93
|
+
const status = category?.installationStatus;
|
|
94
|
+
return status?.serversStatus[serverName];
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async getRequirementStatus(categoryName, requirementName) {
|
|
98
|
+
return await this.withLock(async () => {
|
|
99
|
+
// Inline getInstallationStatus logic
|
|
100
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
101
|
+
const status = category?.installationStatus;
|
|
102
|
+
return status?.requirementsStatus[requirementName];
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async updateInstallationStatus(categoryName, requirementStatus, serverStatus) {
|
|
106
|
+
return await this.withLock(async () => {
|
|
107
|
+
// Inline getServerCategory logic
|
|
108
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
109
|
+
if (!category)
|
|
110
|
+
return false;
|
|
111
|
+
if (!category.installationStatus) {
|
|
112
|
+
category.installationStatus = {
|
|
113
|
+
requirementsStatus: {},
|
|
114
|
+
serversStatus: {},
|
|
115
|
+
lastUpdated: new Date().toISOString()
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
category.installationStatus.requirementsStatus = {
|
|
119
|
+
...category.installationStatus.requirementsStatus,
|
|
120
|
+
...requirementStatus
|
|
121
|
+
};
|
|
122
|
+
category.installationStatus.serversStatus = {
|
|
123
|
+
...category.installationStatus.serversStatus,
|
|
124
|
+
...serverStatus
|
|
125
|
+
};
|
|
126
|
+
category.installationStatus.lastUpdated = new Date().toISOString();
|
|
127
|
+
await this.saveConfiguration();
|
|
128
|
+
return true;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async updateRequirementStatus(categoryName, requirementName, status) {
|
|
132
|
+
return await this.withLock(async () => {
|
|
133
|
+
// Inline getServerCategory logic
|
|
134
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
135
|
+
if (!category?.installationStatus)
|
|
136
|
+
return false;
|
|
137
|
+
category.installationStatus.requirementsStatus[requirementName] = status;
|
|
138
|
+
category.installationStatus.lastUpdated = new Date().toISOString();
|
|
139
|
+
await this.saveConfiguration();
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async updateRequirementOperationStatus(categoryName, requirementName, operationStatus) {
|
|
144
|
+
return await this.withLock(async () => {
|
|
145
|
+
// Inline getServerCategory logic
|
|
146
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
147
|
+
if (!category?.installationStatus?.requirementsStatus[requirementName])
|
|
148
|
+
return false;
|
|
149
|
+
category.installationStatus.requirementsStatus[requirementName].operationStatus = operationStatus;
|
|
150
|
+
category.installationStatus.lastUpdated = new Date().toISOString();
|
|
151
|
+
await this.saveConfiguration();
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async updateServerStatus(categoryName, serverName, status) {
|
|
156
|
+
return await this.withLock(async () => {
|
|
157
|
+
// Inline getServerCategory logic
|
|
158
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
159
|
+
if (!category?.installationStatus)
|
|
160
|
+
return false;
|
|
161
|
+
category.installationStatus.serversStatus[serverName] = status;
|
|
162
|
+
category.installationStatus.lastUpdated = new Date().toISOString();
|
|
163
|
+
await this.saveConfiguration();
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async updateServerOperationStatus(categoryName, serverName, clientName, operationStatus) {
|
|
168
|
+
return await this.withLock(async () => {
|
|
169
|
+
// Inline getServerCategory logic
|
|
170
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
171
|
+
if (!category?.installationStatus?.serversStatus[serverName])
|
|
172
|
+
return false;
|
|
173
|
+
category.installationStatus.serversStatus[serverName].installedStatus[clientName] = operationStatus;
|
|
174
|
+
category.installationStatus.lastUpdated = new Date().toISOString();
|
|
175
|
+
await this.saveConfiguration();
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async isRequirementsReady(categoryName, serverName) {
|
|
180
|
+
return await this.withLock(async () => {
|
|
181
|
+
// Inline getServerCategory logic
|
|
182
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
183
|
+
if (!category?.feedConfiguration)
|
|
184
|
+
return false;
|
|
185
|
+
const serverConfig = category.feedConfiguration.mcpServers.find(s => s.name === serverName);
|
|
186
|
+
if (!serverConfig?.dependencies?.requirements)
|
|
187
|
+
return true; // No requirements means ready
|
|
188
|
+
const requirementNames = serverConfig.dependencies.requirements.map(r => r.name);
|
|
189
|
+
// Inline getInstallationStatus logic (using the already fetched category)
|
|
190
|
+
const status = category?.installationStatus;
|
|
191
|
+
if (!status?.requirementsStatus)
|
|
192
|
+
return false;
|
|
193
|
+
return requirementNames.every(name => {
|
|
194
|
+
const reqStatus = status.requirementsStatus[name];
|
|
195
|
+
return reqStatus?.installed && !reqStatus?.error;
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async isServerReady(categoryName, serverName, clients) {
|
|
200
|
+
return await this.withLock(async () => {
|
|
201
|
+
// Inline the logic from getServerStatus and getInstallationStatus to avoid nested lock
|
|
202
|
+
const category = this.configuration.localServerCategories.find(s => s.name === categoryName);
|
|
203
|
+
const installationStatus = category?.installationStatus;
|
|
204
|
+
const serverStatus = installationStatus?.serversStatus[serverName];
|
|
205
|
+
if (!serverStatus)
|
|
206
|
+
return false;
|
|
207
|
+
return clients.every(clientName => {
|
|
208
|
+
// Add optional chaining for safety in case installedStatus is missing
|
|
209
|
+
const clientStatus = serverStatus.installedStatus?.[clientName];
|
|
210
|
+
return clientStatus?.status === 'completed' && !clientStatus?.error;
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async syncFeeds() {
|
|
215
|
+
return await this.withLock(async () => {
|
|
216
|
+
Logger.log('Starting feed synchronization...');
|
|
217
|
+
try {
|
|
218
|
+
// Check GitHub authentication first
|
|
219
|
+
await checkGithubAuth();
|
|
220
|
+
Logger.debug({
|
|
221
|
+
action: 'create_directories',
|
|
222
|
+
paths: {
|
|
223
|
+
localFeeds: LOCAL_FEEDS_DIR,
|
|
224
|
+
tempDir: this.tempDir
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
await fs.mkdir(LOCAL_FEEDS_DIR, { recursive: true });
|
|
228
|
+
await fs.mkdir(this.tempDir, { recursive: true });
|
|
229
|
+
try {
|
|
230
|
+
await fs.access(path.join(this.tempDir, '.git'));
|
|
231
|
+
Logger.debug('Found existing repository, updating...');
|
|
232
|
+
const { stdout, stderr } = await execAsync('git pull', { cwd: this.tempDir });
|
|
233
|
+
Logger.debug({
|
|
234
|
+
action: 'git_pull',
|
|
235
|
+
stderr,
|
|
236
|
+
stdout
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
Logger.debug('No existing repository found, cloning...');
|
|
241
|
+
await fs.rm(this.tempDir, { recursive: true, force: true });
|
|
242
|
+
const { stdout, stderr } = await execAsync(`git clone ${GITHUB_REPO.url} ${this.tempDir}`);
|
|
243
|
+
Logger.debug({
|
|
244
|
+
action: 'git_clone',
|
|
245
|
+
stderr,
|
|
246
|
+
stdout,
|
|
247
|
+
url: GITHUB_REPO.url
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
Logger.debug('Updating local feeds...');
|
|
251
|
+
await fs.rm(LOCAL_FEEDS_DIR, { recursive: true, force: true });
|
|
252
|
+
const sourceFeedsDir = path.join(this.tempDir, GITHUB_REPO.feedsPath);
|
|
253
|
+
try {
|
|
254
|
+
await fs.access(sourceFeedsDir);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
throw new Error(`Could not find feeds directory in cloned repository: ${sourceFeedsDir}`);
|
|
258
|
+
}
|
|
259
|
+
await fs.cp(sourceFeedsDir, LOCAL_FEEDS_DIR, { recursive: true });
|
|
260
|
+
Logger.log('Successfully updated local feeds');
|
|
261
|
+
// Update configuration with new feeds
|
|
262
|
+
await this.loadFeedsIntoConfiguration();
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
Logger.error('Error during feed synchronization', error);
|
|
266
|
+
throw new Error('Failed to sync feeds. Use --verbose for detailed error information.');
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async loadFeedsIntoConfiguration() {
|
|
271
|
+
try {
|
|
272
|
+
await fs.mkdir(LOCAL_FEEDS_DIR, { recursive: true });
|
|
273
|
+
const files = await fs.readdir(LOCAL_FEEDS_DIR);
|
|
274
|
+
const jsonFiles = files.filter(file => file.endsWith('.json'));
|
|
275
|
+
if (jsonFiles.length === 0) {
|
|
276
|
+
console.log(`No feed configuration files found in ${LOCAL_FEEDS_DIR}`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const feeds = {};
|
|
280
|
+
for (const file of jsonFiles) {
|
|
281
|
+
try {
|
|
282
|
+
const filePath = path.join(LOCAL_FEEDS_DIR, file);
|
|
283
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
284
|
+
const config = JSON.parse(content);
|
|
285
|
+
if (config && config.name) {
|
|
286
|
+
feeds[config.name] = config;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
console.warn(`Error loading feed configuration from ${file}:`, error);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
this.configuration.feeds = feeds;
|
|
294
|
+
await this.syncServerCategoriesWithFeeds(); // Sync categories after loading feeds
|
|
295
|
+
await this.saveConfiguration();
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.error("Error loading feed configurations:", error);
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async syncServerCategoriesWithFeeds() {
|
|
303
|
+
let configUpdated = false;
|
|
304
|
+
// 1. Process existing local servers - update their feed configurations
|
|
305
|
+
for (const server of this.configuration.localServerCategories) {
|
|
306
|
+
if (this.configuration.feeds[server.name]) {
|
|
307
|
+
server.feedConfiguration = this.configuration.feeds[server.name];
|
|
308
|
+
configUpdated = true;
|
|
309
|
+
}
|
|
310
|
+
// If server doesn't have installation status, initialize it
|
|
311
|
+
const feedConfig = server.feedConfiguration;
|
|
312
|
+
// If installationStatus is missing, or requirements/tools are empty, initialize from feed
|
|
313
|
+
if (!server.installationStatus ||
|
|
314
|
+
!server.installationStatus.requirementsStatus ||
|
|
315
|
+
Object.keys(server.installationStatus.requirementsStatus).length === 0 ||
|
|
316
|
+
!server.installationStatus.serversStatus ||
|
|
317
|
+
Object.keys(server.installationStatus.serversStatus).length === 0) {
|
|
318
|
+
const requirementsStatus = {};
|
|
319
|
+
const serversStatus = {};
|
|
320
|
+
if (feedConfig) {
|
|
321
|
+
if (feedConfig.requirements) {
|
|
322
|
+
for (const req of feedConfig.requirements) {
|
|
323
|
+
requirementsStatus[req.name] = {
|
|
324
|
+
name: req.name,
|
|
325
|
+
type: req.type,
|
|
326
|
+
installed: false,
|
|
327
|
+
version: req.version,
|
|
328
|
+
error: undefined,
|
|
329
|
+
updateInfo: null
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (feedConfig.mcpServers) {
|
|
334
|
+
for (const mcp of feedConfig.mcpServers) {
|
|
335
|
+
serversStatus[mcp.name] = {
|
|
336
|
+
name: mcp.name,
|
|
337
|
+
error: undefined,
|
|
338
|
+
installedStatus: {} // Add missing property
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
server.installationStatus = {
|
|
344
|
+
requirementsStatus,
|
|
345
|
+
serversStatus,
|
|
346
|
+
lastUpdated: new Date().toISOString()
|
|
347
|
+
};
|
|
348
|
+
configUpdated = true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// 2. Check for feeds that don't have a corresponding local server and create new entries
|
|
352
|
+
const existingServerCategoryNames = new Set(this.configuration.localServerCategories.map(catetory => catetory.name));
|
|
353
|
+
for (const feedName in this.configuration.feeds) {
|
|
354
|
+
if (!existingServerCategoryNames.has(feedName)) {
|
|
355
|
+
// This feed doesn't have a corresponding local server - create one with empty installation status
|
|
356
|
+
const feedConfig = this.configuration.feeds[feedName];
|
|
357
|
+
// Create new server with empty installation status
|
|
358
|
+
const newServerCategory = {
|
|
359
|
+
name: feedName,
|
|
360
|
+
displayName: feedConfig.displayName || feedName,
|
|
361
|
+
type: 'local',
|
|
362
|
+
description: feedConfig.description || `Local MCP server category: ${feedName}`,
|
|
363
|
+
installationStatus: (() => {
|
|
364
|
+
const requirementsStatus = {};
|
|
365
|
+
const serversStatus = {};
|
|
366
|
+
if (feedConfig) {
|
|
367
|
+
if (feedConfig.requirements) {
|
|
368
|
+
for (const req of feedConfig.requirements) {
|
|
369
|
+
requirementsStatus[req.name] = {
|
|
370
|
+
name: req.name,
|
|
371
|
+
type: req.type,
|
|
372
|
+
installed: false,
|
|
373
|
+
version: req.version,
|
|
374
|
+
error: undefined
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (feedConfig.mcpServers) {
|
|
379
|
+
for (const mcp of feedConfig.mcpServers) {
|
|
380
|
+
serversStatus[mcp.name] = {
|
|
381
|
+
name: mcp.name,
|
|
382
|
+
error: undefined,
|
|
383
|
+
installedStatus: {} // Add missing property
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
requirementsStatus,
|
|
390
|
+
serversStatus,
|
|
391
|
+
lastUpdated: new Date().toISOString()
|
|
392
|
+
};
|
|
393
|
+
})(),
|
|
394
|
+
feedConfiguration: feedConfig
|
|
395
|
+
};
|
|
396
|
+
// Add the new server to the configuration
|
|
397
|
+
this.configuration.localServerCategories.push(newServerCategory);
|
|
398
|
+
console.log(`Created new local server entry for feed: ${feedName}`);
|
|
399
|
+
configUpdated = true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (configUpdated) {
|
|
403
|
+
await this.saveConfiguration();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async syncWithFeed(feedConfiguration) {
|
|
407
|
+
await this.withLock(async () => {
|
|
408
|
+
this.configuration.feeds = feedConfiguration;
|
|
409
|
+
await this.syncServerCategoriesWithFeeds(); // Sync categories after direct update
|
|
410
|
+
await this.saveConfiguration();
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Export a singleton instance
|
|
415
|
+
export const configProvider = ConfigurationProvider.getInstance();
|
|
416
|
+
//# sourceMappingURL=ConfigurationProvider.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ServerInstallOptions, ServerOperationResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handles the actual installation process for an MCP server.
|
|
4
|
+
*/
|
|
5
|
+
export declare class InstallationService {
|
|
6
|
+
private activeInstallations;
|
|
7
|
+
private installerFactory;
|
|
8
|
+
constructor();
|
|
9
|
+
private generateOperationId;
|
|
10
|
+
/**
|
|
11
|
+
* Installs a server based on the provided options and feed configuration.
|
|
12
|
+
* @param serverName The name of the server to install.
|
|
13
|
+
* @param options The installation options.
|
|
14
|
+
* @returns A result object indicating success or failure.
|
|
15
|
+
*/
|
|
16
|
+
install(categoryName: string, serverName: string, options: ServerInstallOptions): Promise<ServerOperationResult>;
|
|
17
|
+
}
|