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.
Files changed (124) hide show
  1. package/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
  2. package/.github/acl/access.yml +20 -0
  3. package/.github/compliance/inventory.yml +5 -0
  4. package/.github/policies/jit.yml +19 -0
  5. package/README.md +137 -0
  6. package/dist/cli/commands/install.d.ts +2 -0
  7. package/dist/cli/commands/install.js +105 -0
  8. package/dist/cli/commands/list.d.ts +2 -0
  9. package/dist/cli/commands/list.js +90 -0
  10. package/dist/cli/commands/pull.d.ts +2 -0
  11. package/dist/cli/commands/pull.js +17 -0
  12. package/dist/cli/commands/serve.d.ts +2 -0
  13. package/dist/cli/commands/serve.js +32 -0
  14. package/dist/cli/commands/start.d.ts +2 -0
  15. package/dist/cli/commands/start.js +32 -0
  16. package/dist/cli/commands/sync.d.ts +2 -0
  17. package/dist/cli/commands/sync.js +17 -0
  18. package/dist/cli/commands/uninstall.d.ts +2 -0
  19. package/dist/cli/commands/uninstall.js +39 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.js +114 -0
  22. package/dist/core/ConfigurationProvider.d.ts +31 -0
  23. package/dist/core/ConfigurationProvider.js +416 -0
  24. package/dist/core/InstallationService.d.ts +17 -0
  25. package/dist/core/InstallationService.js +144 -0
  26. package/dist/core/MCPManager.d.ts +17 -0
  27. package/dist/core/MCPManager.js +98 -0
  28. package/dist/core/RequirementService.d.ts +45 -0
  29. package/dist/core/RequirementService.js +123 -0
  30. package/dist/core/constants.d.ts +29 -0
  31. package/dist/core/constants.js +55 -0
  32. package/dist/core/installers/BaseInstaller.d.ts +73 -0
  33. package/dist/core/installers/BaseInstaller.js +247 -0
  34. package/dist/core/installers/ClientInstaller.d.ts +17 -0
  35. package/dist/core/installers/ClientInstaller.js +307 -0
  36. package/dist/core/installers/CommandInstaller.d.ts +36 -0
  37. package/dist/core/installers/CommandInstaller.js +170 -0
  38. package/dist/core/installers/GeneralInstaller.d.ts +32 -0
  39. package/dist/core/installers/GeneralInstaller.js +87 -0
  40. package/dist/core/installers/InstallerFactory.d.ts +52 -0
  41. package/dist/core/installers/InstallerFactory.js +95 -0
  42. package/dist/core/installers/NpmInstaller.d.ts +25 -0
  43. package/dist/core/installers/NpmInstaller.js +123 -0
  44. package/dist/core/installers/PipInstaller.d.ts +25 -0
  45. package/dist/core/installers/PipInstaller.js +114 -0
  46. package/dist/core/installers/RequirementInstaller.d.ts +32 -0
  47. package/dist/core/installers/RequirementInstaller.js +3 -0
  48. package/dist/core/installers/index.d.ts +6 -0
  49. package/dist/core/installers/index.js +7 -0
  50. package/dist/core/types.d.ts +152 -0
  51. package/dist/core/types.js +16 -0
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.js +19 -0
  54. package/dist/services/InstallRequestValidator.d.ts +21 -0
  55. package/dist/services/InstallRequestValidator.js +99 -0
  56. package/dist/services/ServerService.d.ts +47 -0
  57. package/dist/services/ServerService.js +145 -0
  58. package/dist/utils/UpdateCheckTracker.d.ts +39 -0
  59. package/dist/utils/UpdateCheckTracker.js +80 -0
  60. package/dist/utils/clientUtils.d.ts +29 -0
  61. package/dist/utils/clientUtils.js +105 -0
  62. package/dist/utils/feedUtils.d.ts +5 -0
  63. package/dist/utils/feedUtils.js +29 -0
  64. package/dist/utils/githubAuth.d.ts +1 -0
  65. package/dist/utils/githubAuth.js +123 -0
  66. package/dist/utils/logger.d.ts +14 -0
  67. package/dist/utils/logger.js +90 -0
  68. package/dist/utils/osUtils.d.ts +16 -0
  69. package/dist/utils/osUtils.js +235 -0
  70. package/dist/web/public/css/modal.css +250 -0
  71. package/dist/web/public/css/notifications.css +70 -0
  72. package/dist/web/public/index.html +157 -0
  73. package/dist/web/public/js/api.js +213 -0
  74. package/dist/web/public/js/modal.js +572 -0
  75. package/dist/web/public/js/notifications.js +99 -0
  76. package/dist/web/public/js/serverCategoryDetails.js +210 -0
  77. package/dist/web/public/js/serverCategoryList.js +82 -0
  78. package/dist/web/public/modal.html +61 -0
  79. package/dist/web/public/styles.css +155 -0
  80. package/dist/web/server.d.ts +5 -0
  81. package/dist/web/server.js +150 -0
  82. package/package.json +53 -0
  83. package/src/cli/commands/install.ts +140 -0
  84. package/src/cli/commands/list.ts +112 -0
  85. package/src/cli/commands/pull.ts +16 -0
  86. package/src/cli/commands/serve.ts +37 -0
  87. package/src/cli/commands/uninstall.ts +54 -0
  88. package/src/cli/index.ts +127 -0
  89. package/src/core/ConfigurationProvider.ts +489 -0
  90. package/src/core/InstallationService.ts +173 -0
  91. package/src/core/MCPManager.ts +134 -0
  92. package/src/core/RequirementService.ts +147 -0
  93. package/src/core/constants.ts +61 -0
  94. package/src/core/installers/BaseInstaller.ts +292 -0
  95. package/src/core/installers/ClientInstaller.ts +423 -0
  96. package/src/core/installers/CommandInstaller.ts +185 -0
  97. package/src/core/installers/GeneralInstaller.ts +89 -0
  98. package/src/core/installers/InstallerFactory.ts +109 -0
  99. package/src/core/installers/NpmInstaller.ts +128 -0
  100. package/src/core/installers/PipInstaller.ts +121 -0
  101. package/src/core/installers/RequirementInstaller.ts +38 -0
  102. package/src/core/installers/index.ts +9 -0
  103. package/src/core/types.ts +163 -0
  104. package/src/index.ts +44 -0
  105. package/src/services/InstallRequestValidator.ts +112 -0
  106. package/src/services/ServerService.ts +181 -0
  107. package/src/utils/UpdateCheckTracker.ts +86 -0
  108. package/src/utils/clientUtils.ts +112 -0
  109. package/src/utils/feedUtils.ts +31 -0
  110. package/src/utils/githubAuth.ts +142 -0
  111. package/src/utils/logger.ts +101 -0
  112. package/src/utils/osUtils.ts +250 -0
  113. package/src/web/public/css/modal.css +250 -0
  114. package/src/web/public/css/notifications.css +70 -0
  115. package/src/web/public/index.html +157 -0
  116. package/src/web/public/js/api.js +213 -0
  117. package/src/web/public/js/modal.js +572 -0
  118. package/src/web/public/js/notifications.js +99 -0
  119. package/src/web/public/js/serverCategoryDetails.js +210 -0
  120. package/src/web/public/js/serverCategoryList.js +82 -0
  121. package/src/web/public/modal.html +61 -0
  122. package/src/web/public/styles.css +155 -0
  123. package/src/web/server.ts +195 -0
  124. 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
+ }