@yancyyu/openhermit 1.6.37 → 1.6.39

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 (205) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
  20. package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
  51. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
  52. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
  55. package/dist-renderer/index.html +20 -53
  56. package/package.json +25 -18
  57. package/src/main/ipc/extensions.ts +30 -50
  58. package/src/main/server.ts +890 -247
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
  62. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  63. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  64. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  65. package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
  66. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  67. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  68. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  69. package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
  70. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  71. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  72. package/src/main/utils/teamProjectResolution.ts +15 -0
  73. package/src/renderer/App.tsx +8 -4
  74. package/src/renderer/api/httpClient.ts +174 -38
  75. package/src/renderer/api/providers.ts +23 -2
  76. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  87. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  88. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  89. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  90. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  91. package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
  92. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  93. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  94. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  95. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  96. package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
  97. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
  98. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  99. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  100. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  101. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  102. package/src/renderer/components/layout/TabBar.tsx +1 -1
  103. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  104. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  105. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  106. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  107. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  108. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  109. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  110. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  111. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  112. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  113. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  114. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  115. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  116. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  117. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  118. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  119. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  120. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  121. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  122. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  123. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  124. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  126. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  127. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  128. package/src/renderer/components/settings/sections/index.ts +0 -1
  129. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  130. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  131. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  132. package/src/renderer/components/splash/splashScene.ts +121 -929
  133. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  134. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  135. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  136. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  137. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  138. package/src/renderer/components/team/TeamDetailView.tsx +325 -114
  139. package/src/renderer/components/team/TeamListView.tsx +108 -123
  140. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  141. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  142. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  143. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  144. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  145. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  146. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  147. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  148. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  149. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  150. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  151. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  152. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  153. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  154. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  155. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  156. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  157. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  158. package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
  159. package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
  160. package/src/renderer/components/team/tools/McpChip.tsx +45 -0
  161. package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
  162. package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
  163. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  164. package/src/renderer/index.css +39 -22
  165. package/src/renderer/index.html +17 -50
  166. package/src/renderer/store/index.ts +2 -1
  167. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  168. package/src/renderer/store/slices/teamSlice.ts +45 -168
  169. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  170. package/src/renderer/utils/memberHelpers.ts +5 -17
  171. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  172. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  173. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  174. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  175. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  176. package/src/shared/types/api.ts +121 -2
  177. package/src/shared/types/ccConnect.ts +2 -0
  178. package/src/shared/types/extensions/api.ts +9 -0
  179. package/src/shared/types/extensions/index.ts +4 -0
  180. package/src/shared/types/extensions/mcp.ts +41 -0
  181. package/src/shared/types/index.ts +3 -0
  182. package/src/shared/types/systemManager.ts +49 -0
  183. package/src/shared/types/team.ts +29 -0
  184. package/src/shared/types/terminal.ts +4 -2
  185. package/src/shared/utils/extensionNormalizers.ts +29 -0
  186. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  187. package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
  188. package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
  189. package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
  190. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
  191. package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
  192. package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
  193. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  194. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
  195. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
  196. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
  197. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
  198. package/src/main/services/extensions/install/McpInstallService.ts +0 -407
  199. package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
  200. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
  201. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
  202. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
  203. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
  204. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
  205. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
