@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.
- package/CHANGELOG.md +12 -0
- package/README.md +165 -0
- package/dist/McpRegistryEntityProvider.cjs.js +380 -0
- package/dist/McpRegistryEntityProvider.cjs.js.map +1 -0
- package/dist/client.cjs.js +295 -0
- package/dist/client.cjs.js.map +1 -0
- package/dist/config.cjs.js +231 -0
- package/dist/config.cjs.js.map +1 -0
- package/dist/index.cjs.js +12 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +202 -0
- package/dist/module.cjs.js +43 -0
- package/dist/module.cjs.js.map +1 -0
- package/dist/providerUtils.cjs.js +40 -0
- package/dist/providerUtils.cjs.js.map +1 -0
- package/dist/util.cjs.js +33 -0
- package/dist/util.cjs.js.map +1 -0
- package/package.json +68 -0
package/dist/index.d.ts
ADDED
|
@@ -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;;;;;;;"}
|
package/dist/util.cjs.js
ADDED
|
@@ -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
|
+
}
|