@red-hat-developer-hub/backstage-plugin-catalog-backend-module-mcp-registry-provider 0.2.0

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.
@@ -0,0 +1,202 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+ import type { SchedulerServiceTaskScheduleDefinition, LoggerService, SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
3
+ import type { EntityProvider, EntityProviderConnection } from '@backstage/plugin-catalog-node';
4
+
5
+
6
+ /**
7
+ * The mcp-registry-provider backend module for the catalog plugin.
8
+ *
9
+ * Registers a single entity provider that ingests MCP servers from
10
+ * one configured MCP Registry into the catalog as `mcp-server` API
11
+ * entities.
12
+ *
13
+ * @public
14
+ */
15
+ declare const catalogModuleMcpRegistryProvider: _backstage_backend_plugin_api.BackendFeature;
16
+
17
+
18
+
19
+ /**
20
+ * Provider configuration. Fields with documented defaults may be omitted
21
+ * on direct construction; the entity provider and config reader apply the
22
+ * same defaults as app-config parsing.
23
+ *
24
+ * @public
25
+ */
26
+ interface McpRegistryProviderConfig {
27
+ /** Base URL of the MCP Registry (required). */
28
+ baseUrl: string;
29
+ /** Optional identity prefix override passed to the mapping transform. */
30
+ baseName?: string;
31
+ /** Registry API version slug used in the endpoint path (default `v1`). */
32
+ apiVersion?: string;
33
+ /** Default entity owner ref when the mapping does not supply one. */
34
+ defaultOwner?: string;
35
+ /** Default entity lifecycle when the mapping does not supply one. */
36
+ defaultLifecycle?: string;
37
+ /** Maximum pages fetched per sync (default `10`); excess pages resume next sync. */
38
+ pageLimit?: number;
39
+ /** Registry `?limit=` page-size query; omitted from the request when unset. */
40
+ pageSize?: number;
41
+ /**
42
+ * Maximum total entries buffered for one complete registry traversal
43
+ * before a full mutation (default `5000`). Spans resume syncs when
44
+ * `pageLimit` pauses mid-traversal. When exceeded, the provider
45
+ * commits the buffer, saves an end cursor, and later traversals stop
46
+ * at that cursor until `maxEntries` is patched.
47
+ */
48
+ maxEntries?: number;
49
+ /**
50
+ * When true, only ingest servers with at least one native remote.
51
+ * Package-only / placeholder-remote servers are skipped (default `false`).
52
+ */
53
+ remotesOnly?: boolean;
54
+ /**
55
+ * When true, list requests include `?version=latest` so the registry
56
+ * returns only the latest version of each server (default `false`).
57
+ */
58
+ latestVersion?: boolean;
59
+ /** Optional allowlist of permitted hostnames for defense-in-depth SSRF protection. */
60
+ hostAllowList?: string[];
61
+ /** Schedule for the sync task. */
62
+ schedule: SchedulerServiceTaskScheduleDefinition;
63
+ }
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+ /**
74
+ * Optional constructor dependencies for {@link McpRegistryEntityProvider}.
75
+ *
76
+ * @public
77
+ */
78
+ interface McpRegistryEntityProviderOptions {
79
+ /** Scheduler task runner used to periodically invoke sync. */
80
+ taskRunner?: SchedulerServiceTaskRunner;
81
+ }
82
+ /**
83
+ * Entity provider that ingests MCP servers from one configured
84
+ * MCP Registry into the Backstage catalog.
85
+ *
86
+ * @public
87
+ */
88
+ declare class McpRegistryEntityProvider implements EntityProvider {
89
+ private connection?;
90
+ private readonly config;
91
+ private readonly logger;
92
+ private readonly fetchApi?;
93
+ private readonly taskRunner?;
94
+ /**
95
+ * Internal last-good index: keyed by `name::version`, stores the
96
+ * last successfully committed DeferredEntity so that a subsequent
97
+ * sync can retain it when mapping fails (D6).
98
+ */
99
+ private readonly lastGoodIndex;
100
+ /**
101
+ * Resume state for multi-sync pagination. When a sync hits
102
+ * `pageLimit` with more pages remaining, entries fetched so far and
103
+ * the next cursor are kept here so the following sync continues
104
+ * instead of restarting. Cleared when a traversal reaches the end
105
+ * of the registry (no next cursor) or a `maxEntries` soft-stop and a
106
+ * full mutation is committed.
107
+ */
108
+ private resumeCursor?;
109
+ private pendingEntries;
110
+ private seenCursors;
111
+ /**
112
+ * After a `maxEntries` soft-stop, later full traversals end at this
113
+ * cursor instead of a missing `nextCursor`. Cleared when
114
+ * `maxEntries` is patched (value differs from when it was saved).
115
+ */
116
+ private endCursor?;
117
+ private endCursorMaxEntries?;
118
+ constructor(config: McpRegistryProviderConfig, logger: LoggerService, options?: McpRegistryEntityProviderOptions);
119
+ getProviderName(): string;
120
+ connect(connection: EntityProviderConnection): Promise<void>;
121
+ /**
122
+ * Fetch registry servers for this sync tick.
123
+ *
124
+ * Continues from `resumeCursor` when a prior sync stopped at
125
+ * `pageLimit`. Returns `undefined` when a client error aborts the
126
+ * tick without a mutation, or when more pages remain (entries are
127
+ * buffered until the registry is exhausted or `maxEntries` stops the
128
+ * traversal so a full mutation does not prune unfetched servers).
129
+ *
130
+ * Hitting `maxEntries` commits a full mutation of the buffer, saves
131
+ * `endCursor`, and starts the next cycle from the beginning. Later
132
+ * full traversals stop at that `endCursor` until `maxEntries` is
133
+ * patched.
134
+ */
135
+ private fetchRegistryEntries;
136
+ /**
137
+ * Map every registry entry with per-entry failure isolation.
138
+ * When `remotesOnly` is set, entries without a native remote are skipped.
139
+ */
140
+ private mapRegistryEntries;
141
+ /**
142
+ * When a `maxEntries` soft-stop is active (`endCursor` set), retain
143
+ * last-good entities that fell outside the truncated window so a full
144
+ * mutation does not prune them. Mark retained copies as degraded.
145
+ *
146
+ * Full traversals without an end bound continue to prune servers that
147
+ * are absent from the registry.
148
+ */
149
+ private appendSoftStopRetained;
150
+ /**
151
+ * Map one registry entry into a deferred entity with sync status `ok`.
152
+ */
153
+ private mapRegistryEntry;
154
+ /**
155
+ * Build mapping caller overrides from provider config.
156
+ *
157
+ * `placeholderRemoteUrl` is the target registry `baseUrl` so a server
158
+ * with no valid remotes still gets a D8 placeholder pointing at that
159
+ * registry, before the mapping falls back to `websiteUrl`.
160
+ */
161
+ private buildMappingDefaults;
162
+ /**
163
+ * Stamp provider-owned location and sync-status annotations.
164
+ *
165
+ * Catalog processing requires both location annotations. Without the
166
+ * origin annotation the entity is rejected and never listed.
167
+ */
168
+ private applyProviderAnnotations;
169
+ /**
170
+ * Log a mapping failure and retain a last-good entity when available (D6).
171
+ */
172
+ private retainLastGoodOnMappingFailure;
173
+ /**
174
+ * Rebuild the last-good index from every committed entity that has a
175
+ * registry identity (`ok` and `degraded` alike).
176
+ *
177
+ * Degraded entries stay indexed so they can be re-added on later syncs
178
+ * until the server is refreshed successfully (`ok`) or omitted from the
179
+ * mutation entirely (true prune after a full traversal without soft-stop
180
+ * retention).
181
+ *
182
+ * The annotation keys used here ('modelcontextprotocol.io/name' and
183
+ * 'modelcontextprotocol.io/version') are set by mapServerToEntity in
184
+ * catalog-mcp-registry-server-mapping and correspond to the raw
185
+ * serverDoc.name and serverDoc.version fields used in buildLastGoodKey
186
+ * during failure recovery. If the mapping library changes these
187
+ * annotation keys, both this rebuild and the failure recovery path
188
+ * must be updated in tandem.
189
+ */
190
+ private rebuildLastGoodIndex;
191
+ }
192
+
193
+ /**
194
+ * The mcp-registry-provider backend module for the catalog plugin.
195
+ *
196
+ * @packageDocumentation
197
+ */
198
+
199
+
200
+
201
+ export { McpRegistryEntityProvider, catalogModuleMcpRegistryProvider as default };
202
+ export type { McpRegistryEntityProviderOptions, McpRegistryProviderConfig };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var pluginCatalogNode = require('@backstage/plugin-catalog-node');
5
+ var config = require('./config.cjs.js');
6
+ var McpRegistryEntityProvider = require('./McpRegistryEntityProvider.cjs.js');
7
+
8
+ const catalogModuleMcpRegistryProvider = backendPluginApi.createBackendModule({
9
+ pluginId: "catalog",
10
+ moduleId: "mcp-registry-provider",
11
+ register(env) {
12
+ env.registerInit({
13
+ deps: {
14
+ catalog: pluginCatalogNode.catalogProcessingExtensionPoint,
15
+ config: backendPluginApi.coreServices.rootConfig,
16
+ logger: backendPluginApi.coreServices.logger,
17
+ scheduler: backendPluginApi.coreServices.scheduler
18
+ },
19
+ async init({ catalog, config: config$1, logger, scheduler }) {
20
+ const providerConfig = config.readMcpRegistryProviderConfig(
21
+ config$1,
22
+ (message) => logger.warn(message)
23
+ );
24
+ if (!providerConfig) {
25
+ logger.info(
26
+ "catalog.providers.mcpRegistry.mcpRegistry not configured; MCP Registry provider is inactive."
27
+ );
28
+ return;
29
+ }
30
+ const taskRunner = scheduler.createScheduledTaskRunner(
31
+ providerConfig.schedule
32
+ );
33
+ const provider = new McpRegistryEntityProvider.McpRegistryEntityProvider(providerConfig, logger, {
34
+ taskRunner
35
+ });
36
+ catalog.addEntityProvider(provider);
37
+ }
38
+ });
39
+ }
40
+ });
41
+
42
+ exports.catalogModuleMcpRegistryProvider = catalogModuleMcpRegistryProvider;
43
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';\nimport { readMcpRegistryProviderConfig } from './config';\nimport { McpRegistryEntityProvider } from './McpRegistryEntityProvider';\n\n/**\n * The mcp-registry-provider backend module for the catalog plugin.\n *\n * Registers a single entity provider that ingests MCP servers from\n * one configured MCP Registry into the catalog as `mcp-server` API\n * entities.\n *\n * @public\n */\nexport const catalogModuleMcpRegistryProvider = createBackendModule({\n pluginId: 'catalog',\n moduleId: 'mcp-registry-provider',\n register(env) {\n env.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({ catalog, config, logger, scheduler }) {\n const providerConfig = readMcpRegistryProviderConfig(config, message =>\n logger.warn(message),\n );\n\n if (!providerConfig) {\n logger.info(\n 'catalog.providers.mcpRegistry.mcpRegistry not configured; ' +\n 'MCP Registry provider is inactive.',\n );\n return;\n }\n\n const taskRunner = scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n const provider = new McpRegistryEntityProvider(providerConfig, logger, {\n taskRunner,\n });\n\n catalog.addEntityProvider(provider);\n },\n });\n },\n});\n"],"names":["createBackendModule","catalogProcessingExtensionPoint","coreServices","config","readMcpRegistryProviderConfig","McpRegistryEntityProvider"],"mappings":";;;;;;;AAiCO,MAAM,mCAAmCA,oCAAA,CAAoB;AAAA,EAClE,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,uBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,OAAA,EAASC,iDAAA;AAAA,QACT,QAAQC,6BAAA,CAAa,UAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa,MAAA;AAAA,QACrB,WAAWA,6BAAA,CAAa;AAAA,OAC1B;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,iBAASC,QAAA,EAAQ,MAAA,EAAQ,WAAU,EAAG;AACjD,QAAA,MAAM,cAAA,GAAiBC,oCAAA;AAAA,UAA8BD,QAAA;AAAA,UAAQ,CAAA,OAAA,KAC3D,MAAA,CAAO,IAAA,CAAK,OAAO;AAAA,SACrB;AAEA,QAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,UAAA,MAAA,CAAO,IAAA;AAAA,YACL;AAAA,WAEF;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,aAAa,SAAA,CAAU,yBAAA;AAAA,UAC3B,cAAA,CAAe;AAAA,SACjB;AACA,QAAA,MAAM,QAAA,GAAW,IAAIE,mDAAA,CAA0B,cAAA,EAAgB,MAAA,EAAQ;AAAA,UACrE;AAAA,SACD,CAAA;AAED,QAAA,OAAA,CAAQ,kBAAkB,QAAQ,CAAA;AAAA,MACpC;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var backstagePluginCatalogMcpRegistryServerMapping = require('@red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping');
4
+ var util = require('./util.cjs.js');
5
+
6
+ function hasNativeRemote(doc) {
7
+ const remotes = doc?.remotes ?? [];
8
+ for (const remote of remotes) {
9
+ if (typeof remote.type === "string" && remote.type.length > 0 && remote.url !== void 0 && remote.url !== null && backstagePluginCatalogMcpRegistryServerMapping.isAllowedUrl(remote.url)) {
10
+ return true;
11
+ }
12
+ }
13
+ return false;
14
+ }
15
+ function buildLastGoodKey(name, version) {
16
+ return `${name}::${version}`;
17
+ }
18
+ function readServerIdentity(entry) {
19
+ const serverDoc = entry?.server;
20
+ return {
21
+ name: typeof serverDoc?.name === "string" ? serverDoc.name : void 0,
22
+ version: typeof serverDoc?.version === "string" ? serverDoc.version : void 0
23
+ };
24
+ }
25
+ function formatMappingFailureMessage(name, version, err) {
26
+ let message = "Failed to map MCP Registry server entry";
27
+ if (name) {
28
+ message += ` "${name}"`;
29
+ }
30
+ if (version) {
31
+ message += ` (version "${version}")`;
32
+ }
33
+ return `${message}: ${util.formatErrorDetail(err)}`;
34
+ }
35
+
36
+ exports.buildLastGoodKey = buildLastGoodKey;
37
+ exports.formatMappingFailureMessage = formatMappingFailureMessage;
38
+ exports.hasNativeRemote = hasNativeRemote;
39
+ exports.readServerIdentity = readServerIdentity;
40
+ //# sourceMappingURL=providerUtils.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerUtils.cjs.js","sources":["../src/providerUtils.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isAllowedUrl } from '@red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping';\nimport type { McpServerDocument } from '@red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping';\nimport type { McpRegistryServerEntry } from './client';\nimport { formatErrorDetail } from './util';\n\n/**\n * Whether a server.json document declares at least one native remote\n * (non-empty type and D11-valid URL). Matches the mapping's copy rules\n * for remotes that become `spec.remotes` rather than D8 placeholders.\n *\n * @internal\n */\nexport function hasNativeRemote(doc: McpServerDocument | undefined): boolean {\n const remotes = doc?.remotes ?? [];\n for (const remote of remotes) {\n if (\n typeof remote.type === 'string' &&\n remote.type.length > 0 &&\n remote.url !== undefined &&\n remote.url !== null &&\n isAllowedUrl(remote.url)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Build a last-good lookup key from name and version.\n *\n * @internal\n */\nexport function buildLastGoodKey(name: string, version: string): string {\n return `${name}::${version}`;\n}\n\n/**\n * Read optional name/version from a registry list entry.\n *\n * @internal\n */\nexport function readServerIdentity(\n entry: McpRegistryServerEntry | null | undefined,\n): {\n name?: string;\n version?: string;\n} {\n const serverDoc = entry?.server;\n return {\n name: typeof serverDoc?.name === 'string' ? serverDoc.name : undefined,\n version:\n typeof serverDoc?.version === 'string' ? serverDoc.version : undefined,\n };\n}\n\n/**\n * Format the per-entry mapping failure warning.\n *\n * @internal\n */\nexport function formatMappingFailureMessage(\n name: string | undefined,\n version: string | undefined,\n err: unknown,\n): string {\n let message = 'Failed to map MCP Registry server entry';\n if (name) {\n message += ` \"${name}\"`;\n }\n if (version) {\n message += ` (version \"${version}\")`;\n }\n return `${message}: ${formatErrorDetail(err)}`;\n}\n"],"names":["isAllowedUrl","formatErrorDetail"],"mappings":";;;;;AA4BO,SAAS,gBAAgB,GAAA,EAA6C;AAC3E,EAAA,MAAM,OAAA,GAAU,GAAA,EAAK,OAAA,IAAW,EAAC;AACjC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IACE,OAAO,MAAA,CAAO,IAAA,KAAS,QAAA,IACvB,MAAA,CAAO,KAAK,MAAA,GAAS,CAAA,IACrB,MAAA,CAAO,GAAA,KAAQ,UACf,MAAA,CAAO,GAAA,KAAQ,QACfA,2DAAA,CAAa,MAAA,CAAO,GAAG,CAAA,EACvB;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAOO,SAAS,gBAAA,CAAiB,MAAc,OAAA,EAAyB;AACtE,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA;AAC5B;AAOO,SAAS,mBACd,KAAA,EAIA;AACA,EAAA,MAAM,YAAY,KAAA,EAAO,MAAA;AACzB,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,SAAA,EAAW,IAAA,KAAS,QAAA,GAAW,UAAU,IAAA,GAAO,MAAA;AAAA,IAC7D,SACE,OAAO,SAAA,EAAW,OAAA,KAAY,QAAA,GAAW,UAAU,OAAA,GAAU;AAAA,GACjE;AACF;AAOO,SAAS,2BAAA,CACd,IAAA,EACA,OAAA,EACA,GAAA,EACQ;AACR,EAAA,IAAI,OAAA,GAAU,yCAAA;AACd,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,OAAA,IAAW,KAAK,IAAI,CAAA,CAAA,CAAA;AAAA,EACtB;AACA,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,OAAA,IAAW,cAAc,OAAO,CAAA,EAAA,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,EAAA,EAAKC,sBAAA,CAAkB,GAAG,CAAC,CAAA,CAAA;AAC9C;;;;;;;"}
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ function stripTrailingSlashes(value) {
4
+ let end = value.length;
5
+ while (end > 0 && value.charAt(end - 1) === "/") {
6
+ end -= 1;
7
+ }
8
+ return end === value.length ? value : value.slice(0, end);
9
+ }
10
+ function formatErrorDetail(err) {
11
+ if (err instanceof Error) {
12
+ let current = err;
13
+ for (let depth = 0; depth < 5; depth += 1) {
14
+ const cause = current.cause;
15
+ if (!(cause instanceof Error) || !cause.message) {
16
+ break;
17
+ }
18
+ current = cause;
19
+ }
20
+ if (current.message) {
21
+ return current.message;
22
+ }
23
+ return current.name || "unknown error";
24
+ }
25
+ if (typeof err === "string" && err.length > 0) {
26
+ return err;
27
+ }
28
+ return String(err);
29
+ }
30
+
31
+ exports.formatErrorDetail = formatErrorDetail;
32
+ exports.stripTrailingSlashes = stripTrailingSlashes;
33
+ //# sourceMappingURL=util.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.cjs.js","sources":["../src/util.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Strip trailing `/` characters with a linear scan (no regex backtracking).\n *\n * @internal\n */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charAt(end - 1) === '/') {\n end -= 1;\n }\n return end === value.length ? value : value.slice(0, end);\n}\n\n/**\n * Format an unknown thrown value for operator-facing error text.\n *\n * Prefer the deepest `cause` message (Node/undici often wraps network\n * failures as `TypeError: fetch failed` with a useful cause). Never\n * prefixes the Error constructor name (e.g. `TypeError:`).\n *\n * @internal\n */\nexport function formatErrorDetail(err: unknown): string {\n if (err instanceof Error) {\n let current: Error = err;\n // Walk a short cause chain for a more specific message.\n for (let depth = 0; depth < 5; depth += 1) {\n const cause = (current as Error & { cause?: unknown }).cause;\n if (!(cause instanceof Error) || !cause.message) {\n break;\n }\n current = cause;\n }\n if (current.message) {\n return current.message;\n }\n return current.name || 'unknown error';\n }\n if (typeof err === 'string' && err.length > 0) {\n return err;\n }\n return String(err);\n}\n"],"names":[],"mappings":";;AAqBO,SAAS,qBAAqB,KAAA,EAAuB;AAC1D,EAAA,IAAI,MAAM,KAAA,CAAM,MAAA;AAChB,EAAA,OAAO,MAAM,CAAA,IAAK,KAAA,CAAM,OAAO,GAAA,GAAM,CAAC,MAAM,GAAA,EAAK;AAC/C,IAAA,GAAA,IAAO,CAAA;AAAA,EACT;AACA,EAAA,OAAO,QAAQ,KAAA,CAAM,MAAA,GAAS,QAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,GAAG,CAAA;AAC1D;AAWO,SAAS,kBAAkB,GAAA,EAAsB;AACtD,EAAA,IAAI,eAAe,KAAA,EAAO;AACxB,IAAA,IAAI,OAAA,GAAiB,GAAA;AAErB,IAAA,KAAA,IAAS,KAAA,GAAQ,CAAA,EAAG,KAAA,GAAQ,CAAA,EAAG,SAAS,CAAA,EAAG;AACzC,MAAA,MAAM,QAAS,OAAA,CAAwC,KAAA;AACvD,MAAA,IAAI,EAAE,KAAA,YAAiB,KAAA,CAAA,IAAU,CAAC,MAAM,OAAA,EAAS;AAC/C,QAAA;AAAA,MACF;AACA,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ;AACA,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,OAAO,OAAA,CAAQ,OAAA;AAAA,IACjB;AACA,IAAA,OAAO,QAAQ,IAAA,IAAQ,eAAA;AAAA,EACzB;AACA,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,SAAS,CAAA,EAAG;AAC7C,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAO,GAAG,CAAA;AACnB;;;;;"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@red-hat-developer-hub/backstage-plugin-catalog-backend-module-mcp-registry-provider",
3
+ "version": "0.2.0",
4
+ "license": "Apache-2.0",
5
+ "description": "The mcp-registry-provider backend module for the catalog plugin. Provides the MCP Server API catalog entities from a target MCP Registry.",
6
+ "main": "dist/index.cjs.js",
7
+ "types": "dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "main": "dist/index.cjs.js",
11
+ "types": "dist/index.d.ts"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/redhat-developer/rhdh-plugins",
16
+ "directory": "workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider"
17
+ },
18
+ "backstage": {
19
+ "role": "backend-plugin-module",
20
+ "pluginId": "catalog",
21
+ "pluginPackage": "@backstage/plugin-catalog-backend",
22
+ "features": {
23
+ ".": "@backstage/BackendFeature"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "start": "backstage-cli package start",
28
+ "start-local-mcp-registry": "node ../../scripts/deploy-local-mcp-registry.ts",
29
+ "stop-local-mcp-registry": "node ../../scripts/undeploy-local-mcp-registry.ts",
30
+ "build": "backstage-cli package build",
31
+ "lint": "backstage-cli package lint",
32
+ "lint:check": "backstage-cli package lint",
33
+ "lint:fix": "backstage-cli package lint --fix",
34
+ "test": "backstage-cli package test --passWithNoTests --coverage",
35
+ "clean": "backstage-cli package clean",
36
+ "prepack": "backstage-cli package prepack",
37
+ "postpack": "backstage-cli package postpack",
38
+ "tsc": "tsc",
39
+ "prettier:check": "prettier --ignore-unknown --check .",
40
+ "prettier:fix": "prettier --ignore-unknown --write ."
41
+ },
42
+ "dependencies": {
43
+ "@backstage/backend-plugin-api": "^1.10.0",
44
+ "@backstage/catalog-model": "^1.10.0",
45
+ "@backstage/plugin-catalog-node": "^2.2.4",
46
+ "@red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping": "^0.4.0"
47
+ },
48
+ "devDependencies": {
49
+ "@backstage/backend-defaults": "^0.17.8",
50
+ "@backstage/backend-test-utils": "^1.11.6",
51
+ "@backstage/cli": "^0.36.5",
52
+ "@backstage/config": "^1.3.8",
53
+ "@backstage/plugin-catalog-backend": "^3.9.0",
54
+ "@backstage/plugin-catalog-backend-module-ai-model": "^0.1.3",
55
+ "@types/supertest": "^2.0.12",
56
+ "supertest": "^6.2.4"
57
+ },
58
+ "files": [
59
+ "dist"
60
+ ],
61
+ "typesVersions": {
62
+ "*": {
63
+ "package.json": [
64
+ "package.json"
65
+ ]
66
+ }
67
+ }
68
+ }