@yancyyu/openhermit 1.6.28 → 1.6.30
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.
- package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +907 -184
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +128 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +55 -98
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +67 -25
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +58 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/types/team.ts +104 -1
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
- package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
- package/dist-renderer/assets/index-BIOJremZ.css +0 -1
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -0,0 +1,381 @@
|
|
|
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
|
+
}
|