imcp 0.0.13 → 0.0.15

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 (157) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +1 -0
  2. package/dist/core/ConfigurationProvider.js +15 -0
  3. package/dist/core/InstallationService.js +2 -7
  4. package/dist/core/MCPManager.d.ts +11 -2
  5. package/dist/core/MCPManager.js +24 -1
  6. package/dist/core/RequirementService.js +2 -8
  7. package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
  8. package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
  9. package/dist/core/installers/clients/ClientInstaller.d.ts +16 -8
  10. package/dist/core/installers/clients/ClientInstaller.js +77 -504
  11. package/dist/core/installers/clients/ClientInstallerFactory.d.ts +19 -0
  12. package/dist/core/installers/clients/ClientInstallerFactory.js +41 -0
  13. package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
  14. package/dist/core/installers/clients/ClineInstaller.js +124 -0
  15. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
  16. package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
  17. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
  18. package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
  19. package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
  20. package/dist/core/installers/requirements/BaseInstaller.js +5 -116
  21. package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
  22. package/dist/core/installers/requirements/CommandInstaller.js +7 -0
  23. package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
  24. package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
  25. package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
  26. package/dist/core/installers/requirements/NpmInstaller.js +150 -58
  27. package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
  28. package/dist/core/installers/requirements/PipInstaller.js +66 -28
  29. package/dist/core/onboard/FeedOnboardService.d.ts +72 -0
  30. package/dist/core/onboard/FeedOnboardService.js +312 -0
  31. package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
  32. package/dist/core/onboard/OnboardProcessor.js +290 -0
  33. package/dist/core/onboard/OnboardStatus.d.ts +49 -0
  34. package/dist/core/onboard/OnboardStatus.js +10 -0
  35. package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
  36. package/dist/core/onboard/OnboardStatusManager.js +176 -0
  37. package/dist/core/types.d.ts +6 -6
  38. package/dist/core/validators/FeedValidator.d.ts +20 -0
  39. package/dist/core/validators/FeedValidator.js +80 -0
  40. package/dist/core/validators/IServerValidator.d.ts +19 -0
  41. package/dist/core/validators/IServerValidator.js +2 -0
  42. package/dist/core/validators/SSEServerValidator.d.ts +15 -0
  43. package/dist/core/validators/SSEServerValidator.js +39 -0
  44. package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
  45. package/dist/core/validators/ServerValidatorFactory.js +45 -0
  46. package/dist/core/validators/StdioServerValidator.d.ts +46 -0
  47. package/dist/core/validators/StdioServerValidator.js +229 -0
  48. package/dist/services/InstallRequestValidator.d.ts +1 -1
  49. package/dist/services/ServerService.d.ts +9 -6
  50. package/dist/services/ServerService.js +18 -7
  51. package/dist/utils/adoUtils.d.ts +29 -0
  52. package/dist/utils/adoUtils.js +252 -0
  53. package/dist/utils/clientUtils.d.ts +0 -7
  54. package/dist/utils/clientUtils.js +0 -42
  55. package/dist/utils/githubUtils.d.ts +10 -0
  56. package/dist/utils/githubUtils.js +22 -0
  57. package/dist/utils/macroExpressionUtils.d.ts +38 -0
  58. package/dist/utils/macroExpressionUtils.js +116 -0
  59. package/dist/utils/osUtils.d.ts +4 -20
  60. package/dist/utils/osUtils.js +78 -23
  61. package/dist/web/contract/serverContract.d.ts +66 -0
  62. package/dist/web/contract/serverContract.js +2 -0
  63. package/dist/web/public/css/notifications.css +48 -17
  64. package/dist/web/public/css/onboard.css +107 -0
  65. package/dist/web/public/index.html +90 -18
  66. package/dist/web/public/js/api.js +3 -6
  67. package/dist/web/public/js/flights/flights.js +127 -0
  68. package/dist/web/public/js/modal/index.js +58 -0
  69. package/dist/web/public/js/modal/installHandler.js +227 -0
  70. package/dist/web/public/js/modal/installModal.js +163 -0
  71. package/dist/web/public/js/modal/installation.js +281 -0
  72. package/dist/web/public/js/modal/loadingModal.js +52 -0
  73. package/dist/web/public/js/modal/loadingUI.js +74 -0
  74. package/dist/web/public/js/modal/messageQueue.js +112 -0
  75. package/dist/web/public/js/modal/modalSetup.js +513 -0
  76. package/dist/web/public/js/modal/modalUI.js +214 -0
  77. package/dist/web/public/js/modal/modalUtils.js +49 -0
  78. package/dist/web/public/js/modal/version.js +20 -0
  79. package/dist/web/public/js/modal/versionUtils.js +20 -0
  80. package/dist/web/public/js/modal.js +25 -1041
  81. package/dist/web/public/js/notifications.js +66 -27
  82. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  83. package/dist/web/public/js/onboard/formProcessor.js +864 -0
  84. package/dist/web/public/js/onboard/index.js +374 -0
  85. package/dist/web/public/js/onboard/publishHandler.js +132 -0
  86. package/dist/web/public/js/onboard/state.js +76 -0
  87. package/dist/web/public/js/onboard/templates.js +343 -0
  88. package/dist/web/public/js/onboard/uiHandlers.js +758 -0
  89. package/dist/web/public/js/onboard/validationHandlers.js +378 -0
  90. package/dist/web/public/js/serverCategoryDetails.js +43 -17
  91. package/dist/web/public/js/serverCategoryList.js +15 -2
  92. package/dist/web/public/onboard.html +296 -0
  93. package/dist/web/public/styles.css +91 -1
  94. package/dist/web/server.d.ts +0 -10
  95. package/dist/web/server.js +131 -22
  96. package/package.json +2 -2
  97. package/src/core/ConfigurationProvider.ts +15 -0
  98. package/src/core/InstallationService.ts +2 -7
  99. package/src/core/MCPManager.ts +26 -1
  100. package/src/core/RequirementService.ts +2 -9
  101. package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
  102. package/src/core/installers/clients/ClientInstaller.ts +97 -589
  103. package/src/core/installers/clients/ClientInstallerFactory.ts +46 -0
  104. package/src/core/installers/clients/ClineInstaller.ts +135 -0
  105. package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
  106. package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
  107. package/src/core/installers/requirements/BaseInstaller.ts +13 -136
  108. package/src/core/installers/requirements/CommandInstaller.ts +9 -1
  109. package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
  110. package/src/core/installers/requirements/NpmInstaller.ts +178 -61
  111. package/src/core/installers/requirements/PipInstaller.ts +68 -29
  112. package/src/core/onboard/FeedOnboardService.ts +346 -0
  113. package/src/core/onboard/OnboardProcessor.ts +305 -0
  114. package/src/core/onboard/OnboardStatus.ts +55 -0
  115. package/src/core/onboard/OnboardStatusManager.ts +188 -0
  116. package/src/core/types.ts +6 -6
  117. package/src/core/validators/FeedValidator.ts +79 -0
  118. package/src/core/validators/IServerValidator.ts +21 -0
  119. package/src/core/validators/SSEServerValidator.ts +43 -0
  120. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  121. package/src/core/validators/StdioServerValidator.ts +259 -0
  122. package/src/services/InstallRequestValidator.ts +1 -1
  123. package/src/services/ServerService.ts +22 -7
  124. package/src/utils/adoUtils.ts +291 -0
  125. package/src/utils/clientUtils.ts +0 -44
  126. package/src/utils/githubUtils.ts +24 -0
  127. package/src/utils/macroExpressionUtils.ts +121 -0
  128. package/src/utils/osUtils.ts +89 -24
  129. package/src/web/contract/serverContract.ts +74 -0
  130. package/src/web/public/css/notifications.css +48 -17
  131. package/src/web/public/css/onboard.css +107 -0
  132. package/src/web/public/index.html +90 -18
  133. package/src/web/public/js/api.js +3 -6
  134. package/src/web/public/js/flights/flights.js +127 -0
  135. package/src/web/public/js/modal/index.js +58 -0
  136. package/src/web/public/js/modal/installModal.js +163 -0
  137. package/src/web/public/js/modal/installation.js +281 -0
  138. package/src/web/public/js/modal/loadingModal.js +52 -0
  139. package/src/web/public/js/modal/messageQueue.js +112 -0
  140. package/src/web/public/js/modal/modalSetup.js +513 -0
  141. package/src/web/public/js/modal/modalUtils.js +49 -0
  142. package/src/web/public/js/modal/versionUtils.js +20 -0
  143. package/src/web/public/js/modal.js +25 -1041
  144. package/src/web/public/js/notifications.js +66 -27
  145. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
  146. package/src/web/public/js/onboard/formProcessor.js +864 -0
  147. package/src/web/public/js/onboard/index.js +374 -0
  148. package/src/web/public/js/onboard/publishHandler.js +132 -0
  149. package/src/web/public/js/onboard/state.js +76 -0
  150. package/src/web/public/js/onboard/templates.js +343 -0
  151. package/src/web/public/js/onboard/uiHandlers.js +758 -0
  152. package/src/web/public/js/onboard/validationHandlers.js +378 -0
  153. package/src/web/public/js/serverCategoryDetails.js +43 -17
  154. package/src/web/public/js/serverCategoryList.js +15 -2
  155. package/src/web/public/onboard.html +296 -0
  156. package/src/web/public/styles.css +91 -1
  157. package/src/web/server.ts +167 -58
