@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
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var util = require('./util.cjs.js');
|
|
4
|
+
|
|
5
|
+
const MAX_ERROR_BODY_LENGTH = 256;
|
|
6
|
+
const MAX_REDIRECTS = 10;
|
|
7
|
+
const REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
8
|
+
class McpRegistryClientError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "McpRegistryClientError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function buildServersEndpoint(baseUrl, apiVersion) {
|
|
15
|
+
return `${util.stripTrailingSlashes(baseUrl)}/${apiVersion}/servers`;
|
|
16
|
+
}
|
|
17
|
+
function parseServersEndpointUrl(baseUrl, apiVersion) {
|
|
18
|
+
const endpoint = buildServersEndpoint(baseUrl, apiVersion);
|
|
19
|
+
try {
|
|
20
|
+
return new URL(endpoint);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
throw new McpRegistryClientError(
|
|
23
|
+
`Invalid MCP Registry endpoint URL "${endpoint}": ${util.formatErrorDetail(
|
|
24
|
+
err
|
|
25
|
+
)}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function buildPageRequestUrl(endpoint, cursor, pageSize, latestVersion) {
|
|
30
|
+
const url = new URL(endpoint.toString());
|
|
31
|
+
if (cursor) {
|
|
32
|
+
url.searchParams.set("cursor", cursor);
|
|
33
|
+
}
|
|
34
|
+
if (pageSize !== void 0) {
|
|
35
|
+
url.searchParams.set("limit", String(pageSize));
|
|
36
|
+
}
|
|
37
|
+
if (latestVersion) {
|
|
38
|
+
url.searchParams.set("version", "latest");
|
|
39
|
+
}
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
function truncateErrorBody(rawBody, maxLength = MAX_ERROR_BODY_LENGTH) {
|
|
43
|
+
if (rawBody.length <= maxLength) {
|
|
44
|
+
return rawBody;
|
|
45
|
+
}
|
|
46
|
+
return `${rawBody.substring(0, maxLength)}\u2026(truncated)`;
|
|
47
|
+
}
|
|
48
|
+
function isRedirectStatus(status) {
|
|
49
|
+
return REDIRECT_STATUSES.has(status);
|
|
50
|
+
}
|
|
51
|
+
function resolveRedirectUrl(currentUrl, location) {
|
|
52
|
+
try {
|
|
53
|
+
return new URL(location, currentUrl);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
throw new McpRegistryClientError(
|
|
56
|
+
`MCP Registry returned an invalid redirect Location "${location}" from ${currentUrl}: ${util.formatErrorDetail(err)}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function validateRedirectTarget(targetUrl, hostAllowList) {
|
|
61
|
+
if (targetUrl.protocol !== "http:" && targetUrl.protocol !== "https:") {
|
|
62
|
+
throw new McpRegistryClientError(
|
|
63
|
+
`MCP Registry redirect to disallowed protocol "${targetUrl.protocol}" in "${targetUrl}". Only http and https are permitted.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
assertRequestHostAllowed(targetUrl, hostAllowList);
|
|
67
|
+
}
|
|
68
|
+
async function fetchRegistryPage(doFetch, url, hostAllowList) {
|
|
69
|
+
let currentUrl = url;
|
|
70
|
+
let redirectsFollowed = 0;
|
|
71
|
+
let response = await fetchOnce(doFetch, currentUrl, hostAllowList);
|
|
72
|
+
while (isRedirectStatus(response.status)) {
|
|
73
|
+
if (redirectsFollowed >= MAX_REDIRECTS) {
|
|
74
|
+
throw new McpRegistryClientError(
|
|
75
|
+
`MCP Registry exceeded ${MAX_REDIRECTS} redirects starting from ${url}. Last redirect was from ${currentUrl}.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const location = response.headers.get("Location");
|
|
79
|
+
if (!location) {
|
|
80
|
+
throw new McpRegistryClientError(
|
|
81
|
+
`MCP Registry returned HTTP ${response.status} without a Location header from ${currentUrl}.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const nextUrl = resolveRedirectUrl(currentUrl, location);
|
|
85
|
+
validateRedirectTarget(nextUrl, hostAllowList);
|
|
86
|
+
redirectsFollowed += 1;
|
|
87
|
+
currentUrl = nextUrl;
|
|
88
|
+
response = await fetchOnce(doFetch, currentUrl, hostAllowList);
|
|
89
|
+
}
|
|
90
|
+
const requestUrl = currentUrl.toString();
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const rawBody = await response.text().catch(() => "(no body)");
|
|
93
|
+
throw new McpRegistryClientError(
|
|
94
|
+
`MCP Registry returned HTTP ${response.status} for ${requestUrl}: ${truncateErrorBody(rawBody)}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
let body;
|
|
98
|
+
try {
|
|
99
|
+
body = await response.json();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
throw new McpRegistryClientError(
|
|
102
|
+
`MCP Registry returned unparseable JSON from ${requestUrl}: ${util.formatErrorDetail(
|
|
103
|
+
err
|
|
104
|
+
)}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (!body.servers || !Array.isArray(body.servers)) {
|
|
108
|
+
throw new McpRegistryClientError(
|
|
109
|
+
`MCP Registry response missing "servers" array from ${requestUrl}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return body;
|
|
113
|
+
}
|
|
114
|
+
async function fetchOnce(doFetch, url, hostAllowList) {
|
|
115
|
+
const requestUrl = url.toString();
|
|
116
|
+
assertRequestHostAllowed(url, hostAllowList);
|
|
117
|
+
let response;
|
|
118
|
+
try {
|
|
119
|
+
response = await doFetch(requestUrl, { redirect: "manual" });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
throw new McpRegistryClientError(
|
|
122
|
+
`Failed to reach MCP Registry at ${requestUrl}: ${util.formatErrorDetail(
|
|
123
|
+
err
|
|
124
|
+
)}`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
assertResponseUrlAllowed(response, hostAllowList, requestUrl);
|
|
128
|
+
return response;
|
|
129
|
+
}
|
|
130
|
+
function resolveNextCursor(nextCursor, seenCursors, pagesFetched, pageLimit) {
|
|
131
|
+
if (!nextCursor || nextCursor.length === 0) {
|
|
132
|
+
return { status: "complete" };
|
|
133
|
+
}
|
|
134
|
+
if (seenCursors.has(nextCursor)) {
|
|
135
|
+
throw new McpRegistryClientError(
|
|
136
|
+
`MCP Registry returned a repeated cursor "${nextCursor}" during pagination. Aborting sync to prevent infinite loop.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (pagesFetched >= pageLimit) {
|
|
140
|
+
return { status: "pageLimitReached", resumeCursor: nextCursor };
|
|
141
|
+
}
|
|
142
|
+
return { status: "continue", cursor: nextCursor };
|
|
143
|
+
}
|
|
144
|
+
function assertRequestHostAllowed(url, hostAllowList) {
|
|
145
|
+
if (!hostAllowList) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const hostname = url.hostname.toLowerCase();
|
|
149
|
+
if (!hostAllowList.includes(hostname)) {
|
|
150
|
+
throw new McpRegistryClientError(
|
|
151
|
+
`Request to hostname "${hostname}" blocked: not in the configured hostAllowList [${hostAllowList.join(", ")}].`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function assertResponseUrlAllowed(response, hostAllowList, requestUrl) {
|
|
156
|
+
if (!hostAllowList) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!response.url) {
|
|
160
|
+
throw new McpRegistryClientError(
|
|
161
|
+
`MCP Registry response for ${requestUrl} is missing response.url while hostAllowList is configured; refusing to proceed.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
assertRequestHostAllowed(new URL(response.url), hostAllowList);
|
|
165
|
+
}
|
|
166
|
+
function applyMaxEntriesSoftStop(params) {
|
|
167
|
+
const {
|
|
168
|
+
serversIncludingTippedPage,
|
|
169
|
+
tippedPageSize,
|
|
170
|
+
priorEntryCount,
|
|
171
|
+
pageCursor,
|
|
172
|
+
startCursor,
|
|
173
|
+
tippedNextCursor
|
|
174
|
+
} = params;
|
|
175
|
+
const withoutTip = serversIncludingTippedPage.slice(
|
|
176
|
+
0,
|
|
177
|
+
serversIncludingTippedPage.length - tippedPageSize
|
|
178
|
+
);
|
|
179
|
+
if (priorEntryCount + withoutTip.length === 0) {
|
|
180
|
+
return {
|
|
181
|
+
servers: serversIncludingTippedPage,
|
|
182
|
+
endCursor: typeof tippedNextCursor === "string" && tippedNextCursor.length > 0 ? tippedNextCursor : void 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
servers: withoutTip,
|
|
187
|
+
endCursor: pageCursor ?? startCursor
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function advanceAfterResolvedCursor(next, seenCursors, endCursor) {
|
|
191
|
+
if (next.status === "complete") {
|
|
192
|
+
return { action: "complete" };
|
|
193
|
+
}
|
|
194
|
+
if (next.status === "pageLimitReached") {
|
|
195
|
+
seenCursors.add(next.resumeCursor);
|
|
196
|
+
if (endCursor && next.resumeCursor === endCursor) {
|
|
197
|
+
return { action: "stopAtEnd" };
|
|
198
|
+
}
|
|
199
|
+
return { action: "resume", resumeCursor: next.resumeCursor };
|
|
200
|
+
}
|
|
201
|
+
seenCursors.add(next.cursor);
|
|
202
|
+
if (endCursor && next.cursor === endCursor) {
|
|
203
|
+
return { action: "stopAtEnd" };
|
|
204
|
+
}
|
|
205
|
+
return { action: "continue", cursor: next.cursor };
|
|
206
|
+
}
|
|
207
|
+
function isAtEndCursor(cursor, endCursor) {
|
|
208
|
+
return endCursor !== void 0 && cursor === endCursor;
|
|
209
|
+
}
|
|
210
|
+
async function fetchRegistryServers(options) {
|
|
211
|
+
const {
|
|
212
|
+
baseUrl,
|
|
213
|
+
apiVersion,
|
|
214
|
+
pageLimit,
|
|
215
|
+
pageSize,
|
|
216
|
+
latestVersion,
|
|
217
|
+
maxEntries,
|
|
218
|
+
priorEntryCount = 0,
|
|
219
|
+
startCursor,
|
|
220
|
+
endCursor,
|
|
221
|
+
hostAllowList,
|
|
222
|
+
fetchApi
|
|
223
|
+
} = options;
|
|
224
|
+
const doFetch = fetchApi ?? fetch;
|
|
225
|
+
const endpoint = parseServersEndpointUrl(baseUrl, apiVersion);
|
|
226
|
+
const seenCursors = new Set(options.seenCursors);
|
|
227
|
+
assertRequestHostAllowed(endpoint, hostAllowList);
|
|
228
|
+
let allServers = [];
|
|
229
|
+
let cursor = startCursor;
|
|
230
|
+
let pagesFetched = 0;
|
|
231
|
+
let resumeCursor;
|
|
232
|
+
let maxEntriesEndCursor;
|
|
233
|
+
while (!isAtEndCursor(cursor, endCursor)) {
|
|
234
|
+
const url = buildPageRequestUrl(endpoint, cursor, pageSize, latestVersion);
|
|
235
|
+
const body = await fetchRegistryPage(doFetch, url, hostAllowList);
|
|
236
|
+
allServers.push(...body.servers);
|
|
237
|
+
pagesFetched += 1;
|
|
238
|
+
const totalEntries = priorEntryCount + allServers.length;
|
|
239
|
+
if (maxEntries !== void 0 && totalEntries > maxEntries) {
|
|
240
|
+
const capped = applyMaxEntriesSoftStop({
|
|
241
|
+
serversIncludingTippedPage: allServers,
|
|
242
|
+
tippedPageSize: body.servers.length,
|
|
243
|
+
priorEntryCount,
|
|
244
|
+
pageCursor: cursor,
|
|
245
|
+
startCursor,
|
|
246
|
+
tippedNextCursor: body.metadata?.nextCursor
|
|
247
|
+
});
|
|
248
|
+
allServers = capped.servers;
|
|
249
|
+
maxEntriesEndCursor = capped.endCursor;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
const advance = advanceAfterResolvedCursor(
|
|
253
|
+
resolveNextCursor(
|
|
254
|
+
body.metadata?.nextCursor,
|
|
255
|
+
seenCursors,
|
|
256
|
+
pagesFetched,
|
|
257
|
+
pageLimit
|
|
258
|
+
),
|
|
259
|
+
seenCursors,
|
|
260
|
+
endCursor
|
|
261
|
+
);
|
|
262
|
+
if (advance.action === "complete" || advance.action === "stopAtEnd") {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (advance.action === "resume") {
|
|
266
|
+
resumeCursor = advance.resumeCursor;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
cursor = advance.cursor;
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
servers: allServers,
|
|
273
|
+
resumeCursor,
|
|
274
|
+
endCursor: maxEntriesEndCursor,
|
|
275
|
+
seenCursors
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exports.McpRegistryClientError = McpRegistryClientError;
|
|
280
|
+
exports.advanceAfterResolvedCursor = advanceAfterResolvedCursor;
|
|
281
|
+
exports.applyMaxEntriesSoftStop = applyMaxEntriesSoftStop;
|
|
282
|
+
exports.assertRequestHostAllowed = assertRequestHostAllowed;
|
|
283
|
+
exports.assertResponseUrlAllowed = assertResponseUrlAllowed;
|
|
284
|
+
exports.buildPageRequestUrl = buildPageRequestUrl;
|
|
285
|
+
exports.buildServersEndpoint = buildServersEndpoint;
|
|
286
|
+
exports.fetchRegistryPage = fetchRegistryPage;
|
|
287
|
+
exports.fetchRegistryServers = fetchRegistryServers;
|
|
288
|
+
exports.isAtEndCursor = isAtEndCursor;
|
|
289
|
+
exports.isRedirectStatus = isRedirectStatus;
|
|
290
|
+
exports.parseServersEndpointUrl = parseServersEndpointUrl;
|
|
291
|
+
exports.resolveNextCursor = resolveNextCursor;
|
|
292
|
+
exports.resolveRedirectUrl = resolveRedirectUrl;
|
|
293
|
+
exports.truncateErrorBody = truncateErrorBody;
|
|
294
|
+
exports.validateRedirectTarget = validateRedirectTarget;
|
|
295
|
+
//# sourceMappingURL=client.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.cjs.js","sources":["../src/client.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 type { McpServerDocument } from '@red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping';\nimport { formatErrorDetail, stripTrailingSlashes } from './util';\n\n/** Max characters of an error response body included in client errors. */\nconst MAX_ERROR_BODY_LENGTH = 256;\n\n/** Max redirect hops followed for a single page request. */\nconst MAX_REDIRECTS = 10;\n\n/** HTTP statuses treated as redirects to follow manually. */\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\n\n/**\n * A single server entry from the MCP Registry list response.\n */\nexport interface McpRegistryServerEntry {\n server: McpServerDocument;\n}\n\n/**\n * The MCP Registry servers list response shape.\n */\nexport interface McpRegistryListResponse {\n servers: McpRegistryServerEntry[];\n metadata: {\n count?: number;\n nextCursor?: string | null;\n };\n}\n\n/**\n * Error thrown when the registry client encounters a transport or\n * protocol error that should abort the sync run.\n */\nexport class McpRegistryClientError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'McpRegistryClientError';\n }\n}\n\n/**\n * Build the servers endpoint URL from baseUrl and apiVersion,\n * normalizing slashes so a trailing slash on baseUrl does not\n * produce a double separator.\n */\nexport function buildServersEndpoint(\n baseUrl: string,\n apiVersion: string,\n): string {\n return `${stripTrailingSlashes(baseUrl)}/${apiVersion}/servers`;\n}\n\n/**\n * Options for fetching servers from the MCP Registry.\n */\nexport interface FetchServersOptions {\n baseUrl: string;\n apiVersion: string;\n pageLimit: number;\n pageSize?: number;\n /**\n * When true, each list request includes `?version=latest`. When false\n * or omitted, the `version` query parameter is left unset.\n */\n latestVersion?: boolean;\n /**\n * Maximum total entries buffered across the current registry\n * traversal (including prior resume syncs) before a full mutation.\n * When exceeded, paging stops gracefully: the tipping page is left\n * out (unless it is the only page), and `endCursor` is returned so\n * the provider can commit what was buffered and bound later\n * traversals.\n *\n * Counts `priorEntryCount` plus servers fetched in this call.\n */\n maxEntries?: number;\n /**\n * Entry count already accumulated earlier in the current multi-sync\n * traversal. Used with `maxEntries` so the cap spans resume cycles.\n */\n priorEntryCount?: number;\n /**\n * Cursor to resume from after a prior sync hit `pageLimit`. When\n * omitted, the first request starts at the beginning of the list.\n */\n startCursor?: string;\n /**\n * When set (after a prior `maxEntries` soft-stop), a traversal that\n * starts from the beginning stops when it would advance to this\n * cursor, instead of waiting for a missing `nextCursor`.\n */\n endCursor?: string;\n /**\n * Cursors already seen in the current multi-sync traversal. Shared\n * across resume cycles so repeated-cursor detection spans syncs.\n * Treated as read-only input; the updated set is returned on\n * {@link FetchServersResult.seenCursors}.\n */\n seenCursors?: ReadonlySet<string>;\n /**\n * Optional allowlist of permitted hostnames. When set, every\n * outbound request URL is validated against this list before\n * fetching, providing defense-in-depth against SSRF.\n */\n hostAllowList?: string[];\n /** Optional fetch implementation for testing. */\n fetchApi?: typeof fetch;\n}\n\n/**\n * Result of one `fetchRegistryServers` call (up to `pageLimit` pages).\n *\n * @internal\n */\nexport interface FetchServersResult {\n /** Servers fetched during this call. */\n servers: McpRegistryServerEntry[];\n /**\n * When set, more pages remain after this call stopped at `pageLimit`.\n * The next sync should pass this as `startCursor`.\n */\n resumeCursor?: string;\n /**\n * When set, this call stopped because `maxEntries` was exceeded.\n * The provider should commit a full mutation of the buffer and\n * remember this cursor as the end bound for later full traversals.\n */\n endCursor?: string;\n /**\n * Cursor set after this call, including any newly observed cursors.\n * Callers should replace their prior set with this value on success.\n */\n seenCursors: Set<string>;\n}\n\n/**\n * Outcome of resolving the registry's `nextCursor` for pagination.\n *\n * @internal\n */\nexport type ResolveNextCursorResult =\n | { status: 'complete' }\n | { status: 'continue'; cursor: string }\n | { status: 'pageLimitReached'; resumeCursor: string };\n\n/**\n * Parse the servers list endpoint into a URL.\n *\n * @internal\n */\nexport function parseServersEndpointUrl(\n baseUrl: string,\n apiVersion: string,\n): URL {\n const endpoint = buildServersEndpoint(baseUrl, apiVersion);\n try {\n return new URL(endpoint);\n } catch (err) {\n throw new McpRegistryClientError(\n `Invalid MCP Registry endpoint URL \"${endpoint}\": ${formatErrorDetail(\n err,\n )}`,\n );\n }\n}\n\n/**\n * Build a page request URL with optional cursor, page-size, and\n * latest-version query params.\n *\n * @internal\n */\nexport function buildPageRequestUrl(\n endpoint: URL,\n cursor?: string,\n pageSize?: number,\n latestVersion?: boolean,\n): URL {\n const url = new URL(endpoint.toString());\n if (cursor) {\n url.searchParams.set('cursor', cursor);\n }\n if (pageSize !== undefined) {\n url.searchParams.set('limit', String(pageSize));\n }\n if (latestVersion) {\n url.searchParams.set('version', 'latest');\n }\n return url;\n}\n\n/**\n * Truncate an error response body for safe inclusion in log/error text.\n *\n * @internal\n */\nexport function truncateErrorBody(\n rawBody: string,\n maxLength = MAX_ERROR_BODY_LENGTH,\n): string {\n if (rawBody.length <= maxLength) {\n return rawBody;\n }\n return `${rawBody.substring(0, maxLength)}…(truncated)`;\n}\n\n/**\n * Whether an HTTP status code is a redirect this client follows.\n *\n * @internal\n */\nexport function isRedirectStatus(status: number): boolean {\n return REDIRECT_STATUSES.has(status);\n}\n\n/**\n * Resolve a redirect `Location` header against the current request URL.\n *\n * @internal\n */\nexport function resolveRedirectUrl(currentUrl: URL, location: string): URL {\n try {\n return new URL(location, currentUrl);\n } catch (err) {\n throw new McpRegistryClientError(\n `MCP Registry returned an invalid redirect Location \"${location}\" ` +\n `from ${currentUrl}: ${formatErrorDetail(err)}`,\n );\n }\n}\n\n/**\n * Validate a redirect target before following it.\n *\n * Requires http(s) and, when configured, an allowlisted hostname.\n *\n * @internal\n */\nexport function validateRedirectTarget(\n targetUrl: URL,\n hostAllowList?: string[],\n): void {\n if (targetUrl.protocol !== 'http:' && targetUrl.protocol !== 'https:') {\n throw new McpRegistryClientError(\n `MCP Registry redirect to disallowed protocol \"${targetUrl.protocol}\" ` +\n `in \"${targetUrl}\". Only http and https are permitted.`,\n );\n }\n assertRequestHostAllowed(targetUrl, hostAllowList);\n}\n\n/**\n * Fetch and validate one registry list page.\n *\n * Uses `redirect: 'manual'` and validates each `Location` header\n * against the host allowlist before following, so SSRF via redirect\n * cannot reach a disallowed host.\n *\n * @internal\n */\nexport async function fetchRegistryPage(\n doFetch: typeof fetch,\n url: URL,\n hostAllowList?: string[],\n): Promise<McpRegistryListResponse> {\n let currentUrl = url;\n let redirectsFollowed = 0;\n let response = await fetchOnce(doFetch, currentUrl, hostAllowList);\n\n while (isRedirectStatus(response.status)) {\n if (redirectsFollowed >= MAX_REDIRECTS) {\n throw new McpRegistryClientError(\n `MCP Registry exceeded ${MAX_REDIRECTS} redirects starting from ` +\n `${url}. Last redirect was from ${currentUrl}.`,\n );\n }\n\n const location = response.headers.get('Location');\n if (!location) {\n throw new McpRegistryClientError(\n `MCP Registry returned HTTP ${response.status} without a ` +\n `Location header from ${currentUrl}.`,\n );\n }\n\n const nextUrl = resolveRedirectUrl(currentUrl, location);\n validateRedirectTarget(nextUrl, hostAllowList);\n redirectsFollowed += 1;\n currentUrl = nextUrl;\n response = await fetchOnce(doFetch, currentUrl, hostAllowList);\n }\n\n const requestUrl = currentUrl.toString();\n\n if (!response.ok) {\n const rawBody = await response.text().catch(() => '(no body)');\n throw new McpRegistryClientError(\n `MCP Registry returned HTTP ${response.status} for ` +\n `${requestUrl}: ${truncateErrorBody(rawBody)}`,\n );\n }\n\n let body: McpRegistryListResponse;\n try {\n body = (await response.json()) as McpRegistryListResponse;\n } catch (err) {\n throw new McpRegistryClientError(\n `MCP Registry returned unparseable JSON from ${requestUrl}: ${formatErrorDetail(\n err,\n )}`,\n );\n }\n\n if (!body.servers || !Array.isArray(body.servers)) {\n throw new McpRegistryClientError(\n `MCP Registry response missing \"servers\" array from ${requestUrl}`,\n );\n }\n\n return body;\n}\n\n/**\n * Perform one allowlist-checked fetch with `redirect: 'manual'`.\n *\n * @internal\n */\nasync function fetchOnce(\n doFetch: typeof fetch,\n url: URL,\n hostAllowList?: string[],\n): Promise<Response> {\n const requestUrl = url.toString();\n assertRequestHostAllowed(url, hostAllowList);\n let response: Response;\n try {\n response = await doFetch(requestUrl, { redirect: 'manual' });\n } catch (err) {\n throw new McpRegistryClientError(\n `Failed to reach MCP Registry at ${requestUrl}: ${formatErrorDetail(\n err,\n )}`,\n );\n }\n assertResponseUrlAllowed(response, hostAllowList, requestUrl);\n return response;\n}\n\n/**\n * Resolve the next pagination cursor for this sync.\n *\n * Returns `complete` when paging is done, `continue` when another page\n * should be fetched in this sync, or `pageLimitReached` when this sync\n * should stop and resume from `resumeCursor` on a later sync.\n * Enforces repeated-cursor detection (still a hard error). Does not\n * mutate `seenCursors`; the caller records new cursors.\n *\n * @internal\n */\nexport function resolveNextCursor(\n nextCursor: string | null | undefined,\n seenCursors: ReadonlySet<string>,\n pagesFetched: number,\n pageLimit: number,\n): ResolveNextCursorResult {\n if (!nextCursor || nextCursor.length === 0) {\n return { status: 'complete' };\n }\n\n if (seenCursors.has(nextCursor)) {\n throw new McpRegistryClientError(\n `MCP Registry returned a repeated cursor \"${nextCursor}\" ` +\n `during pagination. Aborting sync to prevent infinite loop.`,\n );\n }\n\n if (pagesFetched >= pageLimit) {\n return { status: 'pageLimitReached', resumeCursor: nextCursor };\n }\n\n return { status: 'continue', cursor: nextCursor };\n}\n\n/**\n * Runtime request guard: assert a URL's hostname is on the configured\n * allow list before issuing (or following) an outbound fetch.\n * Throws McpRegistryClientError when the hostname is not permitted.\n *\n * @internal\n */\nexport function assertRequestHostAllowed(\n url: URL,\n hostAllowList: string[] | undefined,\n): void {\n if (!hostAllowList) {\n return;\n }\n const hostname = url.hostname.toLowerCase();\n if (!hostAllowList.includes(hostname)) {\n throw new McpRegistryClientError(\n `Request to hostname \"${hostname}\" blocked: not in the configured ` +\n `hostAllowList [${hostAllowList.join(', ')}].`,\n );\n }\n}\n\n/**\n * Fail closed when an allowlist is configured but the fetch response\n * does not expose a URL, then validate that URL's hostname.\n *\n * @internal\n */\nexport function assertResponseUrlAllowed(\n response: Response,\n hostAllowList: string[] | undefined,\n requestUrl: string,\n): void {\n if (!hostAllowList) {\n return;\n }\n if (!response.url) {\n throw new McpRegistryClientError(\n `MCP Registry response for ${requestUrl} is missing response.url ` +\n `while hostAllowList is configured; refusing to proceed.`,\n );\n }\n assertRequestHostAllowed(new URL(response.url), hostAllowList);\n}\n\n/**\n * Soft-stop when a tipped page would push the traversal past `maxEntries`.\n *\n * Drops the tipping page unless it is the only content so far (then keeps\n * it and bounds later traversals at its next cursor).\n *\n * @internal\n */\nexport function applyMaxEntriesSoftStop(params: {\n serversIncludingTippedPage: McpRegistryServerEntry[];\n tippedPageSize: number;\n priorEntryCount: number;\n pageCursor: string | undefined;\n startCursor: string | undefined;\n tippedNextCursor: string | null | undefined;\n}): { servers: McpRegistryServerEntry[]; endCursor: string | undefined } {\n const {\n serversIncludingTippedPage,\n tippedPageSize,\n priorEntryCount,\n pageCursor,\n startCursor,\n tippedNextCursor,\n } = params;\n\n const withoutTip = serversIncludingTippedPage.slice(\n 0,\n serversIncludingTippedPage.length - tippedPageSize,\n );\n\n if (priorEntryCount + withoutTip.length === 0) {\n // Single page alone exceeds the cap — keep it so a mutation can\n // still proceed, and bound later traversals at its next cursor.\n return {\n servers: serversIncludingTippedPage,\n endCursor:\n typeof tippedNextCursor === 'string' && tippedNextCursor.length > 0\n ? tippedNextCursor\n : undefined,\n };\n }\n\n // Exclude the tipping page; end at the cursor used to fetch it.\n return {\n servers: withoutTip,\n endCursor: pageCursor ?? startCursor,\n };\n}\n\n/**\n * Record a resolved next-cursor decision into `seenCursors` and map it\n * to a pagination control action for the fetch loop.\n *\n * @internal\n */\nexport function advanceAfterResolvedCursor(\n next: ResolveNextCursorResult,\n seenCursors: Set<string>,\n endCursor: string | undefined,\n):\n | { action: 'complete' }\n | { action: 'stopAtEnd' }\n | { action: 'resume'; resumeCursor: string }\n | { action: 'continue'; cursor: string } {\n if (next.status === 'complete') {\n return { action: 'complete' };\n }\n\n if (next.status === 'pageLimitReached') {\n seenCursors.add(next.resumeCursor);\n if (endCursor && next.resumeCursor === endCursor) {\n return { action: 'stopAtEnd' };\n }\n return { action: 'resume', resumeCursor: next.resumeCursor };\n }\n\n seenCursors.add(next.cursor);\n if (endCursor && next.cursor === endCursor) {\n return { action: 'stopAtEnd' };\n }\n return { action: 'continue', cursor: next.cursor };\n}\n\n/**\n * Whether paging should stop because the current cursor matches a\n * previously saved maxEntries end bound.\n *\n * @internal\n */\nexport function isAtEndCursor(\n cursor: string | undefined,\n endCursor: string | undefined,\n): boolean {\n return endCursor !== undefined && cursor === endCursor;\n}\n\n/**\n * Fetch server entries from the MCP Registry using cursor pagination.\n *\n * Fetches at most `pageLimit` pages starting from `startCursor` (or the\n * beginning when unset). When more pages remain after the cap, returns\n * those pages' servers plus a `resumeCursor` for the next sync instead\n * of failing. When `maxEntries` would be exceeded, stops gracefully and\n * returns `endCursor` so the provider can commit the buffer. When\n * `endCursor` is supplied, paging stops upon reaching that cursor\n * instead of requiring a missing `nextCursor`.\n *\n * @throws McpRegistryClientError on transport, protocol, or\n * repeated-cursor errors.\n */\nexport async function fetchRegistryServers(\n options: FetchServersOptions,\n): Promise<FetchServersResult> {\n const {\n baseUrl,\n apiVersion,\n pageLimit,\n pageSize,\n latestVersion,\n maxEntries,\n priorEntryCount = 0,\n startCursor,\n endCursor,\n hostAllowList,\n fetchApi,\n } = options;\n const doFetch = fetchApi ?? fetch;\n const endpoint = parseServersEndpointUrl(baseUrl, apiVersion);\n // Own a local copy so the caller's options set is never mutated.\n const seenCursors = new Set(options.seenCursors);\n\n // Defense-in-depth: validate endpoint hostname at runtime even\n // though config parsing already checked baseUrl against the list.\n assertRequestHostAllowed(endpoint, hostAllowList);\n\n let allServers: McpRegistryServerEntry[] = [];\n let cursor: string | undefined = startCursor;\n let pagesFetched = 0;\n let resumeCursor: string | undefined;\n let maxEntriesEndCursor: string | undefined;\n\n while (!isAtEndCursor(cursor, endCursor)) {\n const url = buildPageRequestUrl(endpoint, cursor, pageSize, latestVersion);\n const body = await fetchRegistryPage(doFetch, url, hostAllowList);\n allServers.push(...body.servers);\n pagesFetched += 1;\n\n const totalEntries = priorEntryCount + allServers.length;\n if (maxEntries !== undefined && totalEntries > maxEntries) {\n const capped = applyMaxEntriesSoftStop({\n serversIncludingTippedPage: allServers,\n tippedPageSize: body.servers.length,\n priorEntryCount,\n pageCursor: cursor,\n startCursor,\n tippedNextCursor: body.metadata?.nextCursor,\n });\n allServers = capped.servers;\n maxEntriesEndCursor = capped.endCursor;\n break;\n }\n\n const advance = advanceAfterResolvedCursor(\n resolveNextCursor(\n body.metadata?.nextCursor,\n seenCursors,\n pagesFetched,\n pageLimit,\n ),\n seenCursors,\n endCursor,\n );\n\n if (advance.action === 'complete' || advance.action === 'stopAtEnd') {\n break;\n }\n if (advance.action === 'resume') {\n resumeCursor = advance.resumeCursor;\n break;\n }\n cursor = advance.cursor;\n }\n\n return {\n servers: allServers,\n resumeCursor,\n endCursor: maxEntriesEndCursor,\n seenCursors,\n };\n}\n"],"names":["stripTrailingSlashes","formatErrorDetail"],"mappings":"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"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_SCHEDULE = {
|
|
6
|
+
frequency: { minutes: 30 },
|
|
7
|
+
timeout: { minutes: 3 }
|
|
8
|
+
};
|
|
9
|
+
const DEFAULT_API_VERSION = "v1";
|
|
10
|
+
const DEFAULT_PAGE_LIMIT = 10;
|
|
11
|
+
const DEFAULT_MAX_ENTRIES = 5e3;
|
|
12
|
+
const DEFAULT_REMOTES_ONLY = false;
|
|
13
|
+
const DEFAULT_LATEST_VERSION = false;
|
|
14
|
+
const MCP_REGISTRY_INSTANCE_ID = "mcpRegistry";
|
|
15
|
+
const MCP_REGISTRY_INSTANCE_CONFIG_PATH = `catalog.providers.mcpRegistry.${MCP_REGISTRY_INSTANCE_ID}`;
|
|
16
|
+
const KNOWN_MCP_REGISTRY_KEYS = /* @__PURE__ */ new Set([
|
|
17
|
+
"baseUrl",
|
|
18
|
+
"baseName",
|
|
19
|
+
"apiVersion",
|
|
20
|
+
"defaultOwner",
|
|
21
|
+
"defaultLifecycle",
|
|
22
|
+
"pageLimit",
|
|
23
|
+
"pageSize",
|
|
24
|
+
"maxEntries",
|
|
25
|
+
"remotesOnly",
|
|
26
|
+
"latestVersion",
|
|
27
|
+
"hostAllowList",
|
|
28
|
+
"schedule"
|
|
29
|
+
]);
|
|
30
|
+
function safeGetOptionalString(config, key) {
|
|
31
|
+
try {
|
|
32
|
+
return config.getOptionalString(key);
|
|
33
|
+
} catch {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function assertSingleRegistryConfig(registryConfig) {
|
|
38
|
+
const unknownKeys = registryConfig.keys().filter((key) => !KNOWN_MCP_REGISTRY_KEYS.has(key));
|
|
39
|
+
for (const key of unknownKeys) {
|
|
40
|
+
let nested;
|
|
41
|
+
try {
|
|
42
|
+
nested = registryConfig.getOptionalConfig(key);
|
|
43
|
+
} catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (nested && nested.keys().length > 0) {
|
|
47
|
+
const knownKeys = [...KNOWN_MCP_REGISTRY_KEYS].join(", ");
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: found keyed instance "${key}". Configure a single registry object with known keys: ${knownKeys}.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function readReservedRegistryInstanceConfig(providersMap, warn) {
|
|
55
|
+
const keys = providersMap.keys();
|
|
56
|
+
if (keys.length === 0) {
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
const legacyKeys = keys.filter((key) => KNOWN_MCP_REGISTRY_KEYS.has(key));
|
|
60
|
+
if (legacyKeys.length > 0) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Invalid catalog.providers.mcpRegistry configuration: registry options (${legacyKeys.join(", ")}) must be nested under the reserved instance key "${MCP_REGISTRY_INSTANCE_ID}" (e.g. catalog.providers.mcpRegistry.${MCP_REGISTRY_INSTANCE_ID}.baseUrl).`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const unexpectedKeys = keys.filter((key) => key !== MCP_REGISTRY_INSTANCE_ID);
|
|
66
|
+
if (unexpectedKeys.length > 0) {
|
|
67
|
+
warn?.(
|
|
68
|
+
`catalog.providers.mcpRegistry has additional instance id(s) [${unexpectedKeys.join(
|
|
69
|
+
", "
|
|
70
|
+
)}] which are ignored; multiple MCP Registry providers are not supported yet. Only the reserved "${MCP_REGISTRY_INSTANCE_ID}" instance is used.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const registryConfig = providersMap.getOptionalConfig(
|
|
74
|
+
MCP_REGISTRY_INSTANCE_ID
|
|
75
|
+
);
|
|
76
|
+
if (!registryConfig) {
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
return registryConfig;
|
|
80
|
+
}
|
|
81
|
+
function readRequiredHttpBaseUrl(registryConfig) {
|
|
82
|
+
const baseUrl = safeGetOptionalString(registryConfig, "baseUrl");
|
|
83
|
+
if (!baseUrl) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: missing required "baseUrl" field. Set baseUrl to the MCP Registry base URL (e.g., "https://registry.example.com").`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
let parsedUrl;
|
|
89
|
+
try {
|
|
90
|
+
parsedUrl = new URL(baseUrl);
|
|
91
|
+
} catch {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: "baseUrl" is not a valid URL: "${baseUrl}". Set baseUrl to an absolute HTTP(S) URL (e.g., "https://registry.example.com").`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: "baseUrl" must use http or https protocol, got "${parsedUrl.protocol}" in "${baseUrl}".`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return baseUrl;
|
|
102
|
+
}
|
|
103
|
+
function readPageLimit(registryConfig) {
|
|
104
|
+
const pageLimit = registryConfig.getOptionalNumber("pageLimit") ?? DEFAULT_PAGE_LIMIT;
|
|
105
|
+
if (pageLimit < 1) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: "pageLimit" must be at least 1, got ${pageLimit}.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return pageLimit;
|
|
111
|
+
}
|
|
112
|
+
function readMaxEntries(registryConfig) {
|
|
113
|
+
const maxEntries = registryConfig.getOptionalNumber("maxEntries") ?? DEFAULT_MAX_ENTRIES;
|
|
114
|
+
if (maxEntries < 1) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: "maxEntries" must be at least 1, got ${maxEntries}.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return maxEntries;
|
|
120
|
+
}
|
|
121
|
+
function readOptionalPageSize(registryConfig) {
|
|
122
|
+
const pageSize = registryConfig.getOptionalNumber("pageSize");
|
|
123
|
+
if (pageSize !== void 0 && pageSize < 1) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: "pageSize" must be at least 1, got ${pageSize}.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return pageSize;
|
|
129
|
+
}
|
|
130
|
+
function readHostAllowList(registryConfig) {
|
|
131
|
+
const list = registryConfig.getOptionalStringArray("hostAllowList");
|
|
132
|
+
if (!list) {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
return list.map((h) => h.toLowerCase());
|
|
136
|
+
}
|
|
137
|
+
function validateHostAllowList(url, hostAllowList) {
|
|
138
|
+
const parsed = new URL(url);
|
|
139
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
140
|
+
if (!hostAllowList.includes(hostname)) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: the hostname "${hostname}" from baseUrl "${url}" is not in the configured hostAllowList [${hostAllowList.join(", ")}].`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function readRemotesOnly(registryConfig) {
|
|
147
|
+
try {
|
|
148
|
+
return registryConfig.getOptionalBoolean("remotesOnly") ?? DEFAULT_REMOTES_ONLY;
|
|
149
|
+
} catch {
|
|
150
|
+
return DEFAULT_REMOTES_ONLY;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function readLatestVersion(registryConfig) {
|
|
154
|
+
try {
|
|
155
|
+
return registryConfig.getOptionalBoolean("latestVersion") ?? DEFAULT_LATEST_VERSION;
|
|
156
|
+
} catch {
|
|
157
|
+
return DEFAULT_LATEST_VERSION;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function readProviderSchedule(registryConfig) {
|
|
161
|
+
const scheduleConfig = registryConfig.getOptionalConfig("schedule");
|
|
162
|
+
if (!scheduleConfig) {
|
|
163
|
+
return DEFAULT_SCHEDULE;
|
|
164
|
+
}
|
|
165
|
+
return backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(scheduleConfig);
|
|
166
|
+
}
|
|
167
|
+
function resolveMcpRegistryProviderConfig(config) {
|
|
168
|
+
return {
|
|
169
|
+
...config,
|
|
170
|
+
apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,
|
|
171
|
+
pageLimit: config.pageLimit ?? DEFAULT_PAGE_LIMIT,
|
|
172
|
+
maxEntries: config.maxEntries ?? DEFAULT_MAX_ENTRIES,
|
|
173
|
+
remotesOnly: config.remotesOnly ?? DEFAULT_REMOTES_ONLY,
|
|
174
|
+
latestVersion: config.latestVersion ?? DEFAULT_LATEST_VERSION
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function readMcpRegistryProviderConfig(rootConfig, warn) {
|
|
178
|
+
const providersConfig = rootConfig.getOptionalConfig("catalog.providers");
|
|
179
|
+
if (!providersConfig) {
|
|
180
|
+
return void 0;
|
|
181
|
+
}
|
|
182
|
+
const providersMap = providersConfig.getOptionalConfig("mcpRegistry");
|
|
183
|
+
if (!providersMap) {
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
const registryConfig = readReservedRegistryInstanceConfig(providersMap, warn);
|
|
187
|
+
if (!registryConfig) {
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
assertSingleRegistryConfig(registryConfig);
|
|
191
|
+
const baseUrl = readRequiredHttpBaseUrl(registryConfig);
|
|
192
|
+
const hostAllowList = readHostAllowList(registryConfig);
|
|
193
|
+
if (hostAllowList) {
|
|
194
|
+
validateHostAllowList(baseUrl, hostAllowList);
|
|
195
|
+
} else {
|
|
196
|
+
warn?.(
|
|
197
|
+
`${MCP_REGISTRY_INSTANCE_CONFIG_PATH}.hostAllowList is not configured; outbound registry requests are not restricted by hostname. Set hostAllowList to permitted registry hostnames for defense-in-depth against SSRF.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
baseUrl,
|
|
202
|
+
baseName: safeGetOptionalString(registryConfig, "baseName"),
|
|
203
|
+
apiVersion: safeGetOptionalString(registryConfig, "apiVersion") ?? DEFAULT_API_VERSION,
|
|
204
|
+
defaultOwner: safeGetOptionalString(registryConfig, "defaultOwner"),
|
|
205
|
+
defaultLifecycle: safeGetOptionalString(registryConfig, "defaultLifecycle"),
|
|
206
|
+
pageLimit: readPageLimit(registryConfig),
|
|
207
|
+
pageSize: readOptionalPageSize(registryConfig),
|
|
208
|
+
maxEntries: readMaxEntries(registryConfig),
|
|
209
|
+
remotesOnly: readRemotesOnly(registryConfig),
|
|
210
|
+
latestVersion: readLatestVersion(registryConfig),
|
|
211
|
+
hostAllowList,
|
|
212
|
+
schedule: readProviderSchedule(registryConfig)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
exports.MCP_REGISTRY_INSTANCE_ID = MCP_REGISTRY_INSTANCE_ID;
|
|
217
|
+
exports.assertSingleRegistryConfig = assertSingleRegistryConfig;
|
|
218
|
+
exports.readHostAllowList = readHostAllowList;
|
|
219
|
+
exports.readLatestVersion = readLatestVersion;
|
|
220
|
+
exports.readMaxEntries = readMaxEntries;
|
|
221
|
+
exports.readMcpRegistryProviderConfig = readMcpRegistryProviderConfig;
|
|
222
|
+
exports.readOptionalPageSize = readOptionalPageSize;
|
|
223
|
+
exports.readPageLimit = readPageLimit;
|
|
224
|
+
exports.readProviderSchedule = readProviderSchedule;
|
|
225
|
+
exports.readRemotesOnly = readRemotesOnly;
|
|
226
|
+
exports.readRequiredHttpBaseUrl = readRequiredHttpBaseUrl;
|
|
227
|
+
exports.readReservedRegistryInstanceConfig = readReservedRegistryInstanceConfig;
|
|
228
|
+
exports.resolveMcpRegistryProviderConfig = resolveMcpRegistryProviderConfig;
|
|
229
|
+
exports.safeGetOptionalString = safeGetOptionalString;
|
|
230
|
+
exports.validateHostAllowList = validateHostAllowList;
|
|
231
|
+
//# sourceMappingURL=config.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../src/config.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 type { Config } from '@backstage/config';\nimport type { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';\nimport { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api';\n\n/** Default schedule when `schedule` is omitted. */\nconst DEFAULT_SCHEDULE: SchedulerServiceTaskScheduleDefinition = {\n frequency: { minutes: 30 },\n timeout: { minutes: 3 },\n};\n\n/** Default apiVersion when omitted. */\nconst DEFAULT_API_VERSION = 'v1';\n\n/** Default page limit (max pages per sync). */\nconst DEFAULT_PAGE_LIMIT = 10;\n\n/** Default max entries per complete registry traversal (full mutation). */\nconst DEFAULT_MAX_ENTRIES = 5000;\n\n/** Default remotesOnly when omitted. */\nconst DEFAULT_REMOTES_ONLY = false;\n\n/** Default latestVersion when omitted. */\nconst DEFAULT_LATEST_VERSION = false;\n\n/**\n * Reserved instance id under `catalog.providers.mcpRegistry`.\n * This implementation expects only this key; additional ids are rejected\n * until multi-registry support lands.\n */\nexport const MCP_REGISTRY_INSTANCE_ID = 'mcpRegistry';\n\n/** Config path for the reserved registry instance. */\nconst MCP_REGISTRY_INSTANCE_CONFIG_PATH = `catalog.providers.mcpRegistry.${MCP_REGISTRY_INSTANCE_ID}`;\n\n/** Supported single-registry config keys under the reserved instance. */\nconst KNOWN_MCP_REGISTRY_KEYS = new Set([\n 'baseUrl',\n 'baseName',\n 'apiVersion',\n 'defaultOwner',\n 'defaultLifecycle',\n 'pageLimit',\n 'pageSize',\n 'maxEntries',\n 'remotesOnly',\n 'latestVersion',\n 'hostAllowList',\n 'schedule',\n]);\n\n/**\n * Safely read an optional string from config, returning `undefined`\n * when Backstage's ConfigReader throws TypeError for empty-string\n * values from env var substitution like `${VAR:-}`.\n *\n * @internal\n */\nexport function safeGetOptionalString(\n config: Config,\n key: string,\n): string | undefined {\n try {\n return config.getOptionalString(key);\n } catch {\n // ConfigReader throws TypeError for empty-string values\n // from env var substitution like ${VAR:-}\n return undefined;\n }\n}\n\n/**\n * Reject unexpected nested objects under a registry instance config.\n *\n * @internal\n */\nexport function assertSingleRegistryConfig(registryConfig: Config): void {\n const unknownKeys = registryConfig\n .keys()\n .filter(key => !KNOWN_MCP_REGISTRY_KEYS.has(key));\n\n for (const key of unknownKeys) {\n let nested;\n try {\n nested = registryConfig.getOptionalConfig(key);\n } catch {\n // ConfigReader throws TypeError when the value is a scalar\n // rather than an object — skip this key silently.\n continue;\n }\n if (nested && nested.keys().length > 0) {\n const knownKeys = [...KNOWN_MCP_REGISTRY_KEYS].join(', ');\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: found ` +\n `keyed instance \"${key}\". Configure a single registry object ` +\n `with known keys: ${knownKeys}.`,\n );\n }\n }\n}\n\n/**\n * Optional warning sink used when configuration is accepted with caveats\n * (e.g. ignored extra registry instance ids, or absent `hostAllowList`).\n *\n * @internal\n */\nexport type ConfigWarnFn = (message: string) => void;\n\n/**\n * Resolve the reserved registry instance from the providers map.\n *\n * `catalog.providers.mcpRegistry` is a map of instance ids. This\n * implementation only reads the reserved key {@link MCP_REGISTRY_INSTANCE_ID};\n * additional ids are ignored (with an optional warning).\n *\n * @internal\n */\nexport function readReservedRegistryInstanceConfig(\n providersMap: Config,\n warn?: ConfigWarnFn,\n): Config | undefined {\n const keys = providersMap.keys();\n if (keys.length === 0) {\n return undefined;\n }\n\n const legacyKeys = keys.filter(key => KNOWN_MCP_REGISTRY_KEYS.has(key));\n if (legacyKeys.length > 0) {\n throw new Error(\n `Invalid catalog.providers.mcpRegistry configuration: registry ` +\n `options (${legacyKeys.join(', ')}) must be nested under the ` +\n `reserved instance key \"${MCP_REGISTRY_INSTANCE_ID}\" ` +\n `(e.g. catalog.providers.mcpRegistry.${MCP_REGISTRY_INSTANCE_ID}.baseUrl).`,\n );\n }\n\n const unexpectedKeys = keys.filter(key => key !== MCP_REGISTRY_INSTANCE_ID);\n if (unexpectedKeys.length > 0) {\n warn?.(\n `catalog.providers.mcpRegistry has additional instance id(s) ` +\n `[${unexpectedKeys.join(\n ', ',\n )}] which are ignored; multiple MCP Registry providers are not ` +\n `supported yet. Only the reserved \"${MCP_REGISTRY_INSTANCE_ID}\" ` +\n `instance is used.`,\n );\n }\n\n const registryConfig = providersMap.getOptionalConfig(\n MCP_REGISTRY_INSTANCE_ID,\n );\n if (!registryConfig) {\n return undefined;\n }\n\n return registryConfig;\n}\n\n/**\n * Read and validate the required HTTP(S) `baseUrl`.\n *\n * @internal\n */\nexport function readRequiredHttpBaseUrl(registryConfig: Config): string {\n const baseUrl = safeGetOptionalString(registryConfig, 'baseUrl');\n if (!baseUrl) {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: missing ` +\n `required \"baseUrl\" field. Set baseUrl to the MCP Registry base URL ` +\n `(e.g., \"https://registry.example.com\").`,\n );\n }\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(baseUrl);\n } catch {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: \"baseUrl\" ` +\n `is not a valid URL: \"${baseUrl}\". Set baseUrl to an absolute ` +\n `HTTP(S) URL (e.g., \"https://registry.example.com\").`,\n );\n }\n\n if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: \"baseUrl\" ` +\n `must use http or https protocol, got \"${parsedUrl.protocol}\" ` +\n `in \"${baseUrl}\".`,\n );\n }\n\n return baseUrl;\n}\n\n/**\n * Read `pageLimit`, applying the default and rejecting values below 1.\n *\n * @internal\n */\nexport function readPageLimit(registryConfig: Config): number {\n const pageLimit =\n registryConfig.getOptionalNumber('pageLimit') ?? DEFAULT_PAGE_LIMIT;\n if (pageLimit < 1) {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: \"pageLimit\" ` +\n `must be at least 1, got ${pageLimit}.`,\n );\n }\n return pageLimit;\n}\n\n/**\n * Read `maxEntries`, applying the default and rejecting values below 1.\n * Caps total servers buffered for one complete registry traversal\n * (possibly spanning multiple resume syncs) before a full mutation.\n *\n * @internal\n */\nexport function readMaxEntries(registryConfig: Config): number {\n const maxEntries =\n registryConfig.getOptionalNumber('maxEntries') ?? DEFAULT_MAX_ENTRIES;\n if (maxEntries < 1) {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: \"maxEntries\" ` +\n `must be at least 1, got ${maxEntries}.`,\n );\n }\n return maxEntries;\n}\n\n/**\n * Read optional `pageSize`, rejecting values below 1 when set.\n *\n * @internal\n */\nexport function readOptionalPageSize(\n registryConfig: Config,\n): number | undefined {\n const pageSize = registryConfig.getOptionalNumber('pageSize');\n if (pageSize !== undefined && pageSize < 1) {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: \"pageSize\" ` +\n `must be at least 1, got ${pageSize}.`,\n );\n }\n return pageSize;\n}\n\n/**\n * Read optional `hostAllowList`, normalizing entries to lowercase.\n *\n * Returns `undefined` when the key is absent (no filtering).\n * Returns an empty array when configured as `[]` — semantically\n * \"deny all\" (no hostname can pass validation).\n *\n * @internal\n */\nexport function readHostAllowList(\n registryConfig: Config,\n): string[] | undefined {\n const list = registryConfig.getOptionalStringArray('hostAllowList');\n if (!list) {\n return undefined;\n }\n return list.map(h => h.toLowerCase());\n}\n\n/**\n * Validate that a URL's hostname is present in the configured allow list.\n * Throws when the hostname is not in the list.\n *\n * @internal\n */\nexport function validateHostAllowList(\n url: string,\n hostAllowList: string[],\n): void {\n const parsed = new URL(url);\n const hostname = parsed.hostname.toLowerCase();\n if (!hostAllowList.includes(hostname)) {\n throw new Error(\n `Invalid ${MCP_REGISTRY_INSTANCE_CONFIG_PATH} configuration: the hostname ` +\n `\"${hostname}\" from baseUrl \"${url}\" is not in the configured ` +\n `hostAllowList [${hostAllowList.join(', ')}].`,\n );\n }\n}\n\n/**\n * Read `remotesOnly`, defaulting to `false`.\n *\n * @internal\n */\nexport function readRemotesOnly(registryConfig: Config): boolean {\n try {\n return (\n registryConfig.getOptionalBoolean('remotesOnly') ?? DEFAULT_REMOTES_ONLY\n );\n } catch {\n // ConfigReader throws TypeError for empty-string env substitution.\n return DEFAULT_REMOTES_ONLY;\n }\n}\n\n/**\n * Read `latestVersion`, defaulting to `false`.\n *\n * When true, list requests include `?version=latest`.\n *\n * @internal\n */\nexport function readLatestVersion(registryConfig: Config): boolean {\n try {\n return (\n registryConfig.getOptionalBoolean('latestVersion') ??\n DEFAULT_LATEST_VERSION\n );\n } catch {\n // ConfigReader throws TypeError for empty-string env substitution.\n return DEFAULT_LATEST_VERSION;\n }\n}\n\n/**\n * Read the provider schedule, or the documented default when omitted.\n *\n * @internal\n */\nexport function readProviderSchedule(\n registryConfig: Config,\n): SchedulerServiceTaskScheduleDefinition {\n const scheduleConfig = registryConfig.getOptionalConfig('schedule');\n if (!scheduleConfig) {\n return DEFAULT_SCHEDULE;\n }\n return readSchedulerServiceTaskScheduleDefinitionFromConfig(scheduleConfig);\n}\n\n/**\n * Provider configuration. Fields with documented defaults may be omitted\n * on direct construction; the entity provider and config reader apply the\n * same defaults as app-config parsing.\n *\n * @public\n */\nexport interface McpRegistryProviderConfig {\n /** Base URL of the MCP Registry (required). */\n baseUrl: string;\n /** Optional identity prefix override passed to the mapping transform. */\n baseName?: string;\n /** Registry API version slug used in the endpoint path (default `v1`). */\n apiVersion?: string;\n /** Default entity owner ref when the mapping does not supply one. */\n defaultOwner?: string;\n /** Default entity lifecycle when the mapping does not supply one. */\n defaultLifecycle?: string;\n /** Maximum pages fetched per sync (default `10`); excess pages resume next sync. */\n pageLimit?: number;\n /** Registry `?limit=` page-size query; omitted from the request when unset. */\n pageSize?: number;\n /**\n * Maximum total entries buffered for one complete registry traversal\n * before a full mutation (default `5000`). Spans resume syncs when\n * `pageLimit` pauses mid-traversal. When exceeded, the provider\n * commits the buffer, saves an end cursor, and later traversals stop\n * at that cursor until `maxEntries` is patched.\n */\n maxEntries?: number;\n /**\n * When true, only ingest servers with at least one native remote.\n * Package-only / placeholder-remote servers are skipped (default `false`).\n */\n remotesOnly?: boolean;\n /**\n * When true, list requests include `?version=latest` so the registry\n * returns only the latest version of each server (default `false`).\n */\n latestVersion?: boolean;\n /** Optional allowlist of permitted hostnames for defense-in-depth SSRF protection. */\n hostAllowList?: string[];\n /** Schedule for the sync task. */\n schedule: SchedulerServiceTaskScheduleDefinition;\n}\n\n/**\n * {@link McpRegistryProviderConfig} with defaults applied for fields\n * that are optional on the public interface.\n *\n * @internal\n */\nexport type ResolvedMcpRegistryProviderConfig = McpRegistryProviderConfig & {\n apiVersion: string;\n pageLimit: number;\n maxEntries: number;\n remotesOnly: boolean;\n latestVersion: boolean;\n};\n\n/**\n * Apply documented defaults for optional provider config fields.\n *\n * @internal\n */\nexport function resolveMcpRegistryProviderConfig(\n config: McpRegistryProviderConfig,\n): ResolvedMcpRegistryProviderConfig {\n return {\n ...config,\n apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,\n pageLimit: config.pageLimit ?? DEFAULT_PAGE_LIMIT,\n maxEntries: config.maxEntries ?? DEFAULT_MAX_ENTRIES,\n remotesOnly: config.remotesOnly ?? DEFAULT_REMOTES_ONLY,\n latestVersion: config.latestVersion ?? DEFAULT_LATEST_VERSION,\n };\n}\n\n/**\n * Read and validate the MCP Registry provider configuration from\n * `catalog.providers.mcpRegistry.mcpRegistry` (reserved instance id).\n * Returns `undefined` when the providers map or reserved instance is\n * absent (inert module).\n *\n * Additional instance ids under `catalog.providers.mcpRegistry` are\n * ignored; pass `warn` to surface that multiple registries are not\n * supported yet. When `hostAllowList` is omitted, `warn` is also used\n * to recommend configuring hostname restrictions.\n *\n * @throws When a legacy flat `catalog.providers.mcpRegistry` object is\n * used, or when `baseUrl` is missing on the reserved instance.\n */\nexport function readMcpRegistryProviderConfig(\n rootConfig: Config,\n warn?: ConfigWarnFn,\n): ResolvedMcpRegistryProviderConfig | undefined {\n const providersConfig = rootConfig.getOptionalConfig('catalog.providers');\n if (!providersConfig) {\n return undefined;\n }\n\n const providersMap = providersConfig.getOptionalConfig('mcpRegistry');\n if (!providersMap) {\n return undefined;\n }\n\n const registryConfig = readReservedRegistryInstanceConfig(providersMap, warn);\n if (!registryConfig) {\n return undefined;\n }\n\n assertSingleRegistryConfig(registryConfig);\n\n const baseUrl = readRequiredHttpBaseUrl(registryConfig);\n const hostAllowList = readHostAllowList(registryConfig);\n\n if (hostAllowList) {\n validateHostAllowList(baseUrl, hostAllowList);\n } else {\n warn?.(\n `${MCP_REGISTRY_INSTANCE_CONFIG_PATH}.hostAllowList is not configured; ` +\n `outbound registry requests are not restricted by hostname. Set ` +\n `hostAllowList to permitted registry hostnames for defense-in-depth ` +\n `against SSRF.`,\n );\n }\n\n return {\n baseUrl,\n baseName: safeGetOptionalString(registryConfig, 'baseName'),\n apiVersion:\n safeGetOptionalString(registryConfig, 'apiVersion') ??\n DEFAULT_API_VERSION,\n defaultOwner: safeGetOptionalString(registryConfig, 'defaultOwner'),\n defaultLifecycle: safeGetOptionalString(registryConfig, 'defaultLifecycle'),\n pageLimit: readPageLimit(registryConfig),\n pageSize: readOptionalPageSize(registryConfig),\n maxEntries: readMaxEntries(registryConfig),\n remotesOnly: readRemotesOnly(registryConfig),\n latestVersion: readLatestVersion(registryConfig),\n hostAllowList,\n schedule: readProviderSchedule(registryConfig),\n };\n}\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var module$1 = require('./module.cjs.js');
|
|
6
|
+
var McpRegistryEntityProvider = require('./McpRegistryEntityProvider.cjs.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
exports.default = module$1.catalogModuleMcpRegistryProvider;
|
|
11
|
+
exports.McpRegistryEntityProvider = McpRegistryEntityProvider.McpRegistryEntityProvider;
|
|
12
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|