imcp 0.1.5 → 0.1.6

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 (186) 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/.github/workflows/build.yml +28 -0
  6. package/.roo/rules-code/rules.md +88 -0
  7. package/docs/ONBOARDING_PAGE_DESIGN.md +260 -0
  8. package/docs/Telemetry.md +136 -0
  9. package/memory-bank/activeContext.md +26 -0
  10. package/memory-bank/decisionLog.md +91 -0
  11. package/memory-bank/productContext.md +41 -0
  12. package/memory-bank/progress.md +35 -0
  13. package/memory-bank/systemPatterns.md +10 -0
  14. package/package.json +1 -5
  15. package/src/cli/commands/install.ts +139 -0
  16. package/src/cli/commands/list.ts +113 -0
  17. package/src/cli/commands/pull.ts +16 -0
  18. package/src/cli/commands/serve.ts +39 -0
  19. package/src/cli/commands/uninstall.ts +64 -0
  20. package/src/cli/index.ts +82 -0
  21. package/src/core/installers/clients/BaseClientInstaller.ts +341 -0
  22. package/src/core/installers/clients/ClientInstaller.ts +222 -0
  23. package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
  24. package/src/core/installers/clients/ClineInstaller.ts +35 -0
  25. package/src/core/installers/clients/ExtensionInstaller.ts +165 -0
  26. package/src/core/installers/clients/GithubCopilotInstaller.ts +79 -0
  27. package/src/core/installers/clients/MSRooCodeInstaller.ts +32 -0
  28. package/src/core/installers/index.ts +11 -0
  29. package/src/core/installers/requirements/BaseInstaller.ts +85 -0
  30. package/src/core/installers/requirements/CommandInstaller.ts +231 -0
  31. package/src/core/installers/requirements/GeneralInstaller.ts +133 -0
  32. package/src/core/installers/requirements/InstallerFactory.ts +114 -0
  33. package/src/core/installers/requirements/NpmInstaller.ts +271 -0
  34. package/src/core/installers/requirements/NugetInstaller.ts +203 -0
  35. package/src/core/installers/requirements/PipInstaller.ts +207 -0
  36. package/src/core/installers/requirements/RequirementInstaller.ts +42 -0
  37. package/src/core/loaders/ConfigurationLoader.ts +298 -0
  38. package/src/core/loaders/ConfigurationProvider.ts +462 -0
  39. package/src/core/loaders/InstallOperationManager.ts +367 -0
  40. package/src/core/loaders/ServerSchemaLoader.ts +117 -0
  41. package/src/core/loaders/ServerSchemaProvider.ts +99 -0
  42. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  43. package/src/core/metadatas/constants.ts +122 -0
  44. package/src/core/metadatas/recordingConstants.ts +65 -0
  45. package/src/core/metadatas/types.ts +202 -0
  46. package/src/core/onboard/FeedOnboardService.ts +501 -0
  47. package/src/core/onboard/OnboardProcessor.ts +356 -0
  48. package/src/core/onboard/OnboardStatus.ts +60 -0
  49. package/src/core/onboard/OnboardStatusManager.ts +416 -0
  50. package/src/core/validators/FeedValidator.ts +135 -0
  51. package/src/core/validators/IServerValidator.ts +21 -0
  52. package/src/core/validators/SSEServerValidator.ts +43 -0
  53. package/src/core/validators/ServerValidatorFactory.ts +51 -0
  54. package/src/core/validators/StdioServerValidator.ts +313 -0
  55. package/src/index.ts +44 -0
  56. package/src/services/InstallationService.ts +102 -0
  57. package/src/services/MCPManager.ts +249 -0
  58. package/src/services/RequirementService.ts +627 -0
  59. package/src/services/ServerService.ts +161 -0
  60. package/src/services/TelemetryService.ts +59 -0
  61. package/src/utils/UpdateCheckTracker.ts +86 -0
  62. package/src/utils/adoUtils.ts +293 -0
  63. package/src/utils/clientUtils.ts +72 -0
  64. package/src/utils/feedUtils.ts +31 -0
  65. package/src/utils/githubAuth.ts +212 -0
  66. package/src/utils/githubUtils.ts +164 -0
  67. package/src/utils/logger.ts +195 -0
  68. package/src/utils/macroExpressionUtils.ts +104 -0
  69. package/src/utils/osUtils.ts +700 -0
  70. package/src/utils/versionUtils.ts +114 -0
  71. package/src/web/contract/serverContract.ts +74 -0
  72. package/src/web/public/css/detailsWidget.css +235 -0
  73. package/src/web/public/css/modal.css +757 -0
  74. package/src/web/public/css/notifications.css +101 -0
  75. package/src/web/public/css/onboard.css +107 -0
  76. package/src/web/public/css/serverCategoryList.css +120 -0
  77. package/src/web/public/css/serverDetails.css +139 -0
  78. package/src/web/public/index.html +359 -0
  79. package/src/web/public/js/api.js +132 -0
  80. package/src/web/public/js/detailsWidget.js +264 -0
  81. package/src/web/public/js/flights/flights.js +127 -0
  82. package/src/web/public/js/modal/index.js +52 -0
  83. package/src/web/public/js/modal/installModal.js +162 -0
  84. package/src/web/public/js/modal/installation.js +266 -0
  85. package/src/web/public/js/modal/loadingModal.js +182 -0
  86. package/src/web/public/js/modal/modalSetup.js +595 -0
  87. package/src/web/public/js/modal/modalUtils.js +37 -0
  88. package/src/web/public/js/modal/versionUtils.js +20 -0
  89. package/src/web/public/js/modal.js +42 -0
  90. package/src/web/public/js/notifications.js +137 -0
  91. package/src/web/public/js/onboard/formProcessor.js +1037 -0
  92. package/src/web/public/js/onboard/index.js +374 -0
  93. package/src/web/public/js/onboard/publishHandler.js +172 -0
  94. package/src/web/public/js/onboard/state.js +76 -0
  95. package/src/web/public/js/onboard/templates.js +342 -0
  96. package/src/web/public/js/onboard/uiHandlers.js +1076 -0
  97. package/src/web/public/js/onboard/validationHandlers.js +493 -0
  98. package/src/web/public/js/serverCategoryDetails.js +364 -0
  99. package/src/web/public/js/serverCategoryList.js +241 -0
  100. package/src/web/public/js/settings.js +314 -0
  101. package/src/web/public/modal.html +84 -0
  102. package/src/web/public/onboard.html +296 -0
  103. package/src/web/public/settings.html +135 -0
  104. package/src/web/public/styles.css +277 -0
  105. package/src/web/server.ts +478 -0
  106. package/tsconfig.json +18 -0
  107. package/wiki/Installation.md +3 -0
  108. package/wiki/Publish.md +3 -0
  109. package/dist/cli/commands/install.js.map +0 -1
  110. package/dist/cli/commands/list.js.map +0 -1
  111. package/dist/cli/commands/pull.js.map +0 -1
  112. package/dist/cli/commands/serve.js.map +0 -1
  113. package/dist/cli/commands/start.js.map +0 -1
  114. package/dist/cli/commands/sync.js.map +0 -1
  115. package/dist/cli/commands/uninstall.js.map +0 -1
  116. package/dist/cli/index.js.map +0 -1
  117. package/dist/core/ConfigurationLoader.js.map +0 -1
  118. package/dist/core/ConfigurationProvider.js.map +0 -1
  119. package/dist/core/InstallationService.js.map +0 -1
  120. package/dist/core/MCPManager.js.map +0 -1
  121. package/dist/core/RequirementService.js.map +0 -1
  122. package/dist/core/ServerSchemaLoader.js.map +0 -1
  123. package/dist/core/ServerSchemaProvider.js.map +0 -1
  124. package/dist/core/constants.js.map +0 -1
  125. package/dist/core/installers/BaseInstaller.js.map +0 -1
  126. package/dist/core/installers/ClientInstaller.js.map +0 -1
  127. package/dist/core/installers/CommandInstaller.js.map +0 -1
  128. package/dist/core/installers/GeneralInstaller.js.map +0 -1
  129. package/dist/core/installers/InstallerFactory.js.map +0 -1
  130. package/dist/core/installers/NpmInstaller.js.map +0 -1
  131. package/dist/core/installers/PipInstaller.js.map +0 -1
  132. package/dist/core/installers/RequirementInstaller.js.map +0 -1
  133. package/dist/core/installers/clients/BaseClientInstaller.js.map +0 -1
  134. package/dist/core/installers/clients/ClientInstaller.js.map +0 -1
  135. package/dist/core/installers/clients/ClientInstallerFactory.js.map +0 -1
  136. package/dist/core/installers/clients/ClineInstaller.js.map +0 -1
  137. package/dist/core/installers/clients/ExtensionInstaller.js.map +0 -1
  138. package/dist/core/installers/clients/GithubCopilotInstaller.js.map +0 -1
  139. package/dist/core/installers/clients/MSRooCodeInstaller.js.map +0 -1
  140. package/dist/core/installers/index.js.map +0 -1
  141. package/dist/core/installers/requirements/BaseInstaller.js.map +0 -1
  142. package/dist/core/installers/requirements/CommandInstaller.js.map +0 -1
  143. package/dist/core/installers/requirements/GeneralInstaller.js.map +0 -1
  144. package/dist/core/installers/requirements/InstallerFactory.js.map +0 -1
  145. package/dist/core/installers/requirements/NpmInstaller.js.map +0 -1
  146. package/dist/core/installers/requirements/NugetInstaller.js.map +0 -1
  147. package/dist/core/installers/requirements/PipInstaller.js.map +0 -1
  148. package/dist/core/installers/requirements/RequirementInstaller.js.map +0 -1
  149. package/dist/core/loaders/ConfigurationLoader.js.map +0 -1
  150. package/dist/core/loaders/ConfigurationProvider.js.map +0 -1
  151. package/dist/core/loaders/InstallOperationManager.js.map +0 -1
  152. package/dist/core/loaders/ServerSchemaLoader.js.map +0 -1
  153. package/dist/core/loaders/ServerSchemaProvider.js.map +0 -1
  154. package/dist/core/loaders/SystemSettingsManager.js.map +0 -1
  155. package/dist/core/metadatas/constants.js.map +0 -1
  156. package/dist/core/metadatas/recordingConstants.js.map +0 -1
  157. package/dist/core/metadatas/types.js.map +0 -1
  158. package/dist/core/onboard/FeedOnboardService.js.map +0 -1
  159. package/dist/core/onboard/OnboardProcessor.js.map +0 -1
  160. package/dist/core/onboard/OnboardStatus.js.map +0 -1
  161. package/dist/core/onboard/OnboardStatusManager.js.map +0 -1
  162. package/dist/core/types.js.map +0 -1
  163. package/dist/core/validators/FeedValidator.js.map +0 -1
  164. package/dist/core/validators/IServerValidator.js.map +0 -1
  165. package/dist/core/validators/SSEServerValidator.js.map +0 -1
  166. package/dist/core/validators/ServerValidatorFactory.js.map +0 -1
  167. package/dist/core/validators/StdioServerValidator.js.map +0 -1
  168. package/dist/index.js.map +0 -1
  169. package/dist/services/InstallRequestValidator.js.map +0 -1
  170. package/dist/services/InstallationService.js.map +0 -1
  171. package/dist/services/MCPManager.js.map +0 -1
  172. package/dist/services/RequirementService.js.map +0 -1
  173. package/dist/services/ServerService.js.map +0 -1
  174. package/dist/services/TelemetryService.js.map +0 -1
  175. package/dist/utils/UpdateCheckTracker.js.map +0 -1
  176. package/dist/utils/adoUtils.js.map +0 -1
  177. package/dist/utils/clientUtils.js.map +0 -1
  178. package/dist/utils/feedUtils.js.map +0 -1
  179. package/dist/utils/githubAuth.js.map +0 -1
  180. package/dist/utils/githubUtils.js.map +0 -1
  181. package/dist/utils/logger.js.map +0 -1
  182. package/dist/utils/macroExpressionUtils.js.map +0 -1
  183. package/dist/utils/osUtils.js.map +0 -1
  184. package/dist/utils/versionUtils.js.map +0 -1
  185. package/dist/web/contract/serverContract.js.map +0 -1
  186. package/dist/web/server.js.map +0 -1