@@ -1,25 +1,19 @@
1
- import { ExtensionInstaller } from './ExtensionInstaller.js';
1
+ import { ConfigurationProvider } from '../../ConfigurationProvider.js';
2
+ import { SUPPORTED_CLIENTS } from '../../constants.js';
2
3
  import {
3
4
  ServerOperationResult,
4
5
  OperationStatus,
5
- MCPServerStatus,
6
- RequirementStatus,
7
6
  ServerInstallOptions,
8
- FeedConfiguration,
9
- McpConfig
7
+ McpConfig,
8
+ RequirementStatus
10
9
  } from '../../types.js';
11
- import { GetBrowserPath } from '../../../utils/osUtils.js';
12
- import { ConfigurationProvider } from '../../ConfigurationProvider.js';
13
- import { SUPPORTED_CLIENTS } from '../../constants.js';
14
- import { resolveNpmModulePath, readJsonFile, writeJsonFile } from '../../../utils/clientUtils.js';
15
- import { exec } from 'child_process';
16
- import { promisify } from 'util';
17
- import * as path from 'path';
18
10
  import { Logger } from '../../../utils/logger.js';
19
- import { getPythonPackagePath, getSystemPythonPackageDirectory } from '../../../utils/osUtils.js';
20
-
21
- const execAsync = promisify(exec); // Moved promisify here for reuse
11
+ import { ClientInstallerFactory } from './ClientInstallerFactory.js';
22
12
 
