imcp 0.1.7 → 0.1.8-dev

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 (314) hide show
  1. package/dist/cli/commands/install.js +1 -106
  2. package/dist/cli/commands/install.js.map +1 -0
  3. package/dist/cli/commands/list.js +1 -90
  4. package/dist/cli/commands/list.js.map +1 -0
  5. package/dist/cli/commands/pull.js +1 -16
  6. package/dist/cli/commands/pull.js.map +1 -0
  7. package/dist/cli/commands/serve.js +1 -33
  8. package/dist/cli/commands/serve.js.map +1 -0
  9. package/dist/cli/commands/uninstall.js +1 -46
  10. package/dist/cli/commands/uninstall.js.map +1 -0
  11. package/dist/cli/index.js +1 -65
  12. package/dist/cli/index.js.map +1 -0
  13. package/dist/core/installers/clients/BaseClientInstaller.js +1 -282
  14. package/dist/core/installers/clients/BaseClientInstaller.js.map +1 -0
  15. package/dist/core/installers/clients/ClientInstaller.js +1 -163
  16. package/dist/core/installers/clients/ClientInstaller.js.map +1 -0
  17. package/dist/core/installers/clients/ClientInstallerFactory.js +1 -36
  18. package/dist/core/installers/clients/ClientInstallerFactory.js.map +1 -0
  19. package/dist/core/installers/clients/ClineInstaller.js +1 -30
  20. package/dist/core/installers/clients/ClineInstaller.js.map +1 -0
  21. package/dist/core/installers/clients/ExtensionInstaller.js +1 -151
  22. package/dist/core/installers/clients/ExtensionInstaller.js.map +1 -0
  23. package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -68
  24. package/dist/core/installers/clients/GithubCopilotInstaller.js.map +1 -0
  25. package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -28
  26. package/dist/core/installers/clients/MSRooCodeInstaller.js.map +1 -0
  27. package/dist/core/installers/index.js +1 -8
  28. package/dist/core/installers/index.js.map +1 -0
  29. package/dist/core/installers/requirements/BaseInstaller.js +1 -56
  30. package/dist/core/installers/requirements/BaseInstaller.js.map +1 -0
  31. package/dist/core/installers/requirements/CommandInstaller.js +1 -213
  32. package/dist/core/installers/requirements/CommandInstaller.js.map +1 -0
  33. package/dist/core/installers/requirements/GeneralInstaller.js +1 -126
  34. package/dist/core/installers/requirements/GeneralInstaller.js.map +1 -0
  35. package/dist/core/installers/requirements/InstallerFactory.js +1 -99
  36. package/dist/core/installers/requirements/InstallerFactory.js.map +1 -0
  37. package/dist/core/installers/requirements/NpmInstaller.js +1 -235
  38. package/dist/core/installers/requirements/NpmInstaller.js.map +1 -0
  39. package/dist/core/installers/requirements/NugetInstaller.js +1 -188
  40. package/dist/core/installers/requirements/NugetInstaller.js.map +1 -0
  41. package/dist/core/installers/requirements/PipInstaller.js +1 -192
  42. package/dist/core/installers/requirements/PipInstaller.js.map +1 -0
  43. package/dist/core/installers/requirements/RequirementInstaller.js +1 -2
  44. package/dist/core/installers/requirements/RequirementInstaller.js.map +1 -0
  45. package/dist/core/loaders/ConfigurationLoader.js +1 -256
  46. package/dist/core/loaders/ConfigurationLoader.js.map +1 -0
  47. package/dist/core/loaders/ConfigurationProvider.js +1 -383
  48. package/dist/core/loaders/ConfigurationProvider.js.map +1 -0
  49. package/dist/core/loaders/InstallOperationManager.js +1 -310
  50. package/dist/core/loaders/InstallOperationManager.js.map +1 -0
  51. package/dist/core/loaders/ServerSchemaLoader.js +1 -108
  52. package/dist/core/loaders/ServerSchemaLoader.js.map +1 -0
  53. package/dist/core/loaders/ServerSchemaProvider.js +1 -89
  54. package/dist/core/loaders/ServerSchemaProvider.js.map +1 -0
  55. package/dist/core/loaders/SystemSettingsManager.js +1 -256
  56. package/dist/core/loaders/SystemSettingsManager.js.map +1 -0
  57. package/dist/core/metadatas/constants.js +1 -100
  58. package/dist/core/metadatas/constants.js.map +1 -0
  59. package/dist/core/metadatas/recordingConstants.js +1 -46
  60. package/dist/core/metadatas/recordingConstants.js.map +1 -0
  61. package/dist/core/metadatas/types.js +1 -15
  62. package/dist/core/metadatas/types.js.map +1 -0
  63. package/dist/core/onboard/FeedOnboardService.js +1 -422
  64. package/dist/core/onboard/FeedOnboardService.js.map +1 -0
  65. package/dist/core/onboard/OnboardProcessor.js +1 -333
  66. package/dist/core/onboard/OnboardProcessor.js.map +1 -0
  67. package/dist/core/onboard/OnboardStatus.js +1 -9
  68. package/dist/core/onboard/OnboardStatus.js.map +1 -0
  69. package/dist/core/onboard/OnboardStatusManager.js +1 -360
  70. package/dist/core/onboard/OnboardStatusManager.js.map +1 -0
  71. package/dist/core/validators/FeedValidator.js +1 -133
  72. package/dist/core/validators/FeedValidator.js.map +1 -0
  73. package/dist/core/validators/IServerValidator.js +1 -1
  74. package/dist/core/validators/IServerValidator.js.map +1 -0
  75. package/dist/core/validators/SSEServerValidator.js +1 -38
  76. package/dist/core/validators/SSEServerValidator.js.map +1 -0
  77. package/dist/core/validators/ServerValidatorFactory.js +1 -44
  78. package/dist/core/validators/ServerValidatorFactory.js.map +1 -0
  79. package/dist/core/validators/StdioServerValidator.js +1 -281
  80. package/dist/core/validators/StdioServerValidator.js.map +1 -0
  81. package/dist/index.js +1 -18
  82. package/dist/index.js.map +1 -0
  83. package/dist/services/InstallationService.js +1 -81
  84. package/dist/services/InstallationService.js.map +1 -0
  85. package/dist/services/MCPManager.js +1 -197
  86. package/dist/services/MCPManager.js.map +1 -0
  87. package/dist/services/RequirementService.js +1 -548
  88. package/dist/services/RequirementService.js.map +1 -0
  89. package/dist/services/ServerService.js +1 -127
  90. package/dist/services/ServerService.js.map +1 -0
  91. package/dist/services/TelemetryService.js +1 -53
  92. package/dist/services/TelemetryService.js.map +1 -0
  93. package/dist/utils/UpdateCheckTracker.js +1 -79
  94. package/dist/utils/UpdateCheckTracker.js.map +1 -0
  95. package/dist/utils/adoUtils.js +1 -254
  96. package/dist/utils/adoUtils.js.map +1 -0
  97. package/dist/utils/clientUtils.js +1 -65
  98. package/dist/utils/clientUtils.js.map +1 -0
  99. package/dist/utils/feedUtils.js +1 -28
  100. package/dist/utils/feedUtils.js.map +1 -0
  101. package/dist/utils/githubAuth.js +1 -177
  102. package/dist/utils/githubAuth.js.map +1 -0
  103. package/dist/utils/githubUtils.js +1 -125
  104. package/dist/utils/githubUtils.js.map +1 -0
  105. package/dist/utils/logger.js +1 -176
  106. package/dist/utils/logger.js.map +1 -0
  107. package/dist/utils/macroExpressionUtils.js +1 -93
  108. package/dist/utils/macroExpressionUtils.js.map +1 -0
  109. package/dist/utils/osUtils.js +1 -664
  110. package/dist/utils/osUtils.js.map +1 -0
  111. package/dist/utils/versionUtils.js +1 -101
  112. package/dist/utils/versionUtils.js.map +1 -0
  113. package/dist/web/contract/serverContract.js +1 -1
  114. package/dist/web/contract/serverContract.js.map +1 -0
  115. package/dist/web/public/js/api.js +2 -132
  116. package/dist/web/public/js/api.js.map +1 -0
  117. package/dist/web/public/js/detailsWidget.js +2 -264
  118. package/dist/web/public/js/detailsWidget.js.map +1 -0
  119. package/dist/web/public/js/flights/flights.js +2 -127
  120. package/dist/web/public/js/flights/flights.js.map +1 -0
  121. package/dist/web/public/js/modal/index.js +2 -52
  122. package/dist/web/public/js/modal/index.js.map +1 -0
  123. package/dist/web/public/js/modal/installModal.js +2 -162
  124. package/dist/web/public/js/modal/installModal.js.map +1 -0
  125. package/dist/web/public/js/modal/installation.js +2 -266
  126. package/dist/web/public/js/modal/installation.js.map +1 -0
  127. package/dist/web/public/js/modal/loadingModal.js +2 -182
  128. package/dist/web/public/js/modal/loadingModal.js.map +1 -0
  129. package/dist/web/public/js/modal/modalSetup.js +2 -595
  130. package/dist/web/public/js/modal/modalSetup.js.map +1 -0
  131. package/dist/web/public/js/modal/modalUtils.js +2 -37
  132. package/dist/web/public/js/modal/modalUtils.js.map +1 -0
  133. package/dist/web/public/js/modal/versionUtils.js +2 -20
  134. package/dist/web/public/js/modal/versionUtils.js.map +1 -0
  135. package/dist/web/public/js/modal.js +2 -42
  136. package/dist/web/public/js/modal.js.map +1 -0
  137. package/dist/web/public/js/notifications.js +2 -137
  138. package/dist/web/public/js/notifications.js.map +1 -0
  139. package/dist/web/public/js/onboard/formProcessor.js +2 -1037
  140. package/dist/web/public/js/onboard/formProcessor.js.map +1 -0
  141. package/dist/web/public/js/onboard/index.js +2 -374
  142. package/dist/web/public/js/onboard/index.js.map +1 -0
  143. package/dist/web/public/js/onboard/publishHandler.js +2 -172
  144. package/dist/web/public/js/onboard/publishHandler.js.map +1 -0
  145. package/dist/web/public/js/onboard/state.js +2 -76
  146. package/dist/web/public/js/onboard/state.js.map +1 -0
  147. package/dist/web/public/js/onboard/templates.js +2 -342
  148. package/dist/web/public/js/onboard/templates.js.map +1 -0
  149. package/dist/web/public/js/onboard/uiHandlers.js +2 -1076
  150. package/dist/web/public/js/onboard/uiHandlers.js.map +1 -0
  151. package/dist/web/public/js/onboard/validationHandlers.js +2 -493
  152. package/dist/web/public/js/onboard/validationHandlers.js.map +1 -0
  153. package/dist/web/public/js/serverCategoryDetails.js +2 -364
  154. package/dist/web/public/js/serverCategoryDetails.js.map +1 -0
  155. package/dist/web/public/js/serverCategoryList.js +2 -241
  156. package/dist/web/public/js/serverCategoryList.js.map +1 -0
  157. package/dist/web/public/js/settings.js +2 -314
  158. package/dist/web/public/js/settings.js.map +1 -0
  159. package/dist/web/server.js +1 -404
  160. package/dist/web/server.js.map +1 -0
  161. package/package.json +8 -2
  162. package/.github/ISSUE_TEMPLATE/JitAccess.yml +0 -28
  163. package/.github/acl/access.yml +0 -20
  164. package/.github/compliance/inventory.yml +0 -5
  165. package/.github/policies/jit.yml +0 -19
  166. package/.github/workflows/build.yml +0 -28
  167. package/.roo/rules-code/rules.md +0 -88
  168. package/dist/cli/commands/start.d.ts +0 -2
  169. package/dist/cli/commands/start.js +0 -32
  170. package/dist/cli/commands/sync.d.ts +0 -2
  171. package/dist/cli/commands/sync.js +0 -17
  172. package/dist/core/ConfigurationLoader.d.ts +0 -32
  173. package/dist/core/ConfigurationLoader.js +0 -236
  174. package/dist/core/ConfigurationProvider.d.ts +0 -35
  175. package/dist/core/ConfigurationProvider.js +0 -375
  176. package/dist/core/InstallationService.d.ts +0 -50
  177. package/dist/core/InstallationService.js +0 -350
  178. package/dist/core/MCPManager.d.ts +0 -28
  179. package/dist/core/MCPManager.js +0 -188
  180. package/dist/core/RequirementService.d.ts +0 -40
  181. package/dist/core/RequirementService.js +0 -110
  182. package/dist/core/ServerSchemaLoader.d.ts +0 -11
  183. package/dist/core/ServerSchemaLoader.js +0 -43
  184. package/dist/core/ServerSchemaProvider.d.ts +0 -17
  185. package/dist/core/ServerSchemaProvider.js +0 -120
  186. package/dist/core/constants.d.ts +0 -47
  187. package/dist/core/constants.js +0 -94
  188. package/dist/core/installers/BaseInstaller.d.ts +0 -74
  189. package/dist/core/installers/BaseInstaller.js +0 -253
  190. package/dist/core/installers/ClientInstaller.d.ts +0 -23
  191. package/dist/core/installers/ClientInstaller.js +0 -564
  192. package/dist/core/installers/CommandInstaller.d.ts +0 -37
  193. package/dist/core/installers/CommandInstaller.js +0 -173
  194. package/dist/core/installers/GeneralInstaller.d.ts +0 -33
  195. package/dist/core/installers/GeneralInstaller.js +0 -85
  196. package/dist/core/installers/InstallerFactory.d.ts +0 -54
  197. package/dist/core/installers/InstallerFactory.js +0 -97
  198. package/dist/core/installers/NpmInstaller.d.ts +0 -26
  199. package/dist/core/installers/NpmInstaller.js +0 -127
  200. package/dist/core/installers/PipInstaller.d.ts +0 -28
  201. package/dist/core/installers/PipInstaller.js +0 -127
  202. package/dist/core/installers/RequirementInstaller.d.ts +0 -33
  203. package/dist/core/installers/RequirementInstaller.js +0 -3
  204. package/dist/core/types.d.ts +0 -166
  205. package/dist/core/types.js +0 -16
  206. package/dist/services/InstallRequestValidator.d.ts +0 -21
  207. package/dist/services/InstallRequestValidator.js +0 -99
  208. package/dist/web/public/js/modal/installHandler.js +0 -227
  209. package/dist/web/public/js/modal/loadingUI.js +0 -74
  210. package/dist/web/public/js/modal/messageQueue.js +0 -112
  211. package/dist/web/public/js/modal/modalUI.js +0 -214
  212. package/dist/web/public/js/modal/version.js +0 -20
  213. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +0 -370
  214. package/docs/ONBOARDING_PAGE_DESIGN.md +0 -260
  215. package/docs/Telemetry.md +0 -136
  216. package/memory-bank/activeContext.md +0 -26
  217. package/memory-bank/decisionLog.md +0 -91
  218. package/memory-bank/productContext.md +0 -41
  219. package/memory-bank/progress.md +0 -35
  220. package/memory-bank/systemPatterns.md +0 -10
  221. package/src/cli/commands/install.ts +0 -139
  222. package/src/cli/commands/list.ts +0 -113
  223. package/src/cli/commands/pull.ts +0 -16
  224. package/src/cli/commands/serve.ts +0 -39
  225. package/src/cli/commands/uninstall.ts +0 -64
  226. package/src/cli/index.ts +0 -82
  227. package/src/core/installers/clients/BaseClientInstaller.ts +0 -341
  228. package/src/core/installers/clients/ClientInstaller.ts +0 -222
  229. package/src/core/installers/clients/ClientInstallerFactory.ts +0 -43
  230. package/src/core/installers/clients/ClineInstaller.ts +0 -35
  231. package/src/core/installers/clients/ExtensionInstaller.ts +0 -165
  232. package/src/core/installers/clients/GithubCopilotInstaller.ts +0 -79
  233. package/src/core/installers/clients/MSRooCodeInstaller.ts +0 -32
  234. package/src/core/installers/index.ts +0 -11
  235. package/src/core/installers/requirements/BaseInstaller.ts +0 -85
  236. package/src/core/installers/requirements/CommandInstaller.ts +0 -231
  237. package/src/core/installers/requirements/GeneralInstaller.ts +0 -133
  238. package/src/core/installers/requirements/InstallerFactory.ts +0 -114
  239. package/src/core/installers/requirements/NpmInstaller.ts +0 -271
  240. package/src/core/installers/requirements/NugetInstaller.ts +0 -203
  241. package/src/core/installers/requirements/PipInstaller.ts +0 -207
  242. package/src/core/installers/requirements/RequirementInstaller.ts +0 -42
  243. package/src/core/loaders/ConfigurationLoader.ts +0 -298
  244. package/src/core/loaders/ConfigurationProvider.ts +0 -462
  245. package/src/core/loaders/InstallOperationManager.ts +0 -367
  246. package/src/core/loaders/ServerSchemaLoader.ts +0 -117
  247. package/src/core/loaders/ServerSchemaProvider.ts +0 -99
  248. package/src/core/loaders/SystemSettingsManager.ts +0 -278
  249. package/src/core/metadatas/constants.ts +0 -122
  250. package/src/core/metadatas/recordingConstants.ts +0 -65
  251. package/src/core/metadatas/types.ts +0 -202
  252. package/src/core/onboard/FeedOnboardService.ts +0 -501
  253. package/src/core/onboard/OnboardProcessor.ts +0 -356
  254. package/src/core/onboard/OnboardStatus.ts +0 -60
  255. package/src/core/onboard/OnboardStatusManager.ts +0 -416
  256. package/src/core/validators/FeedValidator.ts +0 -135
  257. package/src/core/validators/IServerValidator.ts +0 -21
  258. package/src/core/validators/SSEServerValidator.ts +0 -43
  259. package/src/core/validators/ServerValidatorFactory.ts +0 -51
  260. package/src/core/validators/StdioServerValidator.ts +0 -313
  261. package/src/index.ts +0 -44
  262. package/src/services/InstallationService.ts +0 -102
  263. package/src/services/MCPManager.ts +0 -249
  264. package/src/services/RequirementService.ts +0 -627
  265. package/src/services/ServerService.ts +0 -161
  266. package/src/services/TelemetryService.ts +0 -59
  267. package/src/utils/UpdateCheckTracker.ts +0 -86
  268. package/src/utils/adoUtils.ts +0 -293
  269. package/src/utils/clientUtils.ts +0 -72
  270. package/src/utils/feedUtils.ts +0 -31
  271. package/src/utils/githubAuth.ts +0 -212
  272. package/src/utils/githubUtils.ts +0 -164
  273. package/src/utils/logger.ts +0 -195
  274. package/src/utils/macroExpressionUtils.ts +0 -104
  275. package/src/utils/osUtils.ts +0 -700
  276. package/src/utils/versionUtils.ts +0 -114
  277. package/src/web/contract/serverContract.ts +0 -74
  278. package/src/web/public/css/detailsWidget.css +0 -235
  279. package/src/web/public/css/modal.css +0 -757
  280. package/src/web/public/css/notifications.css +0 -101
  281. package/src/web/public/css/onboard.css +0 -107
  282. package/src/web/public/css/serverCategoryList.css +0 -120
  283. package/src/web/public/css/serverDetails.css +0 -139
  284. package/src/web/public/index.html +0 -359
  285. package/src/web/public/js/api.js +0 -132
  286. package/src/web/public/js/detailsWidget.js +0 -264
  287. package/src/web/public/js/flights/flights.js +0 -127
  288. package/src/web/public/js/modal/index.js +0 -52
  289. package/src/web/public/js/modal/installModal.js +0 -162
  290. package/src/web/public/js/modal/installation.js +0 -266
  291. package/src/web/public/js/modal/loadingModal.js +0 -182
  292. package/src/web/public/js/modal/modalSetup.js +0 -595
  293. package/src/web/public/js/modal/modalUtils.js +0 -37
  294. package/src/web/public/js/modal/versionUtils.js +0 -20
  295. package/src/web/public/js/modal.js +0 -42
  296. package/src/web/public/js/notifications.js +0 -137
  297. package/src/web/public/js/onboard/formProcessor.js +0 -1037
  298. package/src/web/public/js/onboard/index.js +0 -374
  299. package/src/web/public/js/onboard/publishHandler.js +0 -172
  300. package/src/web/public/js/onboard/state.js +0 -76
  301. package/src/web/public/js/onboard/templates.js +0 -342
  302. package/src/web/public/js/onboard/uiHandlers.js +0 -1076
  303. package/src/web/public/js/onboard/validationHandlers.js +0 -493
  304. package/src/web/public/js/serverCategoryDetails.js +0 -364
  305. package/src/web/public/js/serverCategoryList.js +0 -241
  306. package/src/web/public/js/settings.js +0 -314
  307. package/src/web/public/modal.html +0 -84
  308. package/src/web/public/onboard.html +0 -296
  309. package/src/web/public/settings.html +0 -135
  310. package/src/web/public/styles.css +0 -277
  311. package/src/web/server.ts +0 -478
  312. package/tsconfig.json +0 -18
  313. package/wiki/Installation.md +0 -3
  314. package/wiki/Publish.md +0 -3