@@ -0,0 +1,341 @@
1
+ import { Logger } from '../../../utils/logger.js';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import { isCommandAvailable, getNpmExecutablePath } from '../../../utils/osUtils.js';
5
+ import { ExtensionInstaller } from './ExtensionInstaller.js';
6
+ import { SUPPORTED_CLIENTS } from '../../metadatas/constants.js';
7
+ import {
8
+ OperationStatus,
9
+ McpConfig,
10
+ ServerInstallOptions,
11
+ } from '../../metadatas/types.js';
12
+ import {
13
+ MACRO_EXPRESSIONS,
14
+ MacroResolverFunctions
15
+ } from '../../../utils/macroExpressionUtils.js';
16
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
17
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
18
+
19
+ const execAsync = promisify(exec);
20
+
21
+ /**
22
+ * Base class for client installers with shared functionality
23
+ */
24
+ export abstract class BaseClientInstaller {
25
+ protected abstract readonly clientName: string;
26
+ /**
27
+ * Generate a unique operation ID for tracking installations
28
+ */
29
+ protected generateOperationId(): string {
30
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
31
+ }
32
+
33
+ /**
34
+ * Set up installation configuration with environment variables and arguments
35
+ * Handles Python environment configuration if specified
36
+ */
37
+ protected async setupInstallConfig(
38
+ serverConfig: McpConfig,
39
+ options: ServerInstallOptions
40
+ ): Promise<any> {
41
+ const finalConfig: any = { ...serverConfig.installation };
42
+ finalConfig.mode = serverConfig.mode;
43
+
44
+ // Handle command line arguments
45
+ if (options.args && options.args.length > 0) {
46
+ Logger.debug(`Using args from ServerInstallOptions: ${options.args.join(' ')}`);
47
+ finalConfig.args = options.args;
48
+ }
49
+
50
+ // Handle environment variables
51
+ const baseEnv = finalConfig.env || {};
52
+ const defaultEnv: Record<string, string> = {};
53
+ for (const [key, config] of Object.entries(baseEnv)) {
54
+ const envConfig = config as any;
55
+ if (envConfig.Default) {
56
+ defaultEnv[key] = envConfig.Default;
57
+ }
58
+ }
59
+ finalConfig.env = { ...defaultEnv, ...(options.env || {}) };
60
+
61
+ // Handle macro expressions
62
+ await this._resolveConfigMacros(finalConfig, options);
63
+
64
+ Logger.debug(`Final installation config: ${JSON.stringify(finalConfig)}`);
65
+ return finalConfig;
66
+ }
67
+
68
+ /**
69
+ * Resolves and replaces known macro expressions (e.g., ${PYTHON_PACKAGE}, ${NPMPATH}, ${BROWSER_PATH})
70
+ * in the provided configuration object's 'args' and 'env' properties.
71
+ * This method directly mutates the finalConfig object.
72
+ * @param finalConfig The configuration object to modify.
73
+ * @param options Server installation options which may contain settings like pythonEnv or npmPath.
74
+ */
75
+ private async _resolveConfigMacros(
76
+ finalConfig: any,
77
+ options: ServerInstallOptions
78
+ ): Promise<void> {
79
+ for (const macro of Object.values(MACRO_EXPRESSIONS)) {
80
+ const isMacroInArgs = finalConfig.args?.some((arg: string) => typeof arg === 'string' && arg.includes(macro));
81
+ const isMacroInEnv = Object.values(finalConfig.env || {}).some(value => typeof value === 'string' && value.includes(macro));
82
+
83
+ if (!isMacroInArgs && !isMacroInEnv) {
84
+ Logger.debug(`Macro ${macro} not found in args or env, skipping resolution.`);
85
+ continue;
86
+ }
87
+
88
+ const resolver = MacroResolverFunctions[macro];
89
+ if (resolver) {
90
+ const resolvedValue = await resolver(finalConfig, options);
91
+ if (resolvedValue !== undefined) {
92
+ // Replace in args
93
+ if (finalConfig.args) {
94
+ const originalArgsString = finalConfig.args.join(' ');
95
+ finalConfig.args = finalConfig.args.map((arg: string) =>
96
+ typeof arg === 'string' && arg.includes(macro)
97
+ ? arg.replace(new RegExp(macro.replace(/[${}]/g, '\\$&'), 'g'), resolvedValue)
98
+ : arg
99
+ );
100
+ if (finalConfig.args.join(' ') !== originalArgsString) {
101
+ Logger.debug(`Args after ${macro} ('${resolvedValue}') replacement: ${finalConfig.args.join(' ')}`);
102
+ }
103
+ }
104
+ // Replace in env
105
+ if (finalConfig.env) {
106
+ const originalEnvJson = JSON.stringify(finalConfig.env);
107
+ for (const key in finalConfig.env) {
108
+ if (typeof finalConfig.env[key] === 'string' && (finalConfig.env[key] as string).includes(macro)) {
109
+ finalConfig.env[key] = (finalConfig.env[key] as string).replace(new RegExp(macro.replace(/[${}]/g, '\\$&'), 'g'), resolvedValue);
110
+ }
111
+ }
112
+ if (JSON.stringify(finalConfig.env) !== originalEnvJson) {
113
+ Logger.debug(`Env after ${macro} ('${resolvedValue}') replacement: ${JSON.stringify(finalConfig.env)}`);
114
+ }
115
+ }
116
+ } else {
117
+ Logger.debug(`Could not resolve value for macro: ${macro}. It will not be replaced.`);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Handle NPX commands for Windows platform
125
+ */
126
+ protected async handleWindowsNpx(config: any): Promise<any> {
127
+ if (process.platform === 'win32' && config.command === 'npx') {
128
+ const npmPath = await getNpmExecutablePath();
129
+ return {
130
+ ...config,
131
+ command: 'cmd',
132
+ args: ['/c', 'npx', ...config.args],
133
+ env: {
134
+ ...config.env,
135
+ 'APPDATA': npmPath
136
+ }
137
+ };
138
+ }
139
+ return config;
140
+ }
141
+
142
+ /**
143
+ * Initialize settings object with client-specific structure
144
+ * Override in child classes to provide custom initialization
145
+ */
146
+ protected initializeSettings(settings: any): void {
147
+ if (!settings.mcpServers) {
148
+ settings.mcpServers = {};
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Handle stdio mode configuration
154
+ * Override in child classes to provide custom stdio configuration
155
+ */
156
+ protected async handleStdioMode(settings: any, serverName: string, finalConfig: any): Promise<void> {
157
+ // Convert backslashes to forward slashes in args
158
+ if (finalConfig.args) {
159
+ finalConfig.args = finalConfig.args.map((arg: string) =>
160
+ typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg
161
+ );
162
+ }
163
+ settings.mcpServers[serverName] = {
164
+ command: finalConfig.command,
165
+ args: finalConfig.args,
166
+ env: finalConfig.env,
167
+ autoApprove: [],
168
+ disabled: false,
169
+ alwaysAllow: []
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Handle SSE mode configuration
175
+ * Override in child classes to provide custom SSE configuration
176
+ */
177
+ protected handleSseMode(settings: any, serverName: string, installConfig: any): void {
178
+ settings.mcpServers[serverName] = {
179
+ type: 'sse',
180
+ url: installConfig.url
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Checks if VS Code or VS Code Insiders is installed and installs the client extension.
186
+ * @param operationId The operation ID for tracking.
187
+ * @returns An OperationStatus object if checks fail or installation fails, otherwise undefined.
188
+ */
189
+ protected async checkVSCodeAndInstallExtension(operationId: string): Promise<OperationStatus | undefined> {
190
+ // Check if VS Code or VS Code Insiders is installed
191
+ const isVSCodeInstalled = await isCommandAvailable('code');
192
+ const isVSCodeInsidersInstalled = await isCommandAvailable('code-insiders');
193
+
194
+ if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
195
+ return {
196
+ status: 'failed',
197
+ type: 'install',
198
+ target: 'server',
199
+ message: 'Failed to install as neither VS Code nor VS Code Insiders are installed on this system. Please run `code` or `code-insiders` to make sure they are installed. Relaunch imcp after installation.',
200
+ operationId
201
+ };
202
+ }
203
+
204
+ // Install extension
205
+ const extensionResult = await ExtensionInstaller.installExtension(this.clientName);
206
+ if (!extensionResult) {
207
+ Logger.debug(`Failed to install ${this.clientName} extension`);
208
+ return {
209
+ status: 'failed',
210
+ type: 'install',
211
+ target: 'server',
212
+ message: `Failed to install ${this.clientName} extension`,
213
+ operationId
214
+ };
215
+ }
216
+ return undefined;
217
+ }
218
+
219
+ /**
220
+ * Update VS Code settings for both VS Code and VS Code Insiders if installed
221
+ * @param serverName The name of the server to configure
222
+ * @param installConfig The installation configuration
223
+ * @returns Array of results indicating success/failure for each VS Code variant
224
+ */
225
+ protected async updateVSCodeSettings(serverName: string, installConfig: any): Promise<Array<{ success: boolean; path: string; error?: string }>> {
226
+ const results: Array<{ success: boolean; path: string; error?: string }> = [];
227
+ const isVSCodeInstalled = await isCommandAvailable('code');
228
+ const isVSCodeInsidersInstalled = await isCommandAvailable('code-insiders');
229
+
230
+ // Update settings for VS Code if installed
231
+ if (isVSCodeInstalled) {
232
+ try {
233
+ const settingPath = SUPPORTED_CLIENTS[this.clientName].codeSettingPath;
234
+ await this.setupClientSettings(settingPath, serverName, installConfig);
235
+ results.push({ success: true, path: settingPath });
236
+ } catch (error) {
237
+ results.push({
238
+ success: false,
239
+ path: SUPPORTED_CLIENTS[this.clientName].codeSettingPath,
240
+ error: error instanceof Error ? error.message : String(error)
241
+ });
242
+ }
243
+ }
244
+
245
+ // Update settings for VS Code Insiders if installed
246
+ if (isVSCodeInsidersInstalled) {
247
+ try {
248
+ const settingPath = SUPPORTED_CLIENTS[this.clientName].codeInsiderSettingPath;
249
+ await this.setupClientSettings(settingPath, serverName, installConfig);
250
+ results.push({ success: true, path: settingPath });
251
+ } catch (error) {
252
+ results.push({
253
+ success: false,
254
+ path: SUPPORTED_CLIENTS[this.clientName].codeInsiderSettingPath,
255
+ error: error instanceof Error ? error.message : String(error)
256
+ });
257
+ }
258
+ }
259
+
260
+ return results;
261
+ }
262
+
263
+ /**
264
+ * Install the client
265
+ * @param serverConfig Server configuration
266
+ * @param options Installation options including environment variables and arguments
267
+ */
268
+ async install(serverConfig: McpConfig, options: ServerInstallOptions, categoryName?: string): Promise<OperationStatus> {
269
+ const operationId = this.generateOperationId();
270
+
271
+ const recorder = InstallOperationManager.getInstance(categoryName || serverConfig.name, serverConfig.name);
272
+ return await recorder.recording(
273
+ async () => {
274
+ await recorder.recording(
275
+ () => this.checkVSCodeAndInstallExtension(operationId), {
276
+ stepName: RecordingConstants.STEP_CHECK_VSCODE_AND_INSTALL_EXTENSION,
277
+ onResult: (result) => result?.status !== 'failed'
278
+ }
279
+ )
280
+
281
+ const installConfig = await recorder.recording(
282
+ () => this.setupInstallConfig(serverConfig, options), {
283
+ stepName: RecordingConstants.STEP_SETUP_INSTALLATION_CONFIG
284
+ });
285
+
286
+ if (serverConfig.mode) {
287
+ installConfig.mode = serverConfig.mode;
288
+ }
289
+
290
+ const results = await recorder.recording(
291
+ () => this.updateVSCodeSettings(serverConfig.name, installConfig), {
292
+ stepName: RecordingConstants.STEP_UPDATE_VSCODE_SETTINGS,
293
+ onResult: (result) => result?.some(r => r.success)
294
+ }
295
+ );
296
+
297
+ // Determine overall success
298
+ const anySuccess = results.some(r => r.success);
299
+ const successPaths = results.filter(r => r.success).map(r => r.path);
300
+ const errors = results.filter(r => !r.success).map(r => r.error);
301
+
302
+ const finalMessage = anySuccess
303
+ ? `Successfully installed ${this.clientName} client. Updated settings in: ${successPaths.join(', ')}`
304
+ : `Failed to install ${this.clientName} client. Errors: ${errors.join('; ')}`;
305
+
306
+ return {
307
+ status: anySuccess ? 'completed' : 'failed',
308
+ type: 'install',
309
+ target: 'server',
310
+ message: finalMessage,
311
+ operationId,
312
+ error: anySuccess ? undefined : errors.join('; ')
313
+ };
314
+ },
315
+ {
316
+ stepName: RecordingConstants.STEP_INSTALLATION,
317
+ onResult: (result) => result?.status !== 'failed',
318
+ endMessage: (result) => (result as OperationStatus)?.message,
319
+ onError: (error) => {
320
+ const errorMsg = `Unexpected error installing ${this.clientName} client: ${error instanceof Error ? error.message : String(error)}`;
321
+ return {
322
+ result: {
323
+ status: 'failed',
324
+ type: 'install',
325
+ target: 'server',
326
+ message: errorMsg,
327
+ operationId,
328
+ error: error instanceof Error ? error.message : String(error)
329
+ }, message: errorMsg
330
+ };
331
+ }
332
+ }
333
+ );
334
+
335
+ }
336
+
337
+ /**
338
+ * Abstract method that must be implemented by client-specific installers
339
+ */
340
+ abstract setupClientSettings(settingPath: string, serverName: string, installConfig: any): Promise<void>;
341
+ }
@@ -0,0 +1,222 @@
1
+ import { ConfigurationProvider } from '../../loaders/ConfigurationProvider.js';
2
+ import { SUPPORTED_CLIENTS } from '../../metadatas/constants.js';
3
+ import {
4
+ ServerOperationResult,
5
+ OperationStatus,
6
+ ServerInstallOptions,
7
+ McpConfig,
8
+ RequirementStatus
9
+ } from '../../metadatas/types.js';
10
+ import { Logger } from '../../../utils/logger.js';
11
+ import { ClientInstallerFactory } from './ClientInstallerFactory.js';
12
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
13
+ import { stat } from 'fs';
14
+
15
+ /**
16
+ * Main client installer class that orchestrates client installation process
17
+ * Handles requirements checking and delegates to specific client installers
18
+ */
19
+ export class ClientInstaller {
20
+ private configProvider: ConfigurationProvider;
21
+
22
+ constructor(
23
+ private categoryName: string,
24
+ private serverName: string,
25
+ private clients: string[]
26
+ ) {
27
+ this.configProvider = ConfigurationProvider.getInstance();
28
+ }
29
+
30
+ /**
31
+ * Install all specified clients
32
+ */
33
+ async install(options: ServerInstallOptions): Promise<ServerOperationResult> {
34
+ const initialStatuses: OperationStatus[] = [];
35
+
36
+ // Start installation for each client asynchronously
37
+ const installPromises = this.clients.map(async (clientName) => {
38
+ const status = await this.installClient(clientName, options);
39
+ initialStatuses.push(status);
40
+ return status;
41
+ });
42
+
43
+ // Wait for all installations to complete
44
+ await Promise.all(installPromises);
45
+
46
+ // Return result
47
+ return {
48
+ success: true,
49
+ message: 'Client installations completed',
50
+ status: initialStatuses
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Install client with requirements checking
56
+ */
57
+ private async installClient(clientName: string, options: ServerInstallOptions): Promise<OperationStatus> {
58
+ const operationId = this.generateOperationId();
59
+
60
+ // Check if client is supported
61
+ if (!ClientInstallerFactory.isClientSupported(clientName)) {
62
+ return {
63
+ status: 'failed',
64
+ type: 'install',
65
+ target: 'server',
66
+ message: `Unsupported client: ${clientName}`,
67
+ operationId
68
+ };
69
+ }
70
+
71
+ // Create initial operation status
72
+ const initialStatus: OperationStatus = {
73
+ status: 'pending',
74
+ type: 'install',
75
+ target: 'server',
76
+ message: `Initializing installation for client: ${clientName}`,
77
+ operationId
78
+ };
79
+
80
+ // Async installation process
81
+ this.processInstallation(clientName, operationId, options)
82
+ .then((status) => {
83
+ if (status.status === 'completed' || status.status === 'failed') {
84
+ InstallOperationManager
85
+ .getInstance(this.categoryName, this.serverName)
86
+ .markOverallStatus(status.status)
87
+ }
88
+
89
+ this.configProvider.updateServerOperationStatus(
90
+ this.categoryName,
91
+ this.serverName,
92
+ clientName,
93
+ status
94
+ );
95
+ })
96
+ .catch((error) => {
97
+ this.configProvider.updateServerOperationStatus(
98
+ this.categoryName,
99
+ this.serverName,
100
+ clientName,
101
+ {
102
+ status: 'failed',
103
+ type: 'install',
104
+ target: 'server',
105
+ message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
106
+ operationId,
107
+ error: error instanceof Error ? error.message : String(error)
108
+ }
109
+ );
110
+ Logger.error(`Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`);
111
+ InstallOperationManager
112
+ .getInstance(this.categoryName, this.serverName)
113
+ .markOverallStatus('failed', error)
114
+ });
115
+
116
+ // Update server status with initial client installation status
117
+ await this.configProvider.updateServerOperationStatus(
118
+ this.categoryName,
119
+ this.serverName,
120
+ clientName,
121
+ initialStatus
122
+ );
123
+ return initialStatus;
124
+ }
125
+
126
+ private async processInstallation(clientName: string, operationId: string, options: ServerInstallOptions): Promise<OperationStatus> {
127
+ const requirementsReady = await this.checkRequirements(operationId, clientName, options);
128
+ if (!requirementsReady) {
129
+ const failedStatus: OperationStatus = {
130
+ status: 'failed',
131
+ type: 'install',
132
+ target: 'server',
133
+ message: `Requirements not ready for client: ${clientName} after timeout`,
134
+ operationId
135
+ };
136
+ return failedStatus;
137
+ }
138
+
139
+ // Create client-specific installer
140
+ const installer = ClientInstallerFactory.getInstaller(clientName);
141
+
142
+ if (!installer) {
143
+ throw new Error(`Failed to create installer for client: ${clientName}`);
144
+ }
145
+
146
+ const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
147
+ if (!serverConfig) {
148
+ throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
149
+ }
150
+
151
+ // If we've reached here, requirements are ready - update status to in-progress
152
+ const inProgressStatus: OperationStatus = {
153
+ status: 'in-progress',
154
+ type: 'install',
155
+ target: 'server',
156
+ message: `Installing client: ${clientName}`,
157
+ operationId: operationId
158
+ };
159
+
160
+ await this.configProvider.updateServerOperationStatus(
161
+ this.categoryName,
162
+ this.serverName,
163
+ clientName,
164
+ inProgressStatus
165
+ );
166
+ // Install client
167
+ return await installer.install(serverConfig, options, this.categoryName);
168
+ }
169
+
170
+ /**
171
+ * Generate a unique operation ID for tracking installations
172
+ */
173
+ private generateOperationId(): string {
174
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
175
+ }
176
+
177
+ /**
178
+ * Check if server requirements are ready
179
+ * Waits for requirements to be ready with timeout
180
+ */
181
+ private async checkRequirements(operationId: string, clientName: string, options: ServerInstallOptions): Promise<boolean> {
182
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
183
+
184
+ if (!requirementsReady) {
185
+ const pendingStatus: OperationStatus = {
186
+ status: 'pending',
187
+ type: 'install',
188
+ target: 'server',
189
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
190
+ operationId
191
+ };
192
+
193
+ await this.configProvider.updateServerOperationStatus(
194
+ this.categoryName,
195
+ this.serverName,
196
+ clientName,
197
+ pendingStatus
198
+ );
199
+
200
+ // Set up periodic checking with timeout
201
+ const startTime = Date.now();
202
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
203
+ const intervalMs = 5 * 1000; // 5 seconds
204
+
205
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
206
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
207
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
208
+ }
209
+ }
210
+
211
+ var requirementsStatus: RequirementStatus[] = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
212
+
213
+ // Find first non-empty npmPath from requirements status
214
+ const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
215
+ if (npmPathRequirement && npmPathRequirement.npmPath) {
216
+ options.settings = options.settings || {};
217
+ options.settings.npmPath = npmPathRequirement.npmPath;
218
+ }
219
+
220
+ return requirementsReady;
221
+ }
222
+ }
@@ -0,0 +1,43 @@
1
+ import { MSRooCodeInstaller } from './MSRooCodeInstaller.js';
2
+ import { ClineInstaller } from './ClineInstaller.js';
3
+ import { GithubCopilotInstaller } from './GithubCopilotInstaller.js';
4
+ import { BaseClientInstaller } from './BaseClientInstaller.js';
5
+ import { SUPPORTED_CLIENTS } from '../../metadatas/constants.js';
6
+
7
+ /**
8
+ * Factory for creating client-specific installers
9
+ * Handles creation of appropriate installer based on client type
10
+ */
11
+ export class ClientInstallerFactory {
12
+ private static readonly installerMap: Record<string, new () => BaseClientInstaller> = {
13
+ [Object.keys(SUPPORTED_CLIENTS)[0]]: MSRooCodeInstaller,
14
+ [Object.keys(SUPPORTED_CLIENTS)[1]]: ClineInstaller,
15
+ [Object.keys(SUPPORTED_CLIENTS)[2]]: GithubCopilotInstaller
16
+ };
17
+
18
+ /**
19
+ * Get a client installer instance based on client type
20
+ * @param clientName Name of the client to get installer for
21
+ * @returns Client-specific installer instance or undefined if client not supported
22
+ */
23
+ static getInstaller(
24
+ clientName: string
25
+ ): BaseClientInstaller | undefined {
26
+ // Check if client is supported
27
+ if (!SUPPORTED_CLIENTS[clientName]) {
28
+ return undefined;
29
+ }
30
+
31
+ const InstallerClass = this.installerMap[clientName as keyof typeof this.installerMap];
32
+ return InstallerClass ? new InstallerClass() : undefined;
33
+ }
34
+
35
+ /**
36
+ * Check if a client is supported
37
+ * @param clientName Name of the client to check
38
+ * @returns True if client is supported, false otherwise
39
+ */
40
+ static isClientSupported(clientName: string): boolean {
41
+ return !!SUPPORTED_CLIENTS[clientName];
42
+ }
43
+ }
@@ -0,0 +1,35 @@
1
+ import { BaseClientInstaller } from './BaseClientInstaller.js';
2
+ import { readJsonFile, writeJsonFile } from '../../../utils/clientUtils.js';
3
+
4
+ /**
5
+ * Cline client installer implementation
6
+ * Handles installation of Cline client including extension installation and settings configuration
7
+ */
8
+ export class ClineInstaller extends BaseClientInstaller {
9
+ protected readonly clientName: string = 'Cline';
10
+
11
+ /**
12
+ * Set up Cline client settings
13
+ * Updates VS Code settings with MCP server configuration
14
+ */
15
+ async setupClientSettings(settingPath: string, serverName: string, installConfig: any): Promise<void> {
16
+ // Read existing settings
17
+ const settings = await readJsonFile(settingPath, true);
18
+
19
+ // Initialize settings with client-specific structure
20
+ this.initializeSettings(settings);
21
+
22
+ // Handle different modes
23
+ if (installConfig.mode === 'stdio') {
24
+ // Process config for Windows NPX if needed
25
+ const finalConfig = await this.handleWindowsNpx(installConfig);
26
+
27
+ await this.handleStdioMode(settings, serverName, finalConfig);
28
+ } else if (installConfig.mode === 'sse') {
29
+ this.handleSseMode(settings, serverName, installConfig);
30
+ }
31
+
32
+ // Write updated settings
33
+ await writeJsonFile(settingPath, settings);
34
+ }
35
+ }