13
+ /**
14
+ * Main client installer class that orchestrates client installation process
15
+ * Handles requirements checking and delegates to specific client installers
16
+ */
23
17
  export class ClientInstaller {
24
18
  private configProvider: ConfigurationProvider;
25
19
  private operationStatuses: Map<string, OperationStatus>;
@@ -33,106 +27,87 @@ export class ClientInstaller {
33
27
  this.operationStatuses = new Map();
34
28
  }
35
29
 
30
+ /**
31
+ * Generate a unique operation ID for tracking installations
32
+ */
36
33
  private generateOperationId(): string {
37
34
  return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
38
35
  }
39
36
 
40
- private async getNpmPath(): Promise<string> {
41
- try {
42
- // Execute the get-command npm command to find the npm path
43
- const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
44
-
45
- // Extract the directory from the full path (removing npm.cmd)
46
- const npmPath = stdout.trim().replace(/\\npm\.cmd$/, '');
47
- return npmPath;
48
- } catch (error) {
49
- console.error('Error getting npm path:', error);
50
- // Return a default path if the command fails
51
- return 'C:\\Program Files\\nodejs';
52
- }
53
- }
54
-
55
-
56
37
  /**
57
- * Check if a command is available on the system
58
- * @param command The command to check
59
- * @returns True if the command is available, false otherwise
38
+ * Check if server requirements are ready
39
+ * Waits for requirements to be ready with timeout
60
40
  */
61
- private async isCommandAvailable(command: string): Promise<boolean> {
62
- try {
63
- // For VS Code on macOS, check both command-line tool and app bundle
64
- if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
65
- try {
66
- // Try which command first
67
- await execAsync(`which ${command}`);
68
- return true;
69
- } catch (error) {
70
- // If which fails, check application bundles
71
- const systemVSCodePath = command === 'code' ?
72
- '/Applications/Visual Studio Code.app' :
73
- '/Applications/Visual Studio Code - Insiders.app';
41
+ private async checkRequirements(operationId: string, clientName: string, options: ServerInstallOptions): Promise<boolean> {
42
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
74
43
 
75
- try {
76
- // Check system Applications first
77
- await execAsync(`test -d "${systemVSCodePath}"`);
78
- return true;
79
- } catch (error) {
80
- // If system Applications check fails, try user Applications
81
- const homedir = process.env.HOME;
82
- if (homedir) {
83
- const userVSCodePath = command === 'code' ?
84
- `${homedir}/Applications/Visual Studio Code.app` :
85
- `${homedir}/Applications/Visual Studio Code - Insiders.app`;
44
+ if (!requirementsReady) {
45
+ const pendingStatus: OperationStatus = {
46
+ status: 'pending',
47
+ type: 'install',
48
+ target: 'server',
49
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
50
+ operationId
51
+ };
86
52
 
87
- try {
88
- await execAsync(`test -d "${userVSCodePath}"`);
89
- return true;
90
- } catch (error) {
91
- return false;
92
- }
93
- }
94
- return false;
95
- }
96
- }
97
- }
53
+ await this.configProvider.updateServerOperationStatus(
54
+ this.categoryName,
55
+ this.serverName,
56
+ clientName,
57
+ pendingStatus
58
+ );
59
+
60
+ // Set up periodic checking with timeout
61
+ const startTime = Date.now();
62
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
63
+ const intervalMs = 5 * 1000; // 5 seconds
98
64
 
99
- // For Windows, use where command
100
- if (process.platform === 'win32') {
101
- await execAsync(`where ${command}`);
102
- return true;
65
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
66
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
67
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
103
68
  }
69
+ }
104
70
 
105
- // For all other cases (Unix-like systems), use which command
106
- await execAsync(`which ${command}`);
107
- return true;
108
- } catch (error) {
109
- return false;
71
+ var requirementsStatus: RequirementStatus[] = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
72
+
73
+ // Find first non-empty npmPath from requirements status
74
+ const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
75
+ if (npmPathRequirement && npmPathRequirement.npmPath) {
76
+ options.settings = options.settings || {};
77
+ options.settings.npmPath = npmPathRequirement.npmPath;
110
78
  }
79
+
80
+ return requirementsReady;
111
81
  }
112
82
 
113
- // Modified to accept ServerInstallOptions
83
+ /**
84
+ * Install client with requirements checking
85
+ */
114
86
  private async installClient(clientName: string, options: ServerInstallOptions): Promise<OperationStatus> {
87
+ const operationId = this.generateOperationId();
88
+
115
89
  // Check if client is supported
116
- if (!SUPPORTED_CLIENTS[clientName]) {
90
+ if (!ClientInstallerFactory.isClientSupported(clientName)) {
117
91
  return {
118
92
  status: 'failed',
119
93
  type: 'install',
120
94
  target: 'server',
121
95
  message: `Unsupported client: ${clientName}`,
122
- operationId: this.generateOperationId()
96
+ operationId
123
97
  };
124
98
  }
125
99
 
126
100
  // Create initial operation status
127
- const operationId = this.generateOperationId();
128
101
  const initialStatus: OperationStatus = {
129
102
  status: 'pending',
130
103
  type: 'install',
131
104
  target: 'server',
132
105
  message: `Initializing installation for client: ${clientName}`,
133
- operationId: operationId
106
+ operationId
134
107
  };
135
108
 
109
+ this.processInstallation(clientName, operationId, options);
110
+
136
111
  // Update server status with initial client installation status
137
112
  await this.configProvider.updateServerOperationStatus(
138
113
  this.categoryName,
@@ -140,73 +115,41 @@ export class ClientInstaller {
140
115
  clientName,
141
116
  initialStatus
142
117
  );
143
-
144
- // Start the asynchronous installation process without awaiting it
145
- // Pass options down
146
- this.processInstallation(clientName, operationId, options);
147
-
148
- // Return the initial status immediately
149
118
  return initialStatus;
150
119
  }
151
120
 
152
- // Modified to accept ServerInstallOptions
153
121
  private async processInstallation(clientName: string, operationId: string, options: ServerInstallOptions): Promise<void> {
154
122
  try {
155
- await ExtensionInstaller.installExtension(clientName);
156
-
157
- // Check requirements before installation
158
- let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
159
-
160
- // If requirements are not ready, periodically check with timeout
123
+ // Check requirements
124
+ const requirementsReady = await this.checkRequirements(operationId, clientName, options);
161
125
  if (!requirementsReady) {
162
- const pendingStatus: OperationStatus = {
163
- status: 'pending',
126
+ const failedStatus: OperationStatus = {
127
+ status: 'failed',
164
128
  type: 'install',
165
129
  target: 'server',
166
- message: `Waiting for requirements to be ready for client: ${clientName}`,
167
- operationId: operationId
130
+ message: `Requirements not ready for client: ${clientName} after timeout`,
131
+ operationId
168
132
  };
169
133
 
170
- // Update status to pending with reference to configProvider
171
134
  await this.configProvider.updateServerOperationStatus(
172
135
  this.categoryName,
173
136
  this.serverName,
174
137
  clientName,
175
- pendingStatus
138
+ failedStatus
176
139
  );
140
+ return;
141
+ }
177
142
 
178
- // Set up periodic checking with timeout
179
- const startTime = Date.now();
180
- const timeoutMs = 5 * 60 * 1000; // 5 minutes in milliseconds
181
- const intervalMs = 5 * 1000; // 5 seconds in milliseconds
182
-
183
- while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
184
- // Wait for the interval
185
- await new Promise(resolve => setTimeout(resolve, intervalMs));
186
-
187
- // Check again
188
- requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
189
- }
190
-
191
- // If still not ready after timeout, update status as failed and exit
192
- if (!requirementsReady) {
193
- const failedStatus: OperationStatus = {
194
- status: 'failed',
195
- type: 'install',
196
- target: 'server',
197
- message: `Timed out waiting for requirements to be ready for client: ${clientName} after 5 minutes`,
198
- operationId: operationId
199
- };
143
+ // Create client-specific installer
144
+ const installer = ClientInstallerFactory.createInstaller(clientName);
200
145
 
201
- await this.configProvider.updateServerOperationStatus(
202
- this.categoryName,
203
- this.serverName,
204
- clientName,
205
- failedStatus
206
- );
146
+ if (!installer) {
147
+ throw new Error(`Failed to create installer for client: ${clientName}`);
148
+ }
207
149
 
208
- return; // Exit the installation process
209
- }
150
+ const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
151
+ if (!serverConfig) {
152
+ throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
210
153
  }
211
154
 
212
155
  // If we've reached here, requirements are ready - update status to in-progress
@@ -224,101 +167,25 @@ export class ClientInstaller {
224
167
  clientName,
225
168
  inProgressStatus
226
169
  );
170
+ // Install client
171
+ const status = await installer.install(serverConfig, options);
172
+ await this.configProvider.updateServerOperationStatus(
173
+ this.categoryName,
174
+ this.serverName,
175
+ clientName,
176
+ status
177
+ );
227
178
 
228
- // Get feed configuration for the server
229
- const feedConfiguration = await this.configProvider.getFeedConfiguration(this.categoryName);
230
- if (!feedConfiguration) {
231
- const errorStatus: OperationStatus = {
232
- status: 'failed',
233
- type: 'install',
234
- target: 'server',
235
- message: `Failed to get feed configuration for category: ${this.categoryName}`,
236
- operationId: operationId
237
- };
238
- await this.configProvider.updateServerOperationStatus(
239
- this.categoryName,
240
- this.serverName,
241
- clientName,
242
- errorStatus
243
- );
244
- return;
245
- }
246
-
247
- // Find the server config in the feed configuration
248
- const serverConfig = feedConfiguration.mcpServers.find(s => s.name === this.serverName);
249
- if (!serverConfig) {
250
- const errorStatus: OperationStatus = {
251
- status: 'failed',
252
- type: 'install',
253
- target: 'server',
254
- message: `Server ${this.serverName} not found in feed configuration`,
255
- operationId: operationId
256
- };
257
- await this.configProvider.updateServerOperationStatus(
258
- this.categoryName,
259
- this.serverName,
260
- clientName,
261
- errorStatus
262
- );
263
- return;
264
- }
265
-
266
- try {
267
- // Install extension if available
268
- if (SUPPORTED_CLIENTS[clientName]?.extension) {
269
- const extensionResult = await ExtensionInstaller.installExtension(clientName);
270
- if (!extensionResult) {
271
- Logger.debug(`Failed to install extension for client ${clientName}`);
272
- }
273
- }
274
-
275
- // Install client-specific configuration, passing options down
276
- const result = await this.installClientConfig(clientName, options, serverConfig, feedConfiguration);
277
-
278
- const finalStatus: OperationStatus = {
279
- status: result.success ? 'completed' : 'failed',
280
- type: 'install',
281
- target: 'server',
282
- message: result.message || `Installation for client ${clientName}: ${result.success ? 'successful' : 'failed'}`,
283
- operationId: operationId,
284
- error: result.success ? undefined : result.message
285
- };
286
-
287
- await this.configProvider.updateServerOperationStatus(
288
- this.categoryName,
289
- this.serverName,
290
- clientName,
291
- finalStatus
292
- );
293
-
294
- if (result.success) {
295
- await this.configProvider.reloadClientMCPSettings();
296
- }
297
-
298
- } catch (error) {
299
- const errorStatus: OperationStatus = {
300
- status: 'failed',
301
- type: 'install',
302
- target: 'server',
303
- message: `Failed to install client: ${clientName}. Error: ${error instanceof Error ? error.message : String(error)}`,
304
- operationId: operationId,
305
- error: error instanceof Error ? error.message : String(error)
306
- };
307
-
308
- await this.configProvider.updateServerOperationStatus(
309
- this.categoryName,
310
- this.serverName,
311
- clientName,
312
- errorStatus
313
- );
179
+ if (status.status === 'completed') {
180
+ await this.configProvider.reloadClientMCPSettings();
314
181
  }
315
182
  } catch (error) {
316
183
  const errorStatus: OperationStatus = {
317
184
  status: 'failed',
318
185
  type: 'install',
319
186
  target: 'server',
320
- message: `Unexpected error in installation process for client: ${clientName}. Error: ${error instanceof Error ? error.message : String(error)}`,
321
- operationId: operationId,
187
+ message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
188
+ operationId,
322
189
  error: error instanceof Error ? error.message : String(error)
323
190
  };
324
191
 
@@ -331,385 +198,26 @@ export class ClientInstaller {
331
198
  }
332
199
  }
333
200
 
334
- // Modified to accept ServerInstallOptions
335
- private async installClientConfig(
336
- clientName: string,
337
- options: ServerInstallOptions, // Use options directly
338
- serverConfig: McpConfig,
339
- feedConfig: FeedConfiguration
340
- ): Promise<{ success: boolean; message: string }> {
341
- try {
342
- if (!SUPPORTED_CLIENTS[clientName]) {
343
- Logger.debug(`Client ${clientName} is not supported.`);
344
- return { success: false, message: `Unsupported client: ${clientName}` };
345
- }
346
-
347
- const clientSettings = SUPPORTED_CLIENTS[clientName];
348
-
349
- // Get both setting paths for VS Code and VS Code Insiders
350
- const regularSettingPath = clientSettings.codeSettingPath;
351
- const insidersSettingPath = clientSettings.codeInsiderSettingPath;
352
-
353
- Logger.debug(`Starting installation of ${this.serverName} for client ${clientName}`);
354
- Logger.debug(`VS Code settings path configured as: ${regularSettingPath}`);
355
- Logger.debug(`VS Code Insiders settings path configured as: ${insidersSettingPath}`);
356
-
357
- // Check if VS Code and VS Code Insiders are installed
358
- const isVSCodeInstalled = await this.isCommandAvailable('code');
359
- const isVSCodeInsidersInstalled = await this.isCommandAvailable('code-insiders');
360
- Logger.debug(isVSCodeInstalled ? 'VS Code detected on system' : 'VS Code not detected on system');
361
- Logger.debug(isVSCodeInsidersInstalled ? 'VS Code Insiders detected on system' : 'VS Code Insiders not detected on system');
362
-
363
- // If neither is installed, we can't proceed
364
- if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
365
- Logger.debug('No VS Code installations detected on system. Cannot update settings.');
366
- return {
367
- success: false,
368
- message: `Neither VS Code nor VS Code Insiders are installed on this system. Cannot update settings for client: ${clientName}. Please install VS Code or VS Code Insiders and try again.`
369
- };
370
- }
371
-
372
- // --- Start of new logic ---
373
-
374
- // Clone the base installation configuration to avoid modifying the original serverConfig
375
- const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
376
- const pythonEnv = options.settings?.pythonEnv;
377
- let pythonDir: string | null = null;
378
-
379
- // 1. Determine which args to use and resolve npm paths
380
- let finalArgs: string[] = [];
381
- if (options.args && options.args.length > 0) {
382
- Logger.debug(`Using args from ServerInstallOptions: ${options.args.join(' ')}`);
383
- finalArgs = options.args.map(arg => resolveNpmModulePath(arg));
384
- } else if (installConfig.args && installConfig.args.length > 0) {
385
- Logger.debug(`Using args from serverConfig.installation: ${installConfig.args.join(' ')}`);
386
- finalArgs = installConfig.args.map((arg: string) => resolveNpmModulePath(arg));
387
- } else {
388
- Logger.debug('No args found in options or serverConfig.installation.');
389
- finalArgs = [];
390
- }
391
-
392
- // 2. Handle pythonEnv settings
393
- if (pythonEnv) {
394
- // 2.1 If pythonEnv is set
395
- Logger.debug(`Python environment specified: ${pythonEnv}`);
396
- pythonDir = getPythonPackagePath(pythonEnv);
397
-
398
- // 2.1.1 Replace ${PYTHON_PACKAGE} in args
399
- if (pythonDir) {
400
- finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir!) : arg);
401
- Logger.debug(`Args after PYTHON_PACKAGE replacement (using pythonEnv): ${finalArgs.join(' ')}`);
402
- } else {
403
- Logger.debug(`Could not determine directory for pythonEnv: ${pythonEnv}`);
404
- }
405
-
406
- // 2.1.2 Replace command if it's 'python'
407
- if (installConfig.command === 'python') {
408
- Logger.debug(`Replacing command 'python' with specified pythonEnv: ${pythonEnv}`);
409
- installConfig.command = pythonEnv;
410
- }
411
- } else {
412
- // 2.2 If pythonEnv is not set
413
- Logger.debug('No Python environment specified in settings.');
414
- // 2.2.1 Replace ${PYTHON_PACKAGE} with system python directory if needed
415
- if (finalArgs.some(arg => arg.includes('${PYTHON_PACKAGE}'))) {
416
- Logger.debug('Attempting to find system Python directory for ${PYTHON_PACKAGE} replacement.');
417
- pythonDir = await getSystemPythonPackageDirectory();
418
- if (pythonDir) {
419
- finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir!) : arg);
420
- Logger.debug(`Args after PYTHON_PACKAGE replacement (using system python): ${finalArgs.join(' ')}`);
421
- } else {
422
- Logger.debug('Could not find system Python directory. ${PYTHON_PACKAGE} replacement skipped.');
423
- // Optionally, remove or handle the arg containing ${PYTHON_PACKAGE} if Python is required
424
- // finalArgs = finalArgs.filter(arg => !arg.includes('${PYTHON_PACKAGE}'));
425
- }
426
- }
427
- }
428
-
429
- // Update installConfig with potentially modified args
430
- installConfig.args = finalArgs;
431
-
432
-
433
- // 3. Handle environment variables (merge default, serverConfig, and options.env)
434
- const baseEnv = serverConfig.installation.env || {};
435
- const defaultEnv: Record<string, string> = {};
436
- for (const [key, config] of Object.entries(baseEnv)) {
437
- const envConfig = config as any; // Type assertion
438
- if (envConfig.Default) {
439
- defaultEnv[key] = envConfig.Default;
440
- }
441
- }
442
- // Merge: options.env overrides defaultEnv
443
- installConfig.env = { ...defaultEnv, ...(options.env || {}) };
444
-
445
- // Replace ${BROWSER_PATH} with actual browser path
446
- const replacements = Object.entries(installConfig.env)
447
- .filter(([_, value]) => typeof value === 'string' && value.includes('${BROWSER_PATH}'))
448
- .map(async ([key, value]) => {
449
- try {
450
- const browserPath = await GetBrowserPath();
451
- return [key, (value as string).replace('${BROWSER_PATH}', browserPath)] as [string, string];
452
- } catch (error) {
453
- Logger.error(`Failed to get system browser path for env var ${key}:`, error);
454
- return [key, value] as [string, string];
455
- }
456
- });
457
-
458
- // Wait for all replacements to complete
459
- const replacedValues = await Promise.all(replacements);
460
- replacedValues.forEach(([key, value]) => {
461
- installConfig.env[key] = value;
462
- });
463
-
464
- Logger.debug(`Final environment variables: ${JSON.stringify(installConfig.env)}`);
465
-
466
- // --- End of new logic ---
467
-
468
-
469
- // Keep track of success for both installations
470
- let regularSuccess = false;
471
- let insidersSuccess = false;
472
- let errorMessages: string[] = [];
473
-
474
- // Update client-specific settings for both VS Code and VS Code Insiders
475
- if (clientName === 'MSRooCode' || clientName === 'Cline') {
476
- // Update VS Code settings if VS Code is installed
477
- if (isVSCodeInstalled) {
478
- try {
479
- Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
480
- // Pass the modified installConfig
481
- await this.updateClineOrMSRooSettings(regularSettingPath, this.serverName, installConfig, clientName);
482
- regularSuccess = true;
483
- Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
484
- } catch (error) {
485
- const errorMsg = `Error updating VS Code settings: ${error instanceof Error ? error.message : String(error)}`;
486
- errorMessages.push(errorMsg);
487
- console.error(errorMsg);
488
- }
489
- }
490
-
491
- // Update VS Code Insiders settings if VS Code Insiders is installed
492
- if (isVSCodeInsidersInstalled) {
493
- try {
494
- Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
495
- // Pass the modified installConfig
496
- await this.updateClineOrMSRooSettings(insidersSettingPath, this.serverName, installConfig, clientName);
497
- insidersSuccess = true;
498
- Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
499
- } catch (error) {
500
- const errorMsg = `Error updating VS Code Insiders settings: ${error instanceof Error ? error.message : String(error)}`;
501
- errorMessages.push(errorMsg);
502
- console.error(errorMsg);
503
- }
504
- }
505
- } else if (clientName === 'GithubCopilot') {
506
- // Update VS Code settings if VS Code is installed
507
- if (isVSCodeInstalled) {
508
- try {
509
- Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
510
- // Pass the modified installConfig
511
- await this.updateGithubCopilotSettings(regularSettingPath, this.serverName, installConfig);
512
- regularSuccess = true;
513
- Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
514
- } catch (error) {
515
- const errorMsg = `Error updating VS Code settings: ${error instanceof Error ? error.message : String(error)}`;
516
- errorMessages.push(errorMsg);
517
- console.error(errorMsg);
518
- }
519
- }
520
-
521
- // Update VS Code Insiders settings if VS Code Insiders is installed
522
- if (isVSCodeInsidersInstalled) {
523
- try {
524
- Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
525
- // Pass the modified installConfig
526
- await this.updateGithubCopilotSettings(insidersSettingPath, this.serverName, installConfig);
527
- insidersSuccess = true;
528
- Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
529
- } catch (error) {
530
- const errorMsg = `Error updating VS Code Insiders settings: ${error instanceof Error ? error.message : String(error)}`;
531
- errorMessages.push(errorMsg);
532
- console.error(errorMsg);
533
- }
534
- }
535
- } else {
536
- Logger.debug(`No implementation exists for updating settings for client: ${clientName}`);
537
- return {
538
- success: false,
539
- message: `Client ${clientName} is supported but no implementation exists for updating its settings`
540
- };
541
- }
542
-
543
- // Determine overall success status and message
544
- const overallSuccess = regularSuccess || insidersSuccess;
545
- let message = '';
546
-
547
- if (overallSuccess) {
548
- const successDetails = [];
549
- if (regularSuccess) successDetails.push('VS Code');
550
- if (insidersSuccess) successDetails.push('VS Code Insiders');
551
-
552
- // Create a more compact success message with specific paths
553
- const pathDetails = [];
554
- if (regularSuccess) {
555
- pathDetails.push(`\n[VS Code]: ${regularSettingPath}`);
556
- }
557
- if (insidersSuccess) {
558
- pathDetails.push(`\n[VS Code Insiders]: ${insidersSettingPath}`);
559
- }
560
- message = `Successfully installed ${this.serverName} for client: ${clientName}. ${pathDetails.join(' ')}`;
561
-
562
- // Add partial failure information if applicable
563
- if (errorMessages.length > 0) {
564
- message += `\nHowever, some errors occurred:\n${errorMessages.join('\n- ')}`;
565
- }
566
- Logger.debug(`Installation complete: ${message}`);
567
- } else {
568
- // Create a more detailed failure message that includes the detection results
569
- const detectionInfo = [];
570
- if (!isVSCodeInstalled) detectionInfo.push('VS Code not detected');
571
- if (!isVSCodeInsidersInstalled) detectionInfo.push('VS Code Insiders not detected');
572
-
573
- if (errorMessages.length > 0) {
574
- message = `Failed to install ${this.serverName} for client: ${clientName}.\nDetection status: [${detectionInfo.join(', ')}].\nErrors:\n- ${errorMessages.join('\n- ')}`;
575
- } else {
576
- message = `Failed to install ${this.serverName} for client: ${clientName}.\nDetection status: [${detectionInfo.join(', ')}]`;
577
- }
578
- console.error(`Installation failed: ${message}`);
579
- }
580
-
581
- return {
582
- success: overallSuccess,
583
- message: message
584
- };
585
- } catch (error) {
586
- const errorMsg = `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`;
587
- console.error(errorMsg);
588
- return {
589
- success: false,
590
- message: errorMsg
591
- };
592
- }
593
- }
594
-
595
- private async updateClineOrMSRooSettings(
596
- settingPath: string,
597
- serverName: string,
598
- installConfig: any, // Use the processed installConfig
599
- clientName: string
600
- ): Promise<void> {
601
- // Read the Cline/MSRoo settings file
602
- const settings = await readJsonFile(settingPath, true);
603
-
604
- // Initialize mcpServers section if it doesn't exist
605
- if (!settings.mcpServers) {
606
- settings.mcpServers = {};
607
- }
608
-
609
- // Special handling for Windows when command is npx for Cline and MSROO clients
610
- // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
611
- const serverConfigForClient = { ...installConfig };
612
- if (process.platform === 'win32' &&
613
- serverConfigForClient.command === 'npx' &&
614
- (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
615
- // Update command to cmd
616
- serverConfigForClient.command = 'cmd';
617
-
618
- // Add /c and npx at the beginning of args
619
- serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
620
-
621
- // Add APPDATA environment variable pointing to npm directory
622
- if (!serverConfigForClient.env) {
623
- serverConfigForClient.env = {};
624
- }
625
-
626
- // Dynamically get npm path and set APPDATA to it
627
- const npmPath = await this.getNpmPath();
628
- serverConfigForClient.env['APPDATA'] = npmPath;
629
- Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
630
- }
631
- // Convert backslashes to forward slashes in args paths
632
- if (serverConfigForClient.args) {
633
- serverConfigForClient.args = serverConfigForClient.args.map((arg: string) =>
634
- typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg
635
- );
636
- }
637
-
638
- // Add or update the server configuration
639
- settings.mcpServers[serverName] = {
640
- command: serverConfigForClient.command,
641
- args: serverConfigForClient.args,
642
- env: serverConfigForClient.env,
643
- autoApprove: [],
644
- disabled: false,
645
- alwaysAllow: []
646
- };
647
- Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
648
-
649
- // Write the updated settings back to the file
650
- await writeJsonFile(settingPath, settings);
651
- }
652
-
653
- private async updateGithubCopilotSettings(
654
- settingPath: string,
655
- serverName: string,
656
- installConfig: any // Use the processed installConfig
657
- ): Promise<void> {
658
- // Read the VS Code settings.json file
659
- const settings = await readJsonFile(settingPath, true);
660
-
661
- // Initialize the mcp section if it doesn't exist
662
- if (!settings.mcp) {
663
- settings.mcp = {
664
- servers: {},
665
- inputs: []
666
- };
667
- }
668
-
669
- if (!settings.mcp.servers) {
670
- settings.mcp.servers = {};
671
- }
672
-
673
- // Use a copy to avoid modifying the passed installConfig directly
674
- const serverConfigForClient = { ...installConfig };
675
-
676
- // Convert backslashes to forward slashes in args paths
677
- if (serverConfigForClient.args) {
678
- serverConfigForClient.args = serverConfigForClient.args.map((arg: string) =>
679
- typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg
680
- );
681
- }
682
-
683
- // Add or update the server configuration
684
- settings.mcp.servers[serverName] = {
685
- command: serverConfigForClient.command,
686
- args: serverConfigForClient.args,
687
- env: serverConfigForClient.env
688
- };
689
- Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
690
-
691
- // Write the updated settings back to the file
692
- await writeJsonFile(settingPath, settings);
693
- }
694
-
201
+ /**
202
+ * Install all specified clients
203
+ */
695
204
  async install(options: ServerInstallOptions): Promise<ServerOperationResult> {
696
205
  const initialStatuses: OperationStatus[] = [];
697
206
 
698
- // Start installation for each client asynchronously and collect initial statuses
699
- // Pass options down to installClient
207
+ // Start installation for each client asynchronously
700
208
  const installPromises = this.clients.map(async (clientName) => {
701
- const initialStatus = await this.installClient(clientName, options);
702
- initialStatuses.push(initialStatus);
703
- return initialStatus;
209
+ const status = await this.installClient(clientName, options);
210
+ initialStatuses.push(status);
211
+ return status;
704
212
  });
705
213
 
706
- // Wait for all initial statuses (but actual installation continues asynchronously)
214
+ // Wait for all installations to complete
707
215
  await Promise.all(installPromises);
708
216
 
709
- // Return initial result showing installations have been initiated
217
+ // Return result
710
218
  return {
711
219
  success: true,
712
- message: 'Client installations initiated successfully',
220
+ message: 'Client installations completed',
713
221
  status: initialStatuses
714
222
  };
715
223
  }