@@ -1,1037 +0,0 @@
1
- // Form data processing and API calls
2
- import { showToast } from '../notifications.js';
3
- import { state, setServerCounter, getServerCounter, clearEnvCountersForTab, clearServerRequirementCountersForTab } from './state.js';
4
-
5
- /**
6
- * Adds real-time validation to form inputs.
7
- * @param {string} formId - The ID of the form to add validation to.
8
- */
9
- function setupRealTimeValidation(formId) {
10
- const form = document.getElementById(formId);
11
- if (!form) return;
12
-
13
- if (!form) return;
14
-
15
- // Name input validation (category or server)
16
- const nameInput = form.querySelector('input[name="name"]');
17
- if (nameInput) {
18
- nameInput.addEventListener('input', (e) => {
19
- const value = e.target.value.trim();
20
- if (!value) {
21
- showValidationMessage(e.target, 'Name is required');
22
- } else if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
23
- showValidationMessage(e.target, 'Only alphanumeric characters, hyphens, and underscores allowed');
24
- } else if (value.length > 50) {
25
- showValidationMessage(e.target, 'Must not exceed 50 characters');
26
- } else {
27
- showValidationMessage(e.target, 'Valid name', false);
28
- }
29
- });
30
- }
31
-
32
- // Display name validation
33
- const displayNameInput = form.querySelector('input[name="displayName"]');
34
- if (displayNameInput) {
35
- displayNameInput.addEventListener('input', (e) => {
36
- const value = e.target.value.trim();
37
- if (!value) {
38
- showValidationMessage(e.target, 'Display name is required');
39
- } else if (value.length > 100) {
40
- showValidationMessage(e.target, 'Must not exceed 100 characters');
41
- } else {
42
- showValidationMessage(e.target, 'Valid display name', false);
43
- }
44
- });
45
- }
46
-
47
- // Description validation
48
- const descriptionInput = form.querySelector('textarea[name="description"]');
49
- if (descriptionInput) {
50
- descriptionInput.addEventListener('input', (e) => {
51
- const value = e.target.value.trim();
52
- if (value.length > 500) {
53
- showValidationMessage(e.target, 'Must not exceed 500 characters');
54
- } else if (value.length === 0) {
55
- showValidationMessage(e.target, 'Description is required');
56
- } else {
57
- showValidationMessage(e.target, 'Valid description', false);
58
- }
59
- });
60
- }
61
-
62
- // Repository URL validation
63
- const repoInput = form.querySelector('input[name="repository"]');
64
- if (repoInput) {
65
- repoInput.addEventListener('input', (e) => {
66
- const value = e.target.value.trim();
67
- if (value) {
68
- try {
69
- new URL(value);
70
- showValidationMessage(e.target, 'Valid URL', false);
71
- } catch (err) {
72
- showValidationMessage(e.target, 'Invalid URL format');
73
- }
74
- } else {
75
- e.target.nextElementSibling?.remove(); // Remove validation message if empty
76
- }
77
- });
78
- }
79
-
80
- // Server mode validation
81
- form.addEventListener('change', (e) => {
82
- if (e.target.name?.match(/servers\[\d+\]\.mode/)) {
83
- const serverIndex = e.target.name.match(/servers\[(\d+)\]/)[1];
84
- const commandInput = form.querySelector(`[name="servers[${serverIndex}].installation.command"]`);
85
- const urlInput = form.querySelector(`[name="servers[${serverIndex}].installation.url"]`);
86
- const argsInput = form.querySelector(`[name="servers[${serverIndex}].installation.args"]`);
87
-
88
- if (e.target.value === 'stdio') {
89
- if (commandInput) {
90
- commandInput.required = true;
91
- showValidationMessage(commandInput, 'Command is required for stdio mode');
92
- }
93
- if (urlInput) {
94
- urlInput.required = false;
95
- urlInput.value = '';
96
- urlInput.nextElementSibling?.remove();
97
- }
98
- } else if (e.target.value === 'sse') {
99
- if (urlInput) {
100
- urlInput.required = true;
101
- showValidationMessage(urlInput, 'URL is required for sse mode');
102
- }
103
- if (commandInput) {
104
- commandInput.required = false;
105
- commandInput.value = '';
106
- commandInput.nextElementSibling?.remove();
107
- }
108
- if (argsInput) {
109
- argsInput.value = '';
110
- argsInput.nextElementSibling?.remove();
111
- }
112
- }
113
- }
114
- });
115
-
116
- // Environment variable validation
117
- form.addEventListener('input', (e) => {
118
- if (e.target.name?.includes('.installation.env[')) {
119
- const matches = e.target.name.match(/servers\[(\d+)\]\.installation\.env\[(\d+)\]\.(name|default)/);
120
- if (matches) {
121
- const [, serverIndex, envIndex, field] = matches;
122
- if (field === 'name') {
123
- const value = e.target.value.trim();
124
- if (!value) {
125
- showValidationMessage(e.target, 'Environment variable name is required');
126
- } else if (!/^[A-Z_][A-Z0-9_]*$/.test(value)) {
127
- showValidationMessage(e.target, 'Must be uppercase with only letters, numbers, and underscores');
128
- } else if (value.length > 50) {
129
- showValidationMessage(e.target, 'Must not exceed 50 characters');
130
- } else {
131
- showValidationMessage(e.target, 'Valid name', false);
132
- }
133
- }
134
- }
135
- }
136
- });
137
- }
138
-
139
- /**
140
- * Submits the form data for onboarding or updating a server category.
141
- * @param {Event} event - The form submission event.
142
- * @param {string} activeTab - Identifier for the active tab ('create-category' or 'create-server').
143
- * @param {object|null} currentSelectedCategoryData - Full FeedConfiguration of the selected category if activeTab is 'create-server'.
144
- */
145
- export async function submitForm(event, activeTab, currentSelectedCategoryData = null) {
146
- event.preventDefault();
147
- const formElement = event.target;
148
- let feedConfiguration;
149
- let isUpdateOperation = false;
150
-
151
- if (activeTab === 'create-category') {
152
- try {
153
- feedConfiguration = formDataToFeedConfiguration(formElement);
154
- const urlParams = new URLSearchParams(window.location.search);
155
- isUpdateOperation = urlParams.get('action') === 'edit' && urlParams.get('category') === feedConfiguration.name;
156
- } catch (validationError) {
157
- console.error('Validation error:', validationError);
158
- return;
159
- }
160
- } else if (activeTab === 'create-server') {
161
- if (!currentSelectedCategoryData || !currentSelectedCategoryData.name) {
162
- showToast('No existing category selected or category data is missing.', 'error');
163
- return;
164
- }
165
-
166
- // Get the complete state of servers and their requirements from the current form.
167
- // The `true` flag for `forExistingCategoryTab` tells formDataToFeedConfiguration
168
- // to only process server data and not category-level fields from this form.
169
- // It should return ALL servers currently in this form, including modified adhoc and new ones.
170
- const formDerivedData = formDataToFeedConfiguration(formElement, true, currentSelectedCategoryData);
171
-
172
- // Start with a deep clone of the original category data (for name, description, etc.)
173
- feedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData));
174
-
175
- // Replace the mcpServers list entirely with what was parsed from the form.
176
- // This ensures that the list reflects the current state of the form (original read-only, modified adhoc, new).
177
- feedConfiguration.mcpServers = formDerivedData.mcpServers || [];
178
-
179
- // Merge requirements: start with original requirements, then add any new ones from the form.
180
- // `formDerivedData.requirements` should ideally only contain requirements introduced by
181
- // servers in the current form that are not already in `currentSelectedCategoryData.requirements`.
182
- // The existing logic for merging requirements seems okay if `formDerivedData.requirements` is correctly populated.
183
- const existingReqKeys = new Set((feedConfiguration.requirements || []).map(r => `${r.type}|${r.name}|${r.version}`));
184
- (formDerivedData.requirements || []).forEach(newReq => {
185
- const reqKey = `${newReq.type}|${newReq.name}|${newReq.version}`;
186
- if (!existingReqKeys.has(reqKey)) {
187
- feedConfiguration.requirements.push(newReq);
188
- existingReqKeys.add(reqKey);
189
- }
190
- });
191
- isUpdateOperation = true;
192
- } else {
193
- showToast('Invalid tab context for submission.', 'error');
194
- return;
195
- }
196
-
197
- try {
198
- const response = await fetch('/api/categories/onboard', {
199
- method: 'POST',
200
- headers: { 'Content-Type': 'application/json' },
201
- body: JSON.stringify({
202
- categoryData: feedConfiguration,
203
- isUpdate: isUpdateOperation
204
- })
205
- });
206
-
207
- if (!response.ok) {
208
- const errorData = await response.json().catch(() => ({ error: 'Failed to parse error response' }));
209
- throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.error || response.statusText}`);
210
- }
211
-
212
- const result = await response.json();
213
- // console.log('Form submitted successfully:', result);
214
- showToast('Form submitted successfully! See console for operation details.', 'success');
215
- } catch (error) {
216
- console.error('Error submitting form:', error);
217
- showToast(`Error submitting form: ${error.message}. Please try again.`, 'error');
218
- }
219
- }
220
-
221
- /**
222
- * Retrieves form data from a given HTMLFormElement.
223
- * @param {HTMLFormElement} formElement - The form element to process.
224
- * @param {boolean} [forExistingCategoryTab=false] - True if processing for the "Create Server in Existing Category" tab.
225
- * @param {object|null} [baseCategoryData=null] - The base FeedConfiguration of the selected category (used if forExistingCategoryTab is true).
226
- * @returns {object} The FeedConfiguration object (or partial for new servers if forExistingCategoryTab is true).
227
- */
228
- export function getFormData(formElement, forExistingCategoryTab = false, baseCategoryData = null) {
229
- return formDataToFeedConfiguration(formElement, forExistingCategoryTab, baseCategoryData);
230
- }
231
-
232
- /**
233
- * Converts form data to a FeedConfiguration object.
234
- * @param {HTMLFormElement} formElement - The form element.
235
- * @param {boolean} [forExistingCategoryTab=false] - True if processing for the "Create Server in Existing Category" tab.
236
- * @param {object|null} [baseCategoryData=null] - The base FeedConfiguration of the selected category.
237
- * @returns {object} The FeedConfiguration object. If forExistingCategoryTab is true, this will be a partial
238
- * FeedConfiguration containing only newly added servers and their requirements.
239
- */
240
- function validateServerConfig(serverConfig) {
241
- const errors = [];
242
-
243
- // Validate required fields
244
- if (!serverConfig.name) {
245
- errors.push('Server name is required');
246
- } else if (!/^[a-zA-Z0-9-_]+$/.test(serverConfig.name)) {
247
- errors.push('Server name must only contain alphanumeric characters, hyphens, and underscores');
248
- } else if (serverConfig.name.length > 50) {
249
- errors.push('Server name must not exceed 50 characters');
250
- }
251
-
252
- if (!serverConfig.description || serverConfig.description.trim() === '') {
253
- errors.push('Server description is required');
254
- } else if (serverConfig.description.length > 200) {
255
- errors.push('Server description must not exceed 200 characters');
256
- }
257
-
258
- if (!serverConfig.mode) {
259
- errors.push('Server mode is required');
260
- } else if (!['stdio', 'sse'].includes(serverConfig.mode)) {
261
- errors.push('Server mode must be either "stdio" or "sse"');
262
- }
263
-
264
- // Validate installation config based on mode
265
- if (serverConfig.mode === 'stdio') {
266
- if (!serverConfig.installation?.command) {
267
- errors.push('Command is required for stdio server');
268
- } else if (!/^[a-zA-Z0-9-_.\\/]+$/.test(serverConfig.installation.command)) {
269
- errors.push('Command must only contain alphanumeric characters, hyphens, underscores, dots, and slashes');
270
- }
271
-
272
- // Validate arguments format if present
273
- if (serverConfig.installation?.args?.length > 0) {
274
- serverConfig.installation.args.forEach((arg, index) => {
275
- if (!/^[a-zA-Z0-9-_./\\]+$/.test(arg)) {
276
- errors.push(`Argument ${index + 1} must only contain alphanumeric characters, hyphens, underscores, dots, and slashes`);
277
- }
278
- });
279
- }
280
- } else if (serverConfig.mode === 'sse') {
281
- if (!serverConfig.installation?.url) {
282
- errors.push('URL is required for sse server');
283
- } else {
284
- try {
285
- new URL(serverConfig.installation.url);
286
- } catch (e) {
287
- errors.push('Invalid URL format');
288
- }
289
- }
290
- }
291
-
292
- // Validate env variables if present
293
- if (serverConfig.installation?.env) {
294
- Object.entries(serverConfig.installation.env).forEach(([name, config]) => {
295
- if (!name) {
296
- errors.push('Environment variable name is required');
297
- } else if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {
298
- errors.push(`Environment variable name "${name}" must be uppercase with only letters, numbers, and underscores`);
299
- } else if (name.length > 50) {
300
- errors.push(`Environment variable name "${name}" must not exceed 50 characters`);
301
- }
302
-
303
- if (config.Required && !config.Default) {
304
- errors.push(`Required environment variable "${name}" should have a default value`);
305
- }
306
-
307
- if (config.Description && config.Description.length > 200) {
308
- errors.push(`Description for environment variable "${name}" must not exceed 200 characters`);
309
- }
310
- });
311
- }
312
-
313
- // Validate schemas if present
314
- if (serverConfig.schemas) {
315
- try {
316
- JSON.parse(serverConfig.schemas);
317
- } catch (e) {
318
- errors.push('Invalid JSON format in schemas');
319
- }
320
- }
321
-
322
- return errors;
323
- }
324
-
325
- export function formDataToFeedConfiguration(formElement, forExistingCategoryTab = false, baseCategoryData = null) {
326
- const currentFormData = new FormData(formElement);
327
- const feedConfiguration = {
328
- requirements: [],
329
- mcpServers: []
330
- };
331
-
332
- let serversListId;
333
- if (formElement.id === 'onboardForm') {
334
- serversListId = 'serversList';
335
- } else if (formElement.id === 'onboardServerForm') {
336
- serversListId = 'existingCategoryServersList';
337
- } else {
338
- console.warn('[formDataToFeedConfiguration] Could not determine serversListId from formElement.id:', formElement.id);
339
- }
340
-
341
- if (!forExistingCategoryTab) {
342
- feedConfiguration.name = currentFormData.get('name') || '';
343
- feedConfiguration.displayName = currentFormData.get('displayName') || '';
344
- feedConfiguration.description = currentFormData.get('description') || '';
345
- feedConfiguration.repository = currentFormData.get('repository') || undefined;
346
- } else if (baseCategoryData) {
347
- feedConfiguration.name = baseCategoryData.name;
348
- }
349
-
350
- const serverDataMap = new Map();
351
- const globalRequirementsMap = new Map();
352
-
353
- for (const [key, value] of currentFormData.entries()) {
354
- if (forExistingCategoryTab && !key.startsWith('servers[')) {
355
- continue;
356
- }
357
-
358
- const serverMatchKey = key.match(/^servers\[(\d+)\]\.(.+)$/);
359
- if (serverMatchKey) {
360
- let serverIndexInForm = parseInt(serverMatchKey[1], 10);
361
- const fieldPathFromMatch = serverMatchKey[2];
362
- let actualServerDataIndex = serverIndexInForm;
363
-
364
- if (!serverDataMap.has(actualServerDataIndex)) {
365
- serverDataMap.set(actualServerDataIndex, {
366
- installation: { env: new Map() },
367
- dependencies: { requirements: new Map() },
368
- isNew: true // Assuming all servers processed this way are new additions to the form
369
- });
370
- }
371
- const currentServerData = serverDataMap.get(actualServerDataIndex);
372
-
373
- if (fieldPathFromMatch === 'name') currentServerData.name = value;
374
- else if (fieldPathFromMatch === 'description') currentServerData.description = value;
375
- else if (fieldPathFromMatch === 'mode') currentServerData.mode = value;
376
- else if (fieldPathFromMatch === 'repository') currentServerData.repository = value || undefined;
377
- else if (fieldPathFromMatch === 'schemas') currentServerData.schemas = value || undefined;
378
- else if (fieldPathFromMatch === 'installation.command') currentServerData.installation.command = value;
379
- else if (fieldPathFromMatch === 'installation.args') {
380
- currentServerData.installation.args = value ? value.split(',').map(arg => arg.trim()).filter(arg => arg) : [];
381
- }
382
- else if (fieldPathFromMatch === 'installation.url') {
383
- currentServerData.installation.url = value;
384
- }
385
- else if (fieldPathFromMatch.startsWith('installation.env[')) {
386
- const envMatch = fieldPathFromMatch.match(/^installation\.env\[(\d+)\]\.(name|default|required|description)$/);
387
- if (envMatch) {
388
- const envIndex = parseInt(envMatch[1], 10);
389
- const envField = envMatch[2];
390
- if (!currentServerData.installation.env.has(envIndex)) {
391
- currentServerData.installation.env.set(envIndex, {});
392
- }
393
- const currentEnv = currentServerData.installation.env.get(envIndex);
394
- if (envField === 'required') {
395
- currentEnv[envField] = currentFormData.get(key) === 'on';
396
- } else {
397
- currentEnv[envField] = value || undefined;
398
- }
399
- }
400
- }
401
- else if (fieldPathFromMatch.startsWith('requirements[')) {
402
- const reqMatch = fieldPathFromMatch.match(/^requirements\[(\d+)\]\.(.+)$/);
403
- if (reqMatch) {
404
- const reqIndex = parseInt(reqMatch[1], 10);
405
- const reqFieldPath = reqMatch[2];
406
-
407
- if (!currentServerData.dependencies.requirements.has(reqIndex)) {
408
- currentServerData.dependencies.requirements.set(reqIndex, { registry: {} });
409
- }
410
- const currentReq = currentServerData.dependencies.requirements.get(reqIndex);
411
-
412
- if (reqFieldPath === 'name') currentReq.name = value;
413
- else if (reqFieldPath === 'type') currentReq.type = value;
414
- else if (reqFieldPath === 'version') currentReq.version = value;
415
- else if (reqFieldPath === 'order') currentReq.order = value ? parseInt(value, 10) : undefined;
416
- else if (reqFieldPath === 'alias') currentReq.alias = value || undefined;
417
- else if (reqFieldPath === 'registryType') {
418
- currentReq.registryType = value;
419
- } else if (reqFieldPath.startsWith('registry.githubRelease.')) {
420
- currentReq.registry.githubRelease = currentReq.registry.githubRelease || {};
421
- currentReq.registry.githubRelease[reqFieldPath.substring('registry.githubRelease.'.length)] = value || undefined;
422
- } else if (reqFieldPath.startsWith('registry.artifacts.')) {
423
- currentReq.registry.artifacts = currentReq.registry.artifacts || {};
424
- currentReq.registry.artifacts[reqFieldPath.substring('registry.artifacts.'.length)] = value || undefined;
425
- } else if (reqFieldPath.startsWith('registry.local.')) {
426
- currentReq.registry.local = currentReq.registry.local || {};
427
- currentReq.registry.local[reqFieldPath.substring('registry.local.'.length)] = value || undefined;
428
- }
429
- }
430
- }
431
- }
432
- }
433
-
434
- // console.log('[formDataToFeedConfiguration] serverDataMap keys before processing:', Array.from(serverDataMap.keys())); // DEBUG
435
- // console.log('[formDataToFeedConfiguration] serverDataMap size:', serverDataMap.size); // DEBUG
436
-
437
- // Process servers found in the form data (new/adhoc or edited original servers)
438
- serverDataMap.forEach((serverRaw, serverIndex) => {
439
- let mcpServer;
440
- // ... (existing logic to build mcpServer from serverRaw)
441
- if (serverRaw.mode === 'sse') {
442
- mcpServer = {
443
- name: serverRaw.name,
444
- description: serverRaw.description,
445
- mode: serverRaw.mode,
446
- schemas: serverRaw.schemas,
447
- repository: serverRaw.repository,
448
- installation: {
449
- url: serverRaw.installation.url || ''
450
- },
451
- dependencies: {
452
- requirements: []
453
- }
454
- };
455
- } else { // stdio or other modes
456
- mcpServer = {
457
- name: serverRaw.name,
458
- description: serverRaw.description,
459
- mode: serverRaw.mode,
460
- schemas: serverRaw.schemas,
461
- repository: serverRaw.repository,
462
- installation: {
463
- command: serverRaw.installation.command,
464
- args: serverRaw.installation.args || [],
465
- env: {} // Initialize env
466
- },
467
- dependencies: {
468
- requirements: []
469
- }
470
- };
471
-
472
- const envVars = {};
473
- serverRaw.installation.env.forEach(env => {
474
- if (env.name) {
475
- envVars[env.name] = {
476
- Required: env.required || false,
477
- Description: env.description || '',
478
- Default: env.default || undefined
479
- };
480
- }
481
- });
482
- if (Object.keys(envVars).length > 0) {
483
- mcpServer.installation.env = envVars;
484
- } else {
485
- delete mcpServer.installation.env; // Clean up if no env vars
486
- }
487
- }
488
-
489
- serverRaw.dependencies.requirements.forEach(reqRaw => {
490
- if (!reqRaw.name || !reqRaw.type || !reqRaw.version) return;
491
-
492
- const fullRequirementConfig = {
493
- name: reqRaw.name,
494
- type: reqRaw.type,
495
- version: reqRaw.version,
496
- alias: reqRaw.alias,
497
- order: reqRaw.order,
498
- };
499
-
500
- if (reqRaw.registryType && reqRaw.registryType !== 'public') {
501
- fullRequirementConfig.registry = {};
502
- if (reqRaw.registryType === 'github' && reqRaw.registry.githubRelease) {
503
- fullRequirementConfig.registry.githubRelease = {
504
- repository: reqRaw.registry.githubRelease.repository,
505
- assetsName: reqRaw.registry.githubRelease.assetsName,
506
- assetName: reqRaw.registry.githubRelease.assetName,
507
- };
508
- } else if (reqRaw.registryType === 'artifacts' && reqRaw.registry.artifacts) {
509
- fullRequirementConfig.registry.artifacts = {
510
- registryUrl: reqRaw.registry.artifacts.registryUrl,
511
- registryName: reqRaw.registry.artifacts.registryName,
512
- };
513
- } else if (reqRaw.registryType === 'local' && reqRaw.registry.local) {
514
- fullRequirementConfig.registry.local = {
515
- localPath: reqRaw.registry.local.localPath,
516
- assetName: reqRaw.registry.local.assetName,
517
- };
518
- }
519
- // Clean up empty registry objects
520
- if (fullRequirementConfig.registry.githubRelease && !fullRequirementConfig.registry.githubRelease.repository) delete fullRequirementConfig.registry.githubRelease;
521
- if (fullRequirementConfig.registry.artifacts && !fullRequirementConfig.registry.artifacts.registryUrl) delete fullRequirementConfig.registry.artifacts;
522
- if (fullRequirementConfig.registry.local && !fullRequirementConfig.registry.local.localPath) delete fullRequirementConfig.registry.local;
523
- if (Object.keys(fullRequirementConfig.registry).length === 0) delete fullRequirementConfig.registry;
524
- }
525
-
526
- mcpServer.dependencies.requirements.push({
527
- name: reqRaw.name,
528
- version: reqRaw.version,
529
- order: reqRaw.order
530
- });
531
-
532
- const reqKey = `${reqRaw.type}|${reqRaw.name}|${reqRaw.version}`;
533
- if (!globalRequirementsMap.has(reqKey)) {
534
- const { order, ...reqConfigForGlobal } = fullRequirementConfig; // Exclude order for global list
535
- globalRequirementsMap.set(reqKey, reqConfigForGlobal);
536
- }
537
- });
538
- if (mcpServer.dependencies.requirements.length === 0) {
539
- delete mcpServer.dependencies; // Clean up if no requirements
540
- }
541
-
542
- // Attempt to retrieve and add systemTags from the DOM element,
543
- // ONLY if we are in the 'Create Server in Existing Category' tab context.
544
- // For 'Create New Category' tab, systemTags should not be assigned from DOM.
545
- if (formElement.id === 'onboardServerForm' && serversListId) {
546
- const selector = `#${serversListId} .server-item[data-index="${serverIndex}"]`;
547
- const serverItemElement = document.querySelector(selector);
548
- if (serverItemElement && serverItemElement.dataset.systemTags) {
549
- try {
550
- mcpServer.systemTags = JSON.parse(serverItemElement.dataset.systemTags);
551
- } catch (e) {
552
- console.error(`[formDataToFeedConfiguration] Error parsing systemTags for server index ${serverIndex} ('${mcpServer.name}') on ${formElement.id}:`, e, serverItemElement.dataset.systemTags);
553
- mcpServer.systemTags = { parseError: true };
554
- }
555
- }
556
- // If no dataset.systemTags or not onboardServerForm, mcpServer.systemTags remains undefined.
557
- } else {
558
- // Ensure systemTags is not carried over if not in the correct context or not present in dataset
559
- delete mcpServer.systemTags;
560
- }
561
-
562
- feedConfiguration.mcpServers.push(mcpServer);
563
- });
564
-
565
-
566
- // If processing for an existing category, ensure original non-adhoc servers are included
567
- // if they weren't picked up by the form (e.g., because they were read-only and disabled).
568
- if (forExistingCategoryTab && baseCategoryData && Array.isArray(baseCategoryData.mcpServers)) {
569
- baseCategoryData.mcpServers.forEach(originalServer => {
570
- // Check if this original server (by name) is already in our processed list.
571
- // We use name as the primary identifier for existing servers.
572
- const isAlreadyProcessed = feedConfiguration.mcpServers.some(
573
- processedServer => processedServer.name === originalServer.name
574
- );
575
-
576
- if (!isAlreadyProcessed && (!originalServer.systemTags || originalServer.systemTags.adhoc !== 'true')) {
577
- // This original server was not in the form data (likely read-only) and is not adhoc. Add it.
578
- // Ensure its requirements are also added to the global list if not already present.
579
- feedConfiguration.mcpServers.push(JSON.parse(JSON.stringify(originalServer))); // Add a clone
580
-
581
- if (originalServer.dependencies && Array.isArray(originalServer.dependencies.requirements)) {
582
- originalServer.dependencies.requirements.forEach(req => {
583
- // We need the full requirement definition for the global map.
584
- // This might require looking up the full definition from baseCategoryData.requirements
585
- // if originalServer.dependencies.requirements only has name/version/order.
586
- // For simplicity here, we assume baseCategoryData.requirements contains full definitions.
587
- const originalGlobalReq = baseCategoryData.requirements?.find(
588
- gReq => gReq.name === req.name && gReq.type && gReq.version === req.version // Type might not be in server's dep list
589
- );
590
-
591
- if (originalGlobalReq) {
592
- const reqKey = `${originalGlobalReq.type}|${originalGlobalReq.name}|${originalGlobalReq.version}`;
593
- if (!globalRequirementsMap.has(reqKey)) {
594
- globalRequirementsMap.set(reqKey, JSON.parse(JSON.stringify(originalGlobalReq)));
595
- }
596
- } else {
597
- // Fallback if full definition not found, add what we have, though type might be missing.
598
- // This part might need refinement based on actual structure of baseCategoryData.requirements
599
- // and how server-specific dependencies link to global ones.
600
- // The current server-specific req usually has name, version, order. Type is global.
601
- // We need to find the type from the global requirements list.
602
- // This logic assumes that if a server has a dependency, its full definition (including type)
603
- // must exist in the global `baseCategoryData.requirements`.
604
-
605
- // Let's find the type from baseCategoryData.requirements based on name and version
606
- const matchingGlobalReqForType = baseCategoryData.requirements?.find(
607
- gReq => gReq.name === req.name && gReq.version === req.version
608
- );
609
- if (matchingGlobalReqForType && matchingGlobalReqForType.type) {
610
- const reqKey = `${matchingGlobalReqForType.type}|${req.name}|${req.version}`;
611
- if (!globalRequirementsMap.has(reqKey)) {
612
- // Construct a basic global requirement if not found, though ideally it should exist.
613
- globalRequirementsMap.set(reqKey, {
614
- name: req.name,
615
- version: req.version,
616
- type: matchingGlobalReqForType.type
617
- // Other fields like alias, registry would be missing here if not in matchingGlobalReqForType
618
- });
619
- }
620
- } else {
621
- console.warn(`Could not find full global requirement definition (or type) for ${req.name} v${req.version} from original server ${originalServer.name}`);
622
- }
623
- }
624
- });
625
- }
626
- }
627
- });
628
- }
629
-
630
-
631
- feedConfiguration.requirements = Array.from(globalRequirementsMap.values());
632
-
633
- // Validate the entire configuration
634
- const errors = [];
635
-
636
- // Validate basic category information
637
- if (!forExistingCategoryTab) {
638
- if (!feedConfiguration.name) {
639
- errors.push('Category name is required');
640
- } else if (!/^[a-zA-Z0-9-_]+$/.test(feedConfiguration.name)) {
641
- errors.push('Category name must only contain alphanumeric characters, hyphens, and underscores');
642
- }
643
-
644
- if (!feedConfiguration.displayName) {
645
- errors.push('Display name is required');
646
- }
647
- }
648
-
649
- // Validate each server configuration
650
- feedConfiguration.mcpServers.forEach((server, index) => {
651
- const serverErrors = validateServerConfig(server);
652
- if (serverErrors.length > 0) {
653
- errors.push(`Server ${index + 1} (${server.name || 'unnamed'}): ${serverErrors.join(', ')}`);
654
- }
655
- });
656
-
657
- // Validate requirements
658
- feedConfiguration.requirements.forEach((req, index) => {
659
- if (!req.name || !req.type || !req.version) {
660
- errors.push(`Requirement ${index + 1}: name, type, and version are required`);
661
- }
662
- if (req.registry) {
663
- const registryType = Object.keys(req.registry)[0];
664
- if (!['githubRelease', 'artifacts', 'local'].includes(registryType)) {
665
- errors.push(`Requirement ${index + 1}: Invalid registry type ${registryType}`);
666
- }
667
- }
668
- });
669
-
670
- if (errors.length > 0) {
671
- // Remove previous error messages
672
- formElement.querySelectorAll('.validation-error-message').forEach(el => el.remove());
673
-
674
- // Map field names to input selectors
675
- const fieldMap = {
676
- 'Category name is required': 'input[name="name"]',
677
- 'Category name must only contain alphanumeric characters, hyphens, and underscores': 'input[name="name"]',
678
- 'Category name must not exceed 50 characters': 'input[name="name"]',
679
- 'Display name is required': 'input[name="displayName"]',
680
- 'Display name must not exceed 100 characters': 'input[name="displayName"]',
681
- 'Description must not exceed 500 characters': 'textarea[name="description"]',
682
- 'Repository URL must be a valid URL': 'input[name="repository"]'
683
- };
684
-
685
- // Show error messages below relevant fields
686
- errors.forEach(errorMsg => {
687
- let selector = null;
688
- if (errorMsg.includes('Category name')) selector = 'input[name="name"]';
689
- else if (errorMsg.includes('Display name')) selector = 'input[name="displayName"]';
690
- // Add more mappings as needed
691
-
692
- if (selector) {
693
- const input = formElement.querySelector(selector);
694
- if (input) {
695
- const errorDiv = document.createElement('div');
696
- errorDiv.className = 'validation-error-message text-red-500 text-xs mt-1';
697
- errorDiv.textContent = errorMsg;
698
- input.insertAdjacentElement('afterend', errorDiv);
699
- }
700
- }
701
- });
702
- }
703
-
704
- return feedConfiguration;
705
- }
706
-
707
- /**
708
- * Populates the form with data from a FeedConfiguration object.
709
- * @param {object} feedConfig - The FeedConfiguration object.
710
- * @param {string} [formId='onboardForm'] - The ID of the form to populate.
711
- * @param {boolean} [renderServersAsReadOnly=false] - If true, renders servers from feedConfig as read-only.
712
- * This is used for the "Create Server in Existing Category" tab.
713
- * @param {string|null} [targetServersListId=null] - The ID of the servers list container. Defaults based on formId.
714
- */
715
- export function populateForm(feedConfig, formId = 'onboardForm', renderServersAsReadOnly = false, targetServersListId = null) {
716
- const currentForm = document.getElementById(formId);
717
- if (!currentForm) {
718
- console.error(`populateForm: Form with ID "${formId}" not found.`);
719
- return;
720
- }
721
-
722
- const serversListId = targetServersListId || (formId === 'onboardForm' ? 'serversList' : 'existingCategoryServersList');
723
-
724
- resetOnboardFormDynamicContent(formId, serversListId); // This also clears the target server list's innerHTML
725
-
726
- // Setup real-time validation
727
- setupRealTimeValidation(formId);
728
-
729
- if (!feedConfig) return;
730
-
731
- if (formId === 'onboardForm') {
732
- currentForm.querySelector('[name="name"]').value = feedConfig.name || '';
733
- currentForm.querySelector('[name="displayName"]').value = feedConfig.displayName || '';
734
- currentForm.querySelector('[name="description"]').value = feedConfig.description || '';
735
- if (feedConfig.repository) {
736
- currentForm.querySelector('[name="repository"]').value = feedConfig.repository;
737
- }
738
- const urlParams = new URLSearchParams(window.location.search);
739
- if (urlParams.get('action') === 'edit' && urlParams.get('category') === feedConfig.name) {
740
- const nameInput = currentForm.querySelector('[name="name"]');
741
- if (nameInput) {
742
- nameInput.readOnly = true;
743
- nameInput.classList.add('bg-gray-100', 'cursor-not-allowed');
744
- }
745
- }
746
- } else if (formId === 'onboardServerForm' && feedConfig) {
747
- // Populate basic category information for the "Create Server in Existing Category" tab
748
- // These fields are read-only in this tab, but their values need to be set from feedConfig.
749
- currentForm.querySelector('input[name="name"]').value = feedConfig.name || '';
750
- currentForm.querySelector('input[name="displayName"]').value = feedConfig.displayName || '';
751
- currentForm.querySelector('textarea[name="description"]').value = feedConfig.description || '';
752
- currentForm.querySelector('input[name="repository"]').value = feedConfig.repository || '';
753
-
754
- // Ensure the containers for basic info and MCP servers are visible,
755
- // as resetOnboardFormDynamicContent might have hidden them.
756
- const basicInfoContainer = document.getElementById('existingCategoryBasicInfoContainer');
757
- if (basicInfoContainer) {
758
- basicInfoContainer.classList.remove('hidden');
759
- }
760
- const mcpServersContainer = document.getElementById('existingCategoryMcpServersContainer');
761
- if (mcpServersContainer) {
762
- // Visibility of mcpServersContainer is also handled by whether servers exist,
763
- // but ensuring it's not hidden here is a good measure after reset.
764
- mcpServersContainer.classList.remove('hidden');
765
- }
766
- }
767
-
768
- // Reset tab-specific server counters before populating a list of servers from data.
769
- // This ensures that servers rendered by this populateForm call start their numbering fresh for this tab.
770
- setServerCounter(serversListId, 0);
771
- clearEnvCountersForTab(serversListId);
772
- clearServerRequirementCountersForTab(serversListId);
773
-
774
- (feedConfig.mcpServers || []).forEach((serverData) => { // Renamed 'server' to 'serverData' for clarity
775
- // Get the correct current index for this tab before adding the server
776
- const currentServerIndex = getServerCounter(serversListId);
777
- // window.addServer will increment the counter for serversListId internally
778
- // It also returns the server item, but we'll query it again for safety after DOM manipulation.
779
- window.addServer(serversListId, renderServersAsReadOnly, serverData);
780
-
781
- // After addServer, the server item should exist in the DOM.
782
- const serverItem = currentForm.querySelector(`#${serversListId} .server-item[data-index="${currentServerIndex}"]`);
783
-
784
- if (serverItem) {
785
- if (formId === 'onboardServerForm') {
786
- if (serverData.systemTags?.adhoc === "true") {
787
- // Case 1: JSON data explicitly marks it as adhoc. Respect this.
788
- serverItem.dataset.systemTags = JSON.stringify(serverData.systemTags);
789
- // console.log(`[populateForm] Server ${serverData.name || currentServerIndex} in ${formId} retains adhoc status from JSON.`);
790
- } else {
791
- // Case 2: JSON data does NOT explicitly mark it adhoc.
792
- // Check if it was an original server from the category.
793
- // state.originalServerNamesForFormPopulation is set when toggling from JSON to Form view for an existing category.
794
- if (state.originalServerNamesForFormPopulation && serverData.name && state.originalServerNamesForFormPopulation.has(serverData.name)) {
795
- // It's an original server from the category, now treated as adhoc because it passed through JSON view.
796
- serverItem.dataset.systemTags = JSON.stringify({ adhoc: "true" });
797
- // console.log(`[populateForm] Original server ${serverData.name} in ${formId} marked as adhoc after JSON view.`);
798
- } else {
799
- // It's a new server (not in original list) and JSON didn't mark it adhoc.
800
- // Or, it's an original server but its JSON representation explicitly removed/lacked adhoc tag.
801
- // Ensure it's NOT adhoc.
802
- // If serverData.systemTags exists but doesn't have adhoc:true, preserve those other tags.
803
- if (serverData.systemTags && Object.keys(serverData.systemTags).length > 0) {
804
- const newTags = { ...serverData.systemTags };
805
- delete newTags.adhoc; // Ensure adhoc is not true
806
- if (Object.keys(newTags).length > 0) {
807
- serverItem.dataset.systemTags = JSON.stringify(newTags);
808
- } else {
809
- delete serverItem.dataset.systemTags;
810
- }
811
- } else {
812
- delete serverItem.dataset.systemTags;
813
- }
814
- // console.log(`[populateForm] Server ${serverData.name || currentServerIndex} in ${formId} is NOT marked adhoc.`);
815
- }
816
- }
817
- } else {
818
- // For 'onboardForm' (Create Category tab), or if serverData had systemTags not making it adhoc.
819
- // If serverData has systemTags, reflect them. Otherwise, ensure no systemTags.
820
- if (serverData.systemTags) {
821
- serverItem.dataset.systemTags = JSON.stringify(serverData.systemTags);
822
- } else {
823
- delete serverItem.dataset.systemTags;
824
- }
825
- }
826
- }
827
- // Continue with populating fields using serverData
828
- const serverNameInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].name"]`);
829
- if (serverNameInput) serverNameInput.value = serverData.name || '';
830
-
831
- const modeInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].mode"]`);
832
- if (modeInput) {
833
- modeInput.value = serverData.mode || 'stdio';
834
- if (typeof window.renderInstallationConfig === 'function') {
835
- window.renderInstallationConfig(currentServerIndex, serversListId, serverData.mode || 'stdio', renderServersAsReadOnly, serverData.installation);
836
- }
837
- }
838
- const descInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].description"]`);
839
- if (descInput) descInput.value = serverData.description || '';
840
-
841
- if (serverData.schemas) {
842
- const schemaPathEl = document.getElementById(`schema-path-${currentServerIndex}`);
843
- if (schemaPathEl) schemaPathEl.value = serverData.schemas;
844
- }
845
- if (serverData.repository) {
846
- const repoInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].repository"]`);
847
- if (repoInput) repoInput.value = serverData.repository;
848
- }
849
-
850
- if (serverData.installation) {
851
- if (serverData.mode === 'sse') {
852
- const urlInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.url"]`);
853
- if (urlInput) urlInput.value = serverData.installation.url || '';
854
- } else {
855
- const cmdInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.command"]`);
856
- if (cmdInput) cmdInput.value = serverData.installation.command || '';
857
- if (serverData.installation.args && Array.isArray(serverData.installation.args)) {
858
- const argsInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.args"]`);
859
- if (argsInput) argsInput.value = serverData.installation.args.join(', ');
860
- }
861
- if (serverData.installation.env) {
862
- Object.entries(serverData.installation.env).forEach(([envName, envConfig]) => {
863
- const currentEnvIndex = window.addEnvVariable(currentServerIndex, serversListId, renderServersAsReadOnly);
864
-
865
- const nameInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].name"]`);
866
- if (nameInput) nameInput.value = envName;
867
- if (envConfig.Default) {
868
- const defInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].default"]`);
869
- if (defInput) defInput.value = envConfig.Default;
870
- }
871
- if (envConfig.Required) {
872
- const reqInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].required"]`);
873
- if (reqInput) reqInput.checked = true;
874
- }
875
- if (envConfig.Description) {
876
- const descInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].description"]`);
877
- if (descInput) descInput.value = envConfig.Description;
878
- }
879
- });
880
- }
881
- }
882
- }
883
-
884
- if (serverData.dependencies && serverData.dependencies.requirements) {
885
- // Determine if this server's requirements should be effectively read-only
886
- const serverIsEffectivelyReadOnlyForReqs = renderServersAsReadOnly && !(serverData.systemTags?.adhoc === 'true');
887
- // console.log(`[populateForm] Server: ${serverData.name}, Adhoc: ${serverData.systemTags?.adhoc === 'true'}, renderServersAsReadOnly: ${renderServersAsReadOnly}, Calculated serverIsEffectivelyReadOnlyForReqs: ${serverIsEffectivelyReadOnlyForReqs}`); // DEBUG
888
-
889
- serverData.dependencies.requirements.forEach((depReq) => {
890
- // Find requirement by name only, as requested.
891
- const fullReq = (feedConfig.requirements || []).find(r => r.name === depReq.name);
892
- if (!fullReq) {
893
- console.warn(`Could not find full requirement config for dependency name: ${depReq.name} (version ${depReq.version} specified by server, but lookup is by name only). Skipping.`);
894
- return;
895
- }
896
-
897
- // Pass serverIsEffectivelyReadOnlyForReqs to addServerRequirement
898
- const currentReqIndex = window.addServerRequirement(currentServerIndex, serversListId, serverIsEffectivelyReadOnlyForReqs);
899
-
900
- currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].name"]`).value = fullReq.name || '';
901
- currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].type"]`).value = fullReq.type || '';
902
- currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].version"]`).value = fullReq.version || '';
903
-
904
- if (depReq.order !== undefined) {
905
- currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].order"]`).value = depReq.order;
906
- }
907
-
908
- if (fullReq.type === 'command' && fullReq.alias) {
909
- currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].alias"]`).value = fullReq.alias;
910
- window.toggleServerAliasField(currentServerIndex, currentReqIndex, serversListId);
911
- }
912
-
913
- let registryType = 'public';
914
- if (fullReq.registry) {
915
- if (fullReq.registry.githubRelease) registryType = 'github';
916
- else if (fullReq.registry.artifacts) registryType = 'artifacts';
917
- else if (fullReq.registry.local) registryType = 'local';
918
- }
919
-
920
- const registrySelect = currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registryType"]`);
921
- if (registrySelect) {
922
- registrySelect.value = registryType;
923
- // Pass serversListId to ensure context is correct for toggling UI elements
924
- window.toggleServerRegistryConfig(currentServerIndex, currentReqIndex, serversListId);
925
- }
926
-
927
- if (registryType === 'github' && fullReq.registry.githubRelease) {
928
- const gh = fullReq.registry.githubRelease;
929
- if (gh.repository) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.githubRelease.repository"]`).value = gh.repository;
930
- if (gh.assetsName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.githubRelease.assetsName"]`).value = gh.assetsName;
931
- if (gh.assetName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.githubRelease.assetName"]`).value = gh.assetName;
932
- } else if (registryType === 'artifacts' && fullReq.registry.artifacts) {
933
- const art = fullReq.registry.artifacts;
934
- if (art.registryUrl) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.artifacts.registryUrl"]`).value = art.registryUrl;
935
- if (art.registryName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.artifacts.registryName"]`).value = art.registryName;
936
- } else if (registryType === 'local' && fullReq.registry.local) {
937
- const loc = fullReq.registry.local;
938
- if (loc.localPath) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.local.localPath"]`).value = loc.localPath;
939
- if (loc.assetName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.local.assetName"]`).value = loc.assetName;
940
- }
941
- });
942
- }
943
- });
944
- }
945
-
946
- /**
947
- * Resets the dynamic parts of the onboarding form, specifically the servers list.
948
- * Also resets server-related counters in the state.
949
- * This function is called before populating the form with new data (e.g., when loading an existing category
950
- * or switching from JSON view to form view).
951
- * @param {string} formId - The ID of the form whose dynamic content is to be reset.
952
- * @param {string} serversListId - The ID of the server list container to clear.
953
- */
954
- export function resetOnboardFormDynamicContent(formId = 'onboardForm', serversListId = 'serversList') {
955
- const formToReset = document.getElementById(formId);
956
- if (formToReset) {
957
- // formToReset.reset(); // This resets the entire form, including the category dropdown.
958
- // We want to avoid resetting the dropdown when a category is selected.
959
- // Other parts of this function handle clearing specific dynamic content.
960
- if (formId !== 'onboardServerForm') { // Only reset other forms, not the one with the category select that triggers loading
961
- formToReset.reset();
962
- }
963
- }
964
-
965
- const serversListContainer = document.getElementById(serversListId);
966
- if (serversListContainer) {
967
- serversListContainer.innerHTML = '';
968
- }
969
-
970
- setServerCounter(0);
971
- if (state && state.envCounters && typeof state.envCounters.clear === 'function') {
972
- state.envCounters.clear();
973
- }
974
- if (state && state.serverRequirementCounters && typeof state.serverRequirementCounters.clear === 'function') {
975
- state.serverRequirementCounters.clear();
976
- }
977
-
978
- // Determine the correct validation panel and content IDs based on formId
979
- let validationPanelIdToReset;
980
- let validationContentIdToReset;
981
-
982
- if (formId === 'onboardForm') {
983
- validationPanelIdToReset = 'validationStatusPanelNewCategory';
984
- validationContentIdToReset = 'validationStatusContentNewCategory';
985
- } else if (formId === 'onboardServerForm') {
986
- validationPanelIdToReset = 'validationStatusPanelExistingCategory';
987
- validationContentIdToReset = 'validationStatusContentExistingCategoryTab';
988
- }
989
- // else if other forms have validation panels, add conditions here
990
-
991
- if (validationPanelIdToReset) {
992
- const validationPanel = document.getElementById(validationPanelIdToReset);
993
- if (validationPanel) {
994
- validationPanel.classList.add('hidden');
995
- }
996
- }
997
- if (validationContentIdToReset) {
998
- const validationContent = document.getElementById(validationContentIdToReset);
999
- if (validationContent) {
1000
- validationContent.innerHTML = '';
1001
- }
1002
- }
1003
-
1004
- if (formId === 'onboardServerForm') {
1005
- // const existingCategorySelect = document.getElementById('existingCategorySelect');
1006
- // if (existingCategorySelect) existingCategorySelect.value = ''; // This was incorrectly resetting the dropdown
1007
-
1008
- const basicInfoContainer = document.getElementById('existingCategoryBasicInfoContainer');
1009
- if (basicInfoContainer) basicInfoContainer.classList.add('hidden');
1010
-
1011
- const mcpServersContainer = document.getElementById('existingCategoryMcpServersContainer');
1012
- if (mcpServersContainer) mcpServersContainer.classList.add('hidden');
1013
- }
1014
-
1015
- // Setup real-time validation after reset
1016
- setupRealTimeValidation(formId);
1017
- }
1018
-
1019
- /**
1020
- * Shows validation message under an input field
1021
- * @param {HTMLElement} element - The element to show validation message for
1022
- * @param {string} message - The validation message to display
1023
- * @param {boolean} isError - Whether this is an error message
1024
- */
1025
- export function showValidationMessage(element, message, isError = true) {
1026
- let messageDiv = element.nextElementSibling;
1027
- if (!messageDiv || !messageDiv.classList.contains('validation-message')) {
1028
- messageDiv = document.createElement('div');
1029
- messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
1030
- element.insertAdjacentElement('afterend', messageDiv);
1031
- }
1032
- messageDiv.textContent = message;
1033
- messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
1034
- }
1035
-
1036
- // Export setupRealTimeValidation for external use if needed
1037
- export { setupRealTimeValidation };