@@ -1,150 +0,0 @@
1
- /**
2
- * Aggregates MCP catalog data from Official Registry + Glama.
3
- *
4
- * - Uses Promise.allSettled so partial API failures don't break the whole catalog
5
- * - Dedup by repository URL; Official source takes priority
6
- * - Enriches Official entries with Glama data (license, tools) when matched
7
- * - Provides getById() for secure install flow
8
- */
9
-
10
- import { normalizeRepoUrl } from '@shared/utils/extensionNormalizers';
11
- import { createLogger } from '@shared/utils/logger';
12
-
13
- import { type GlamaMcpEnrichmentService } from './GlamaMcpEnrichmentService';
14
- import { type OfficialMcpRegistryService } from './OfficialMcpRegistryService';
15
-
16
- import type { McpCatalogItem, McpSearchResult } from '@shared/types/extensions';
17
-
18
- const logger = createLogger('Extensions:McpAggregator');
19
-
20
- export class McpCatalogAggregator {
21
- constructor(
22
- private readonly official: OfficialMcpRegistryService,
23
- private readonly glama: GlamaMcpEnrichmentService
24
- ) {}
25
-
26
- /**
27
- * Search both sources and return merged results.
28
- */
29
- async search(query: string, limit = 20): Promise<McpSearchResult> {
30
- const warnings: string[] = [];
31
-
32
- const [officialResult, glamaResult] = await Promise.allSettled([
33
- this.official.search(query, limit),
34
- this.glama.search(query, limit),
35
- ]);
36
-
37
- const officialServers = officialResult.status === 'fulfilled' ? officialResult.value : [];
38
- const glamaServers = glamaResult.status === 'fulfilled' ? glamaResult.value : [];
39
-
40
- if (officialResult.status === 'rejected') {
41
- warnings.push('Official MCP Registry unavailable');
42
- logger.warn('Official registry search failed:', officialResult.reason);
43
- }
44
- if (glamaResult.status === 'rejected') {
45
- warnings.push('Glama enrichment unavailable');
46
- logger.warn('Glama search failed:', glamaResult.reason);
47
- }
48
-
49
- const merged = this.mergeAndDeduplicate(officialServers, glamaServers);
50
-
51
- return { servers: merged, warnings };
52
- }
53
-
54
- /**
55
- * Browse the official registry with optional Glama enrichment.
56
- */
57
- async browse(
58
- cursor?: string,
59
- limit = 20
60
- ): Promise<{ servers: McpCatalogItem[]; nextCursor?: string }> {
61
- // Browse primarily from official registry (has pagination)
62
- const result = await this.official.browse(cursor, limit);
63
-
64
- // Optionally enrich with Glama data (best effort, no pagination sync)
65
- try {
66
- const glamaBrowse = await this.glama.browse(undefined, limit);
67
- const enriched = this.enrichOfficialWithGlama(result.servers, glamaBrowse.servers);
68
- return { servers: enriched, nextCursor: result.nextCursor };
69
- } catch {
70
- return result;
71
- }
72
- }
73
-
74
- /**
75
- * Get a single server by ID for secure install flow.
76
- * Delegates to appropriate source based on ID prefix.
77
- */
78
- async getById(registryId: string): Promise<McpCatalogItem | null> {
79
- // Glama IDs are prefixed with "glama:"
80
- if (registryId.startsWith('glama:')) {
81
- logger.warn(`Cannot install Glama-only server: ${registryId}`);
82
- return null; // Glama servers can't be auto-installed
83
- }
84
-
85
- // Official registry lookup
86
- return this.official.getById(registryId);
87
- }
88
-
89
- // ── Private ────────────────────────────────────────────────────────────
90
-
91
- /**
92
- * Merge Official + Glama, dedup by repository URL.
93
- * Official entries take priority.
94
- */
95
- private mergeAndDeduplicate(
96
- official: McpCatalogItem[],
97
- glama: McpCatalogItem[]
98
- ): McpCatalogItem[] {
99
- // Build repo URL index from official entries
100
- const officialRepoUrls = new Set<string>();
101
- for (const item of official) {
102
- if (item.repositoryUrl) {
103
- officialRepoUrls.add(normalizeRepoUrl(item.repositoryUrl));
104
- }
105
- }
106
-
107
- // Enrich official entries with Glama data
108
- const enriched = this.enrichOfficialWithGlama(official, glama);
109
-
110
- // Add Glama-only entries (no matching official entry)
111
- const glamaOnly = glama.filter((g) => {
112
- if (!g.repositoryUrl) return true; // no repo URL = can't match, show separately
113
- return !officialRepoUrls.has(normalizeRepoUrl(g.repositoryUrl));
114
- });
115
-
116
- return [...enriched, ...glamaOnly];
117
- }
118
-
119
- /**
120
- * Enrich official entries with Glama metadata (license, tools, glamaUrl).
121
- */
122
- private enrichOfficialWithGlama(
123
- official: McpCatalogItem[],
124
- glama: McpCatalogItem[]
125
- ): McpCatalogItem[] {
126
- // Index Glama by normalized repo URL
127
- const glamaByRepo = new Map<string, McpCatalogItem>();
128
- for (const g of glama) {
129
- if (g.repositoryUrl) {
130
- glamaByRepo.set(normalizeRepoUrl(g.repositoryUrl), g);
131
- }
132
- }
133
-
134
- return official.map((item) => {
135
- if (!item.repositoryUrl) return item;
136
-
137
- const glamaMatch = glamaByRepo.get(normalizeRepoUrl(item.repositoryUrl));
138
- if (!glamaMatch) return item;
139
-
140
- return {
141
- ...item,
142
- license: item.license ?? glamaMatch.license,
143
- tools: item.tools.length > 0 ? item.tools : glamaMatch.tools,
144
- glamaUrl: glamaMatch.glamaUrl,
145
- author: item.author ?? glamaMatch.author,
146
- hostingType: item.hostingType ?? glamaMatch.hostingType,
147
- };
148
- });
149
- }
150
- }
@@ -1,381 +0,0 @@
1
- /**
2
- * Fetches and normalizes MCP servers from the Official MCP Registry.
3
- *
4
- * Base URL: https://registry.modelcontextprotocol.io/v0.1/servers
5
- * Cursor-based pagination, no auth required.
6
- * Filters for _meta.isLatest to pick only latest versions.
7
- */
8
-
9
- import http from 'node:http';
10
- import https from 'node:https';
11
-
12
- import { createLogger } from '@shared/utils/logger';
13
-
14
- import type {
15
- McpAuthHeaderDef,
16
- McpCatalogItem,
17
- McpEnvVarDef,
18
- McpInstallSpec,
19
- } from '@shared/types/extensions';
20
-
21
- const logger = createLogger('Extensions:OfficialMcpRegistry');
22
-
23
- // ── Constants ──────────────────────────────────────────────────────────────
24
-
25
- const REGISTRY_BASE_URL = 'https://registry.modelcontextprotocol.io/v0.1/servers';
26
- const HTTP_TIMEOUT_MS = 15_000;
27
- const MAX_REDIRECTS = 5;
28
- const CACHE_TTL_MS = 15 * 60_000; // 15 minutes
29
- const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB safety limit
30
-
31
- // ── HTTP helper ────────────────────────────────────────────────────────────
32
-
33
- function httpGet(
34
- url: string,
35
- redirectsLeft = MAX_REDIRECTS
36
- ): Promise<{ statusCode: number; body: string }> {
37
- return new Promise((resolve, reject) => {
38
- const parsedUrl = new URL(url);
39
- const transport = parsedUrl.protocol === 'http:' ? http : https;
40
- let settled = false;
41
-
42
- const settleResolve = (v: { statusCode: number; body: string }): void => {
43
- if (!settled) {
44
- settled = true;
45
- resolve(v);
46
- }
47
- };
48
- const settleReject = (e: Error): void => {
49
- if (!settled) {
50
- settled = true;
51
- reject(e);
52
- }
53
- };
54
-
55
- const req = transport.get(url, (res) => {
56
- const status = res.statusCode ?? 0;
57
- if (status >= 300 && status < 400 && res.headers.location) {
58
- if (redirectsLeft <= 0) {
59
- res.destroy();
60
- settleReject(new Error('Too many redirects'));
61
- return;
62
- }
63
- const redirectUrl = new URL(res.headers.location, url).toString();
64
- res.destroy();
65
- httpGet(redirectUrl, redirectsLeft - 1).then(settleResolve, settleReject);
66
- return;
67
- }
68
- const chunks: Buffer[] = [];
69
- let totalSize = 0;
70
- res.on('data', (c: Buffer) => {
71
- totalSize += c.length;
72
- if (totalSize > MAX_BODY_SIZE) {
73
- res.destroy(new Error(`Response body exceeds ${MAX_BODY_SIZE} bytes`));
74
- return;
75
- }
76
- chunks.push(c);
77
- });
78
- res.on('end', () =>
79
- settleResolve({ statusCode: status, body: Buffer.concat(chunks).toString('utf-8') })
80
- );
81
- res.on('error', settleReject);
82
- });
83
- req.setTimeout(HTTP_TIMEOUT_MS, () => req.destroy(new Error(`Timeout fetching ${url}`)));
84
- req.on('error', (e) => settleReject(e instanceof Error ? e : new Error(String(e))));
85
- });
86
- }
87
-
88
- // ── Raw API response shapes ────────────────────────────────────────────────
89
-
90
- interface RegistryResponse {
91
- servers: RegistryServerEntry[];
92
- metadata: { nextCursor?: string; count?: number };
93
- }
94
-
95
- interface RegistryIcon {
96
- src: string;
97
- mimeType?: string;
98
- sizes?: string[];
99
- theme?: 'light' | 'dark';
100
- }
101
-
102
- interface RegistryServerEntry {
103
- server: {
104
- name: string;
105
- description?: string;
106
- title?: string;
107
- version?: string;
108
- repository?: { url: string; source?: string };
109
- websiteUrl?: string;
110
- packages?: RegistryPackage[];
111
- remotes?: RegistryRemote[];
112
- icons?: RegistryIcon[];
113
- };
114
- _meta?: {
115
- 'io.modelcontextprotocol.registry/official'?: {
116
- status?: string;
117
- isLatest?: boolean;
118
- publishedAt?: string;
119
- updatedAt?: string;
120
- };
121
- };
122
- }
123
-
124
- interface RegistryPackage {
125
- registryType: string;
126
- identifier: string;
127
- version?: string;
128
- transport?: { type: string };
129
- environmentVariables?: RegistryEnvVar[];
130
- }
131
-
132
- interface RegistryRemote {
133
- type: string;
134
- url: string;
135
- headers?: RegistryHeader[];
136
- }
137
-
138
- interface RegistryHeader {
139
- name: string;
140
- description?: string;
141
- isRequired?: boolean;
142
- isSecret?: boolean;
143
- value?: string;
144
- }
145
-
146
- interface RegistryEnvVar {
147
- name: string;
148
- description?: string;
149
- isSecret?: boolean;
150
- isRequired?: boolean;
151
- }
152
-
153
- // ── Cache ──────────────────────────────────────────────────────────────────
154
-
155
- interface SearchCache {
156
- key: string;
157
- result: McpCatalogItem[];
158
- fetchedAt: number;
159
- }
160
-
161
- // ── Service ────────────────────────────────────────────────────────────────
162
-
163
- export class OfficialMcpRegistryService {
164
- private searchCache: SearchCache | null = null;
165
-
166
- /**
167
- * Search the official registry by query text.
168
- */
169
- async search(query: string, limit = 20): Promise<McpCatalogItem[]> {
170
- const cacheKey = `search:${query}:${limit}`;
171
- if (
172
- this.searchCache?.key === cacheKey &&
173
- Date.now() - this.searchCache.fetchedAt < CACHE_TTL_MS
174
- ) {
175
- return this.searchCache.result;
176
- }
177
-
178
- const params = new URLSearchParams({ search: query, limit: String(limit) });
179
- const url = `${REGISTRY_BASE_URL}?${params}`;
180
-
181
- try {
182
- const resp = await httpGet(url);
183
- if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
184
- const json = JSON.parse(resp.body) as RegistryResponse;
185
- const items = this.normalizeServers(json.servers);
186
- this.searchCache = { key: cacheKey, result: items, fetchedAt: Date.now() };
187
- return items;
188
- } catch (err) {
189
- logger.error('Official MCP Registry search failed:', err);
190
- return this.searchCache?.result ?? [];
191
- }
192
- }
193
-
194
- /**
195
- * Browse the registry with cursor-based pagination.
196
- */
197
- async browse(
198
- cursor?: string,
199
- limit = 20
200
- ): Promise<{ servers: McpCatalogItem[]; nextCursor?: string }> {
201
- const params = new URLSearchParams({ limit: String(limit) });
202
- if (cursor) params.set('cursor', cursor);
203
- const url = `${REGISTRY_BASE_URL}?${params}`;
204
-
205
- try {
206
- const resp = await httpGet(url);
207
- if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
208
- const json = JSON.parse(resp.body) as RegistryResponse;
209
- return {
210
- servers: this.normalizeServers(json.servers),
211
- nextCursor: json.metadata.nextCursor,
212
- };
213
- } catch (err) {
214
- logger.error('Official MCP Registry browse failed:', err);
215
- return { servers: [] };
216
- }
217
- }
218
-
219
- /**
220
- * Get a single server by its registry ID (reverse-DNS name).
221
- * Used for secure install flow (main-side re-resolution).
222
- */
223
- async getById(registryId: string): Promise<McpCatalogItem | null> {
224
- // The official registry search API can find by exact name
225
- const params = new URLSearchParams({ search: registryId, limit: '5' });
226
- const url = `${REGISTRY_BASE_URL}?${params}`;
227
-
228
- try {
229
- const resp = await httpGet(url);
230
- if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
231
- const json = JSON.parse(resp.body) as RegistryResponse;
232
- const items = this.normalizeServers(json.servers);
233
- return items.find((s) => s.id === registryId) ?? null;
234
- } catch (err) {
235
- logger.error(`Official MCP Registry getById(${registryId}) failed:`, err);
236
- return null;
237
- }
238
- }
239
-
240
- // ── Private ────────────────────────────────────────────────────────────
241
-
242
- private normalizeServers(entries: RegistryServerEntry[]): McpCatalogItem[] {
243
- // Filter to isLatest only (same server name may appear multiple times)
244
- const latest = entries.filter((e) => {
245
- const meta = e._meta?.['io.modelcontextprotocol.registry/official'];
246
- return meta?.isLatest !== false; // include if isLatest is true or undefined
247
- });
248
-
249
- // Filter to active only (include servers with no status or status "active")
250
- const active = latest.filter((e) => {
251
- const meta = e._meta?.['io.modelcontextprotocol.registry/official'];
252
- const status = meta?.status;
253
- return !status || status === 'active';
254
- });
255
-
256
- // Deduplicate by server name (take first = latest version)
257
- const seen = new Set<string>();
258
- const unique: RegistryServerEntry[] = [];
259
- for (const entry of active) {
260
- if (!seen.has(entry.server.name)) {
261
- seen.add(entry.server.name);
262
- unique.push(entry);
263
- }
264
- }
265
-
266
- return unique.map((entry) => this.normalizeEntry(entry));
267
- }
268
-
269
- private normalizeEntry(entry: RegistryServerEntry): McpCatalogItem {
270
- const { server } = entry;
271
- const meta = entry._meta?.['io.modelcontextprotocol.registry/official'];
272
- const installSpec = this.deriveInstallSpec(server);
273
- const envVars = this.collectEnvVars(server);
274
- const authHeaders = this.collectAuthHeaders(server);
275
- const requiresAuth = this.detectAuthRequired(server);
276
-
277
- return {
278
- id: server.name,
279
- name: server.title ?? server.name.split('/').pop() ?? server.name,
280
- description: server.description ?? '',
281
- repositoryUrl: server.repository?.url,
282
- version: server.version,
283
- source: 'official',
284
- installSpec,
285
- envVars,
286
- license: undefined, // Official registry doesn't expose license
287
- tools: [], // Tools not included in registry list response
288
- glamaUrl: undefined,
289
- requiresAuth,
290
- iconUrl: this.pickIconUrl(server.icons),
291
- websiteUrl: server.websiteUrl,
292
- status: meta?.status,
293
- publishedAt: meta?.publishedAt,
294
- updatedAt: meta?.updatedAt,
295
- authHeaders,
296
- };
297
- }
298
-
299
- private deriveInstallSpec(server: RegistryServerEntry['server']): McpInstallSpec | null {
300
- // Prefer npm stdio package
301
- const npmPkg = server.packages?.find((p) => p.registryType === 'npm');
302
- if (npmPkg) {
303
- return {
304
- type: 'stdio',
305
- npmPackage: npmPkg.identifier,
306
- npmVersion: npmPkg.version,
307
- };
308
- }
309
-
310
- // HTTP/SSE remote
311
- const remote = server.remotes?.[0];
312
- if (remote) {
313
- return {
314
- type: 'http',
315
- url: remote.url,
316
- transportType: remote.type as 'streamable-http' | 'sse' | 'http',
317
- };
318
- }
319
-
320
- return null;
321
- }
322
-
323
- private collectEnvVars(server: RegistryServerEntry['server']): McpEnvVarDef[] {
324
- const envVars: McpEnvVarDef[] = [];
325
-
326
- // From packages
327
- for (const pkg of server.packages ?? []) {
328
- for (const ev of pkg.environmentVariables ?? []) {
329
- envVars.push({
330
- name: ev.name,
331
- isSecret: ev.isSecret ?? false,
332
- description: ev.description,
333
- isRequired: ev.isRequired,
334
- });
335
- }
336
- }
337
-
338
- return envVars;
339
- }
340
-
341
- private collectAuthHeaders(server: RegistryServerEntry['server']): McpAuthHeaderDef[] {
342
- const headers: McpAuthHeaderDef[] = [];
343
- const seenKeys = new Set<string>();
344
-
345
- for (const remote of server.remotes ?? []) {
346
- for (const header of remote.headers ?? []) {
347
- const key = header.name.trim();
348
- if (!key || seenKeys.has(key)) {
349
- continue;
350
- }
351
- seenKeys.add(key);
352
- headers.push({
353
- key,
354
- description: header.description,
355
- isRequired: header.isRequired,
356
- isSecret: header.isSecret,
357
- valueTemplate: header.value,
358
- });
359
- }
360
- }
361
-
362
- return headers;
363
- }
364
-
365
- private detectAuthRequired(server: RegistryServerEntry['server']): boolean {
366
- for (const remote of server.remotes ?? []) {
367
- for (const header of remote.headers ?? []) {
368
- if (header.isRequired) return true;
369
- }
370
- }
371
- return false;
372
- }
373
-
374
- /** Pick best icon URL from the registry icons array (prefer dark theme PNG). */
375
- private pickIconUrl(icons?: RegistryIcon[]): string | undefined {
376
- if (!icons || icons.length === 0) return undefined;
377
- // Prefer dark-theme icon, then first available
378
- const darkIcon = icons.find((i) => i.theme === 'dark');
379
- return (darkIcon ?? icons[0]).src;
380
- }
381
- }