Skip to content

feat(#4815): implement MCP registry provider backend plugin - #4871

Merged
gabemontero merged 63 commits into
mainfrom
agent/4815-mcp-registry-provider
Sep 21, 2026
Merged

gabemontero merged 63 commits into
mainfrom
agent/4815-mcp-registry-provider

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Summary

  • Implements MCP Registry provider backend module (catalog-backend-module-mcp-registry-provider) that ingests MCP servers from one configured MCP Registry into the RHDH catalog as mcp-server API entities
  • Config at catalog.providers.mcpRegistry (single object): baseUrl (required), baseName, apiVersion (default v1), schedule (default 30m/3m), pageLimit (default 10 pages/sync), pageSize, defaultOwner; inert when absent; keyed multi-registry maps rejected at startup
  • Registry client with full cursor pagination, page cap and repeated-cursor safeguards, and typed error handling
  • EntityProvider with full-mutation semantics (catalog converges to registry state), per-entry failure isolation with last-good retention (D6), and redhat.com/rhdh-mcp-registry-sync-status annotation (ok/degraded per D8)
  • Wholly delegates to mcp-registry-server-mapping-common for the server.json → entity transform and annotation projection

Testing

  • 43 unit tests covering config parsing/validation, registry client pagination, provider mapping integration, last-good retention, error handling, and module export
  • TypeScript type check passes (yarn tsc)
  • Lint passes (backstage-cli package lint)
  • Prettier passes (yarn prettier:check)
  • API report generated and verified clean

✔️ Checklist

  • A changeset describing the change and affected packages
  • Added documentation (README.md with configuration reference)
  • Tests for new functionality (43 unit tests)

Closes #4815

Post-script verification

  • Branch is not main/master (agent/4815-mcp-registry-provider)
  • Secret scan passed (gitleaks — 5fc4c610086737e81d15fc9e612ba2663d534f18..HEAD)
  • PR body secret scan passed (gitleaks — no-git)

@rhdh-gh-app

rhdh-gh-app Bot commented Sep 18, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
backend workspaces/ai-integrations/packages/backend none v0.0.0
@red-hat-developer-hub/backstage-plugin-catalog-backend-module-mcp-registry-provider workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider minor v0.1.0
@red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping workspaces/ai-integrations/plugins/catalog-mcp-registry-server-mapping minor v0.3.0

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:21 PM UTC · Completed 6:48 PM UTC

Commit: 5dde1b3 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $8.49

@codecov

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.04305% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.97%. Comparing base (ab17662) to head (c3b8dfa).
⚠️ Report is 20 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4871      +/-   ##
==========================================
+ Coverage   63.81%   63.97%   +0.16%     
==========================================
  Files        2698     2705       +7     
  Lines      107951   108462     +511     
  Branches    30264    30362      +98     
==========================================
+ Hits        68887    69388     +501     
- Misses      38506    38529      +23     
+ Partials      558      545      -13     
Flag Coverage Δ *Carryforward flag
adoption-insights 84.77% <ø> (ø) Carriedforward from 74fa58d
ai-integrations 87.10% <98.04%> (+2.76%) ⬆️
app-defaults 63.63% <ø> (ø) Carriedforward from 74fa58d
augment 46.67% <ø> (ø) Carriedforward from 74fa58d
boost 84.97% <ø> (ø) Carriedforward from 74fa58d
bulk-import 73.12% <ø> (ø) Carriedforward from 74fa58d
cost-management 13.53% <ø> (ø) Carriedforward from 74fa58d
dcm 73.47% <ø> (ø) Carriedforward from 74fa58d
e2e-adoption-insights 60.00% <ø> (ø) Carriedforward from 74fa58d
e2e-extensions 62.31% <ø> (ø) Carriedforward from 74fa58d
e2e-global-header 51.82% <ø> (ø) Carriedforward from 74fa58d
e2e-homepage 61.11% <ø> (ø) Carriedforward from 74fa58d
e2e-intelligent-assistant 46.01% <ø> (ø) Carriedforward from 74fa58d
e2e-orchestrator 49.49% <ø> (ø) Carriedforward from 74fa58d
e2e-orchestrator-plugin 49.48% <ø> (ø) Carriedforward from 74fa58d
e2e-quickstart 55.21% <ø> (ø) Carriedforward from 74fa58d
e2e-scorecard 49.83% <ø> (ø) Carriedforward from 74fa58d
e2e-theme 16.36% <ø> (ø) Carriedforward from 74fa58d
extensions 58.30% <ø> (ø) Carriedforward from 74fa58d
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 74fa58d
global-header 67.76% <ø> (ø) Carriedforward from 74fa58d
homepage 55.05% <ø> (ø) Carriedforward from 74fa58d
install-dynamic-plugins 73.52% <ø> (ø) Carriedforward from 74fa58d
intelligent-assistant 78.04% <ø> (ø) Carriedforward from 74fa58d
konflux 91.98% <ø> (ø) Carriedforward from 74fa58d
lightspeed 69.02% <ø> (ø) Carriedforward from 74fa58d
mcp-integrations 84.46% <ø> (ø) Carriedforward from 74fa58d
orchestrator 77.69% <ø> (ø) Carriedforward from 74fa58d
quickstart 63.74% <ø> (ø) Carriedforward from 74fa58d
sandbox 79.56% <ø> (ø) Carriedforward from 74fa58d
scorecard 88.91% <ø> (ø) Carriedforward from 74fa58d
theme 87.94% <ø> (ø) Carriedforward from 74fa58d
translations 5.12% <ø> (ø) Carriedforward from 74fa58d
x2a 78.44% <ø> (ø) Carriedforward from 74fa58d

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ab17662...c3b8dfa. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Sep 18, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

A large but bot-authored PR introducing an entirely new isolated plugin: the high change size and multiple dependency file changes are substantially offset by all-new-file isolation, zero protected-path and security-sensitive exposure, and a well-scoped, well-triaged linked issue with detailed acceptance criteria.

Previous run

Risk Assessment: moderate (2/5)

Details

Large but well-tested net-new plugin addition with strong test coverage (~3100 lines). Package rename introduces breaking-change risk. No protected-path or critical security sensitivity concerns beyond SSRF defense patterns. Moderate composite risk driven by breaking API change and SSRF gap.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Larger new plugin addition (56 files, 5036 lines, 3 dependency files) compared to prior snapshot, but same dimension scores apply — net-new area, no protected paths, no security concerns, no CI changes, bot author, well-bounded issue with resolved prerequisites — yielding an unchanged moderate composite risk.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Large new plugin addition (32 files, 3743 lines, 3 dependency files changed) in a well-scoped, net-new area with no security concerns, no CI changes, and a bot author on an explicitly bounded issue, yielding a moderate composite risk.

Previous run (4)

Risk Assessment: moderate (2/5)

Details

Signals are unchanged from the prior assessment -- bot-authored, all-additive new backend plugin with large line count and two dependency files raises Tier 1 to 2.25, but solid test coverage (ratio 0.21), net-new files with no modification of shared code, and full acceptance-criteria coverage keep Tier 2 and Tier 3 at moderate (2), yielding a composite score of 2.

Previous run (5)

Risk Assessment: moderate (2/5)

Details

A large-line-count, all-additive new backend plugin with dependency additions raises Tier 1, but bot authorship, solid test coverage (ratio 0.24), net-new files with no modification of existing code, and full acceptance-criteria coverage yield a composite moderate score of 2.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [scope-creep] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:259 — The implementation introduces a multi-tick resume pagination model (resumeCursor, pendingEntries, seenCursors, endCursor, endCursorMaxEntries state machine) that diverges from the documented contract. Task 3.4 says "exceeding the page cap or a repeated cursor fails the run rather than looping forever" but the implementation buffers entries across scheduled ticks and defers mutation until pagination is complete. The design.md risk section also states "a registry that still has nextCursor after pageLimit pages (default 10) fails the sync until the operator raises pageLimit" which contradicts the resume behavior.
    Remediation: Either revert to the authorized behavior (hit pageLimit → fail the run), or update task 3.4 and the design.md risks section to describe the resume-across-ticks model.

Medium

  • [breaking-api] workspaces/ai-integrations/plugins/catalog-mcp-registry-server-mapping/package.json:2 — The npm package is renamed from @red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common to @red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping. The changeset classifies this as "minor". At 0.x semver, minor bumps conventionally permit breaking changes, so the classification is defensible but the rename is still a hard break for existing import paths.
    Remediation: Consider publishing a shim package that re-exports from the new name, or at minimum add a deprecation notice to the old package name.

  • [scope-creep] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/config.d.ts — Three config fields — maxEntries, remotesOnly, and hostAllowList — are added to the public config schema. Task 2.1 in this PR's own tasks.md lists only: baseUrl, baseName, apiVersion, schedule, pageLimit, pageSize, and defaultOwner. The three extra fields are documented in the design.md and README but not in the task list.
    Remediation: Update task 2.1 to include the additional config fields, or open follow-on issues if they are out of scope.

Low

  • [error-handling-idiom] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:93 — The user-facing error message in assertSingleRegistryConfig lists only 7 config keys but KNOWN_MCP_REGISTRY_KEYS contains 10 keys. The keys maxEntries, remotesOnly, and hostAllowList are missing from the message.

  • [fail-open] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:358hostAllowList is optional and defaults to undefined (no filtering). When omitted, the SSRF defense layer is entirely absent. An empty array hostAllowList: [] correctly acts as "deny all", but omitting the key entirely results in no runtime hostname validation.

  • [scope-creep] workspaces/ai-integrations/.changeset/mcp-registry-mapping-common-provider-link.md:1 — The rename of catalog-mcp-registry-server-mapping-commoncatalog-mcp-registry-server-mapping is bundled in this PR without dedicated authorization from issue feat(ai-integrations): implement MCP registry provider backend plugin - 1 / 1 (mcp-registry-provider) #4815.

  • [naming-alignment] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/module.ts:36moduleId is set to catalog-backend-module-mcp-registry-provider, which redundantly embeds the catalog-backend-module- prefix alongside pluginId: 'catalog'. Backstage convention uses the module-specific suffix as the moduleId (e.g., mcp-registry-provider).

  • [api-shape] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:144run() and fetchApi are marked @internal in JSDoc but carry no TypeScript private modifier. This is a standard Backstage test-seam pattern: API Extractor strips @internal members from the published dist/index.d.ts, so external consumers using the published package cannot access these members.

  • [documentation-comment] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/README.md:17 — The Installation section instructs users to add backend.add(import('@backstage/plugin-catalog-backend-module-ai-model')) but the yarn add step only installs the provider package. Missing install instruction for the ai-model dependency.

  • [code-organization] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.parts.test.ts — The .parts.test.ts suffix is not established in the workspace. This is the only file using this naming convention across all ai-integrations plugins.

  • [edge-case] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:571 — The isAtEndCursor guard at the top of the while loop in fetchRegistryServers is checked before any page is fetched. When startCursor matches endCursor, the loop returns zero servers. This is correct behavior for resume-cursor completion but is untested.

  • [error-handling-gap] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:262fetchRegistryEntries() clears pagination state before applyMutation is called. If applyMutation throws, the next sync starts a fresh traversal. This is intended and tested behavior, but the implicit coupling between state transitions is not documented.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

New MCP Registry provider plugin for the RHDH catalog: an EntityProvider that paginates the MCP Registry API, maps server entries to Backstage catalog entities via the existing catalog-mcp-registry-server-mapping package, and applies full-mutation semantics with last-good retention. The PR also renames @red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common to @red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping. The implementation includes cursor pagination with cross-tick resume, configurable hostAllowList for SSRF defense, and comprehensive test coverage (~3100 lines of tests).

Findings

High

  1. [breaking-api] workspaces/ai-integrations/plugins/catalog-mcp-registry-server-mapping/package.json:2 — Package @red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common (v0.3.0) has been renamed to @red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping with no backward-compatibility shim, deprecation notice, or changeset entry for the old package name. Cross-repo consumers importing the old name will get an unresolved module error. The changeset records only a minor bump on the new name.
    • Remediation: Publish a deprecation shim package under the old name that re-exports from the new name, or add a changeset entry for the old package marking it deprecated, or reclassify as major on the new name.

Medium

  1. [SSRF] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:214 — TOCTOU gap in hostAllowList enforcement: fetchRegistryPage() calls doFetch(requestUrl) with the default redirect:'follow' policy, then validates response.url against the allow list after redirects have already been followed. An allowed registry host that 302-redirects to an internal host reaches that host before the post-redirect check fires.

    • Remediation: Use redirect: 'manual' and validate the Location header before following, or use redirect: 'error' to reject all redirects.
  2. [naming-convention] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:294 — Both client.ts and config.ts export a function named validateHostAllowList with incompatible signatures and error types. client.ts accepts (url: URL, hostAllowList: string[] | undefined) and throws McpRegistryClientError; config.ts accepts (url: string, hostAllowList: string[]) and throws plain Error. The identical name masks different contracts.

    • Remediation: Rename the client.ts version (e.g., assertRequestHostAllowed) to distinguish the runtime request guard from the config-time validation.
  3. [configuration-correctness] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/app-config.yaml:22hostAllowList commented-out examples use full URLs (with scheme prefix) but the validation code compares against url.hostname (bare hostname). Users uncommenting these examples will get a config validation error at startup. The README correctly documents bare hostnames.

    • Remediation: Change the example entries to bare hostnames (e.g., registry.modelcontextprotocol.io).
  4. [configuration-correctness] workspaces/ai-integrations/app-config.yaml:161 — Same hostAllowList example issue in the workspace-level config file. Commented-out entries use full URLs instead of bare hostnames, inconsistent with the validation logic.

    • Remediation: Change examples to bare hostnames.
  5. [stale-doc] workspaces/ai-integrations/openspec/changes/mcp-registry-server-mapping/audit.md:3design.md and proposal.md in the mcp-registry-server-mapping change root were modified in this PR but audit.md was not updated. Per repo conventions, audit.md must be refreshed when design documents change.

    • Remediation: Update the Last audited timestamp in audit.md.

Low

  1. [SSRF] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:223 — Post-redirect hostAllowList validation is skipped when response.url is falsy. The guard if (response.url) silently bypasses the check when response.url is empty or undefined.

    • Remediation: Fail closed: throw when hostAllowList is configured but response.url is absent.
  2. [api-shape] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:162 — Constructor options parameter is typed as an anonymous inline object with @internal marker, but the API report shows options?: {}. Extracting a named interface clarifies the public API surface.

    • Remediation: Extract into a named exported McpRegistryEntityProviderOptions interface.
  3. [stale-doc] workspaces/ai-integrations/openspec/changes/mcp-registry-server-mapping/design.md:218 — Open Questions section still refers to items as "deferred to the ingestion change" as if future work, but this PR implements the ingestion change.

    • Remediation: Update wording to acknowledge the ingestion layer now exists.
  4. [spec-doc-coherence] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/tasks.md:27 — Task 3.4 says "exceeding the page cap fails the run" but the implementation saves the resume cursor and defers to the next sync tick (soft-stop with resume).

    • Remediation: Update task 3.4 to distinguish page cap (soft-stop with resume) from repeated cursor (hard error).
  5. [design-direction] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:147 — Cross-tick resume state (resumeCursor, pendingEntries, seenCursors) goes beyond the linked issue's per-sync page cap specification. The design is internally coherent but not authorized by the issue.

  6. [api-shape] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:104FetchServersOptions.seenCursors is mutated in place (seenCursors.add(cursor)), which is atypical for options objects in this workspace and makes caller-side reasoning harder.

    • Remediation: Return the updated cursor set as part of FetchServersResult instead of mutating the input.
  7. [code-organization] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:58 — Four internal helpers (hasNativeRemote, buildLastGoodKey, readServerIdentity, formatMappingFailureMessage) are exported from the provider file. Extracting them into a separate src/providerUtils.ts would reduce file length and clarify the public surface.

    • Remediation: Move helper functions to src/providerUtils.ts.

Risk Assessment

Signal Value
Composite score 2 / 5
Risk level moderate

Provenance

Prior review provenance: unverifiable-wrong-app — prior review comment was created by a different app than expected. Severity anchoring was skipped for this run; this is a full independent review.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

High

  • [error-handling-gap] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:324 — When fetchRegistryServers fails mid-traversal after successfully fetching one or more pages in a multi-page resume sync, the seenCursors set retains cursors added during the failed call (via resolveNextCursor at client.ts:272 which mutates the shared set). On retry, this.resumeCursor is unchanged, so the provider re-fetches the same pages and encounters the same nextCursor values already in seenCursors. This triggers a false-positive "repeated cursor" McpRegistryClientError. The provider enters a permanently stuck state where every subsequent sync fails until the process is restarted.
    Remediation: Snapshot seenCursors before calling fetchRegistryServers and restore on error, or clear seenCursors on error and let repeated-cursor detection operate only within a single call.

Medium

  • [SSRF] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:213fetch() is called without redirect: 'manual'. Node.js fetch defaults to redirect: 'follow', which silently follows HTTP redirects. When hostAllowList is configured, the hostname validation only checks the initial request URL. A compromised or malicious registry could respond with a 3xx redirect to an internal host (e.g., cloud metadata at 169.254.169.254), bypassing the hostAllowList SSRF defense.
    Remediation: Pass { redirect: 'manual' } as the second argument to doFetch(requestUrl), or validate response.url against the hostAllowList after the fetch completes.

  • [fail-open] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:200readOptionalHostAllowList treats an explicit empty array (hostAllowList: []) the same as an absent key: both return undefined, which disables all hostname restrictions. An operator who sets hostAllowList: [] expecting it to block all outbound requests would unknowingly permit all hosts.
    Remediation: When the operator explicitly sets hostAllowList: [], either throw a validation error ("hostAllowList must contain at least one hostname when set") or treat it as "deny all" by returning an empty array instead of undefined.

  • [scope-creep] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts — Three config options (maxEntries, remotesOnly, hostAllowList) were added with no authorization from issue feat(ai-integrations): implement MCP registry provider backend plugin - 1 / 1 (mcp-registry-provider) #4815. The issue's explicit config surface is: baseUrl, baseName, apiVersion, schedule, pageLimit, pageSize, defaultOwner. Additionally, the pagination behavior was re-architected: spec task 3.4 says "exceeding the page cap fails the run" but the implementation saves the cursor and resumes on the next tick.
    Remediation: File a follow-on issue or amend feat(ai-integrations): implement MCP registry provider backend plugin - 1 / 1 (mcp-registry-provider) #4815 to explicitly authorize maxEntries, remotesOnly, and hostAllowList. Update tasks.md task 3.4 to reflect the resume-on-cap behavior.

  • [spec-coherence] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/tasks.md:27 — Task 3.4 is marked complete, but its text still says "exceeding the page cap... fails the run rather than looping forever." The implementation saves the cursor and resumes on the next tick. The repeated-cursor part still correctly aborts the run, but the page-limit trip behaves differently from what the spec describes.
    Remediation: Update task 3.4 to distinguish: repeated cursor aborts the run; page-limit saves the resume cursor and returns without a mutation.

  • [documentation-correctness] workspaces/ai-integrations/app-config.yaml:49 — The commented-out hostAllowList examples in both app-config.yaml files show full URLs with https:// scheme (e.g., https://registry.modelcontextprotocol.io). The implementation compares url.hostname (bare hostname) against the list, so a list entry with a scheme prefix will never match. If an operator uncomments these examples, validateHostAgainstAllowList will fail at startup. The README correctly shows bare hostnames.
    Remediation: Change hostAllowList examples in both app-config.yaml files to use bare hostnames (e.g., registry.modelcontextprotocol.io).

  • [code-organization] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.parts.test.ts:63createMockLogger, createDefaultConfig, and mockFetchForResponses are each defined identically in both McpRegistryEntityProvider.parts.test.ts and McpRegistryEntityProvider.test.ts. Project conventions (AGENTS.md) require shared test helpers to be extracted into testUtils.ts, which already exists in this package.
    Remediation: Move createMockLogger, createDefaultConfig, and mockFetchForResponses into src/testUtils.ts and import them in each test file.

  • [breaking-package-rename] workspaces/ai-integrations/plugins/catalog-mcp-registry-server-mapping/package.json:2 — The npm package name changes from @red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common to @red-hat-developer-hub/backstage-plugin-catalog-mcp-registry-server-mapping, and the Backstage pluginId changes from mcp-registry-provider to catalog-mcp-registry-server-mapping. The changeset labels this "minor" but it is a breaking change for any existing consumer.
    Remediation: Publish a final version of the old package that re-exports from the new package and carries a deprecation notice. Or label the changeset as "major" if no compatibility shim is desired. (Note: under 0.x semver, breaking changes in minor versions are permitted, but a deprecation shim is still good practice.)

  • [missing-doc-coverage] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/design.md:138maxEntries, remotesOnly, and hostAllowList are implemented but not described in the design document. hostAllowList addresses the SSRF risk called out in audit finding H, but design.md still says "Auth is a non-goal" with no mention of the allowlist mitigation.
    Remediation: Add design decisions for maxEntries, remotesOnly, and hostAllowList.

  • [missing-doc-coverage] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/specs/mcp-registry-provider/spec.md:15 — Configuration requirements in spec.md omit maxEntries, remotesOnly, and hostAllowList. These options have observable behavioral requirements but no spec coverage.
    Remediation: Add SHALL statements and scenarios for maxEntries, remotesOnly, and hostAllowList.

  • [stale-reference] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/audit.md:32 — Audit finding H says SSRF risk exists "until auth and allowlisting are in scope." This PR implements hostAllowList, making the "not yet in scope" language inaccurate.
    Remediation: Mark finding H as resolved or update to reflect that hostAllowList is implemented.

Low

  • [naming-convention] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:213 — Two functions for the same validation have divergent names: validateHostAgainstAllowList (config.ts) and validateUrlHostAllowList (client.ts). Inconsistent naming makes the relationship non-obvious.
    Remediation: Align naming so both functions share a common verb/noun pattern.

  • [documentation-comment-format] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts:107formatMappingFailureMessage produces awkward grammar when name is undefined but version is present: "Failed to map MCP Registry server entry version "1.0.0"".
    Remediation: Reorder fragments: "Failed to map MCP Registry server entry (version "1.0.0"): boom".

  • [architectural-coherence] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/McpRegistryEntityProvider.ts — No startup signal indicating last-good index is cold. Operators cannot distinguish "no degraded retention because first sync" from "no entries failed mapping".
    Remediation: Add a log.info at startup noting the last-good index is empty.

  • [incomplete-doc] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/README.md:5 — Installation section has no yarn add command. Sibling READMEs include explicit install instructions.
    Remediation: Add yarn add @red-hat-developer-hub/backstage-plugin-catalog-backend-module-mcp-registry-provider before the backend.add code.

  • [required-fields-with-implicit-defaults] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/report.api.md:30McpRegistryProviderConfig has non-optional fields (maxEntries, pageLimit, remotesOnly) but config-parsing provides defaults. Direct construction requires these fields even for default case.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

Medium

  • [public-api-surface] report.api.md:22 — The fetchApi?: typeof fetch parameter is baked into the @public constructor of McpRegistryEntityProvider. This test-injection seam is now a permanent API contract — any future refactor (e.g., adopting Backstage's FetchService) becomes a breaking change.
    Remediation: Move the test seam out of the public constructor, use an options object for optional parameters, or mark the class @alpha while the API stabilises.

  • [public-api-surface] report.api.md:29run(): Promise<void> is exported as @public on McpRegistryEntityProvider but is not part of the EntityProvider interface contract. Removing or changing its signature becomes a breaking @public change.
    Remediation: Mark run() as @internal or @alpha to signal it may change without a major version bump.

Low

  • [logic-error] provider.ts:371rebuildLastGoodIndex filters out degraded entities with === 'degraded' (negative check), but design.md D6 specifies "only entities with ok are stored in the index." Under the current two-status invariant both checks are equivalent, but a positive check (!== 'ok') would match the spec and be forward-compatible.
    Remediation: Change the filter from === 'degraded' to !== 'ok'.

  • [dos-resource-exhaustion] client.ts:236 — The pagination loop accumulates all server entries in memory without validating per-page array size. While pageLimit caps the number of pages (default 10), a malfunctioning registry could return an arbitrarily large servers array on a single page.
    Remediation: Add a configurable maxEntries cap that aborts sync when allServers.length exceeds the threshold.

  • [naming-convention] provider.ts:1 — The source file for McpRegistryEntityProvider uses the generic name provider.ts. The workspace's only other EntityProvider (ModelCatalogResourceEntityProvider.ts) uses a class-based filename.
    Remediation: Rename to McpRegistryEntityProvider.ts and update imports.

  • [code-organization] package.json:30 — The scripts block uses lint:check/lint:fix split and adds tsc/prettier:* scripts. All other catalog-backend-module-* plugins use "lint": "backstage-cli package lint". The divergence is also propagated to mcp-registry-server-mapping-common/package.json.
    Remediation: Use "lint": "backstage-cli package lint" to match the workspace standard.

  • [public-api-surface] report.api.md:33 — All seven fields of the @public McpRegistryProviderConfig interface are flagged (undocumented) by API Extractor.
    Remediation: Add TSDoc to each field in McpRegistryProviderConfig.

  • [spec-tracking-gap] tasks.md — All 18 task items remain unchecked despite the PR delivering a complete implementation. The header comment says "After each completed task, commit the changes."
    Remediation: Mark tasks as [x] or amend the header comment.

  • [stale-reference] audit.md:3 — The mcp-registry-server-mapping/proposal.md was updated in this PR (consumer name change) but the co-located audit.md timestamp was not updated.
    Remediation: Update the audit.md timestamp to reflect the proposal.md change.

  • [commit-convention] workspaces/ai-integrations — AGENTS.md requires an Assisted-by: <model> footer on all commits. This cannot be verified from the diff.
    Remediation: Verify all commits include the required footer.

  • [ssrf] client.ts:143 — The fetchRegistryPage function makes outbound HTTP requests to the admin-configured baseUrl without host validation against private network ranges. This is consistent with the standard Backstage entity-provider threat model where backend config is trusted.

  • [stale-reference] design.md:73 — The mcp-registry-server-mapping/design.md contains references to a "future ingestion change" that is now stale since this PR ships that ingestion layer.

  • [config-schema] config.d.ts:21 — The config key catalog.providers.mcpRegistry is typed as a single flat object. Multi-registry support would require a breaking config schema change. This is an intentional design decision documented in D1.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

High

  • [stale-behavior-description] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/design.md:108 — The design doc section D6 still describes the old last-good index behavior: "At the start of each sync, the provider loads existing provider-managed entities into an index for last-good retention." The spec.md was updated in this PR to reflect the new implementation: the last-good index is now an in-memory structure populated at the END of each sync run. design.md contradicts the updated spec and the actual implementation.
    Remediation: Update design.md to reflect the in-memory index populated at end of sync.

Medium

  • [Error handling gap] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:103 — The multi-registry detection heuristic calls registryConfig.getOptionalConfig(key) on unknown config keys without a try-catch. Backstage's ConfigReader.getOptionalConfig() throws TypeError when the underlying value is a scalar rather than an object. A user adding an unrecognized scalar key (e.g., a typo) would get a confusing ConfigReader error instead of the intended "missing baseUrl" message. The PR itself demonstrates awareness of this via the safeGetOptionalString() helper, yet this call lacks equivalent protection.
    Remediation: Wrap the getOptionalConfig(key) call in a try-catch that catches TypeError and continues to the next key.

  • [scope-creep] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts:243 — The last-good index is rebuilt from ALL entities committed in a mutation, including entities already carrying sync-status: degraded. This creates perpetual retention: a server that persistently fails to map is committed with "degraded" on cycle N, stored back in lastGoodIndex, and on cycle N+1 the same degraded entity is again retrieved and committed indefinitely.
    Remediation: After rebuilding lastGoodIndex, consider skipping entities whose redhat.com/rhdh-mcp-registry-sync-status annotation is already degraded.

  • [stale-behavior-description] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/tasks.md:33 — tasks.md task 4.1 still prescribes "at the start of each run(), load provider-managed entities into a last-good index" and task 5.2 refers to "indexed at sync start". The actual implementation populates the index at the END of each sync.
    Remediation: Update task 4.1 and task 5.2 to describe the new in-memory index populated at end of sync.

  • [stale-audit-timestamp] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/audit.md:3 — The audit.md "Last audited" timestamp is 2026-09-15. The spec.md was modified in this PR (behavioral change to the last-good index mechanism). Per workspace AGENTS.md, the audit timestamp must be updated.
    Remediation: Update audit.md timestamp to 2026-09-18.

Low

  • [Error handling gap] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts:132 — The property access entry.server at line 132 is outside the per-entry try-catch block. If the registry returns a malformed entry where entry is null/undefined, the TypeError propagates out of the for-loop and aborts the entire sync, bypassing per-entry failure isolation.
    Remediation: Move const serverDoc = entry.server; inside the try block.

  • [Test adequacy] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.test.ts — No test covers the empty-registry scenario (servers: []). The provider would commit a full mutation with an empty entities array, deleting all managed entities.

  • [Test adequacy] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.test.ts — No test verifies that when applyMutation throws, the lastGoodIndex is NOT updated. This invariant protects against future regressions.

  • [Data Exposure] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:124 — When the registry returns a non-2xx status, the full response body is interpolated into the error message and logged. If baseUrl is misconfigured to point to an internal service, the response body may contain sensitive data.
    Remediation: Truncate the response body to a fixed maximum (e.g., 256 characters).

  • [API shape patterns] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/index.ts:22 — The index.ts re-exports only the default module, leaving McpRegistryEntityProvider and McpRegistryProviderConfig entirely unexported. Sibling backend-module plugins export their core public types.

  • [code organization] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts:60 — The McpRegistryEntityProvider class uses direct constructor injection rather than the static fromConfig() factory pattern established by the workspace's existing entity provider sibling.

  • [stale-forward-reference] workspaces/ai-integrations/openspec/changes/mcp-registry-server-mapping/proposal.md:45 — The proposal.md lists consumers as "future registry entity provider". The provider is no longer future — it is implemented in this PR.

  • [code organization] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:103 — Production source contains an eslint-disable-next-line no-constant-condition directive for a while(true) loop. Across sibling plugins, eslint-disable directives appear exclusively in test files.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Review

Findings

Medium

  • [error handling gap] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts:96 — The new URL(endpoint) call is not wrapped in a try/catch. If baseUrl is not a valid absolute URL, new URL() throws a TypeError that is not a McpRegistryClientError, so it propagates past the provider’s catch block (which only handles McpRegistryClientError) and reaches the scheduler as an unhandled error on every sync cycle, bypassing the provider’s safe-abort path.
    Remediation: Wrap new URL(endpoint) in a try/catch that throws a McpRegistryClientError with an actionable message, or validate URL shape in readMcpRegistryProviderConfig.

  • [Error-handling idioms] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:99 — All four getOptionalString() calls (baseUrl, baseName, apiVersion, defaultOwner) lack the safeGetOptionalString try-catch wrapper used by sibling plugins (catalog-techdoc-url-reader-backend, kserve-kubeflow-connector-backend). Backstage’s ConfigReader throws TypeError for empty-string values from unresolved env var substitution (e.g., ${UNSET_VAR:-}), causing a raw TypeError before the custom error message is reached.
    Remediation: Introduce a safeGetOptionalString helper matching the established workspace pattern.

  • [spec-implementation-divergence] workspaces/ai-integrations/openspec/changes/mcp-registry-provider/specs/mcp-registry-provider/spec.md:208 — The spec states the provider SHALL load all provider-managed catalog entities into a last-good index at the start of each sync run (implying catalog-backed, cross-restart persistence). The implementation uses an in-memory lastGoodIndex cleared and repopulated at the end of each sync. After restart, last-good retention is unavailable for any entry that fails mapping on the first sync.
    Remediation: Update the spec to document in-memory semantics, or implement catalog-backed loading to match the spec.

Low

  • [edge case / input validation] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:113pageLimit and pageSize accept 0 or negative numbers without bounds validation, producing confusing semantics.
    Remediation: Add minimum checks (throw if < 1 or clamp with Math.max(1, value)).

  • [SSRF / input validation] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts:99baseUrl lacks URL scheme validation. Explicit http:/https: validation at startup provides defense in depth and clearer error messages than the runtime rejection from fetch.
    Remediation: Parse with new URL(baseUrl) and verify protocol is http: or https:.

  • [test adequacy] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.test.ts:300 — The degraded-retention test creates a first provider instance (lines 300–327) whose results are unused by the actual assertion; the meaningful test starts at line 347 with provider2.
    Remediation: Remove redundant first provider setup (lines 300–327).

  • [architectural-coherence] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts:240 — The last-good index rebuild reads annotation keys (modelcontextprotocol.io/name, modelcontextprotocol.io/version) while failure recovery constructs keys from raw serverDoc fields — an implicit contract with the mapping library that could silently break if annotation naming changes.
    Remediation: Document this dependency or store raw identity fields separately.

  • [API shape patterns] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts:134 — Inline structural type { prefix?: string; owner?: string; } for mappingDefaults duplicates a subset of McpServerMappingDefaults from the imported common package.
    Remediation: Import and use McpServerMappingDefaults directly.

  • [stale-doc] workspaces/ai-integrations/plugins/mcp-registry-server-mapping-common/README.md:9 — Sibling README describes consumer as "a future mcp-registry-provider" — this PR introduces that provider, making the qualifier stale.
    Remediation: Update to reference the now-existing provider.

  • [Config schema visibility] workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/config.d.ts:34 — The schedule field is missing @visibility backend while all other config fields have it.
    Remediation: Add /** @visibility backend */ before the schedule?: line.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@michael-valdron

Copy link
Copy Markdown
Member

/fs-fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 7:30 PM UTC · Completed 7:48 PM UTC

Commit: 5dde1b3 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.05

fullsend-ai-coder Bot added a commit that referenced this pull request Sep 18, 2026
- Wrap new URL(endpoint) in try/catch throwing McpRegistryClientError
  in client.ts (error handling gap)
- Add safeGetOptionalString helper and use for all config string reads
  in config.ts (error-handling idioms)
- Add URL scheme validation (http/https only) for baseUrl in config.ts
  (SSRF/input validation)
- Add pageLimit >= 1 and pageSize >= 1 bounds checks in config.ts
  (edge case/input validation)
- Update spec.md to document in-memory last-good index semantics
  (spec-implementation divergence)
- Remove redundant first provider setup in degraded-retention test
  (test adequacy)
- Document annotation key dependency in last-good index rebuild comment
  in provider.ts (architectural coherence)
- Import and use McpServerMappingDefaults type instead of inline type
  in provider.ts (API shape patterns)
- Update README.md to reference existing provider instead of future
  (stale-doc)
- Add @visibility backend annotation to schedule field in config.d.ts
  (config schema visibility)

Addresses #4871

Assisted-by: Claude Opus 4.6
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Addressed all 10 review findings: 3 medium (URL error handling in client, safeGetOptionalString in config, spec-implementation divergence for in-memory last-good index) and 7 low (pageLimit/pageSize bounds, URL scheme validation, test cleanup, architectural coherence documentation, McpServerMappingDefaults import, stale README, config visibility annotation). All 43 tests pass, lint clean, no secrets detected.

Fixed (10):

  1. error handling gap: new URL(endpoint) not wrapped in try/catch (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts): Wrapped new URL(endpoint) in a try/catch that throws McpRegistryClientError with an actionable message, so invalid URLs are caught at the provider's error boundary instead of propagating as raw TypeError
  2. Error-handling idioms: getOptionalString() calls lack safeGetOptionalString wrapper (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts): Added safeGetOptionalString helper matching the established workspace pattern and replaced all four getOptionalString() calls (baseUrl, baseName, apiVersion, defaultOwner) with it to handle TypeError from empty-string env var substitution
  3. spec-implementation-divergence: spec implies catalog-backed last-good index but implementation is in-memory (workspaces/ai-integrations/openspec/changes/mcp-registry-provider/specs/mcp-registry-provider/spec.md): Updated the spec requirement text to explicitly document in-memory semantics: index is populated from successfully committed entities at end of each sync run, does not persist across provider restarts, and no last-good entries are available on first sync after restart
  4. edge case / input validation: pageLimit and pageSize accept 0 or negative numbers (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts): Added minimum checks: pageLimit must be at least 1 (throws if < 1), pageSize must be at least 1 when present (throws if < 1)
  5. SSRF / input validation: baseUrl lacks URL scheme validation (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts): Added URL parsing with new URL(baseUrl) and explicit protocol check requiring http: or https:, providing defense in depth and clear error messages at startup
  6. test adequacy: redundant first provider setup in degraded-retention test (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.test.ts): Removed the redundant first provider instance and its assertions (lines 300-327 in the original), keeping only the single-provider combined-fetch approach that tests both sync cycles
  7. architectural-coherence: implicit contract between last-good index annotation keys and mapping library (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts): Added detailed inline comment documenting the dependency between annotation keys (modelcontextprotocol.io/name and modelcontextprotocol.io/version) set by mapServerToEntity and the raw serverDoc fields used in buildLastGoodKey, noting that both paths must be updated in tandem if annotation naming changes
  8. API shape patterns: inline structural type for mappingDefaults duplicates McpServerMappingDefaults (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts): Imported McpServerMappingDefaults from the common package and replaced the inline type annotation with it
  9. stale-doc: README describes consumer as future mcp-registry-provider (workspaces/ai-integrations/plugins/mcp-registry-server-mapping-common/README.md): Updated README text from 'intended for consumers such as a future mcp-registry-provider' to 'consumed by the catalog-backend-module-mcp-registry-provider' to reflect the now-existing provider
  10. Config schema visibility: schedule field missing @visibility backend (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/config.d.ts): Added /** @visibility backend */ JSDoc annotation before the schedule field, consistent with all other fields in the config schema

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@michael-valdron

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:39 PM UTC · Completed 9:03 PM UTC

Commit: 7577d6b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $8.47

fullsend-ai-review[bot]

This comment was marked as outdated.

@michael-valdron

Copy link
Copy Markdown
Member

/fs-fix address review comments

@fullsend-ai-coder

fullsend-ai-coder Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 9:21 PM UTC · Completed 9:39 PM UTC

Commit: 7577d6b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.70

fullsend-ai-coder Bot added a commit that referenced this pull request Sep 18, 2026
- Wrap getOptionalConfig() in try-catch for scalar TypeError (config.ts)
- Move entry.server access inside try block for null safety (provider.ts)
- Exclude degraded entities from lastGoodIndex to prevent perpetual retention
- Truncate error response body to 256 chars to avoid data exposure (client.ts)
- Refactor while(true) to while(hasMorePages) to remove eslint-disable
- Re-export McpRegistryEntityProvider and McpRegistryProviderConfig (index.ts)
- Add @public release tags and regenerate API report
- Update design.md D6 to describe in-memory index populated at end of sync
- Update tasks.md 4.1/5.2 to reflect end-of-sync index behavior
- Update audit.md timestamp to 2026-09-18
- Update proposal.md consumer reference from "future" to actual plugin name
- Add test: empty registry commits full mutation with 0 entities
- Add test: applyMutation throw does not update lastGoodIndex
- Add test: degraded entities excluded from lastGoodIndex on subsequent syncs

Addresses #4871

Assisted-by: Claude Opus 4.6
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 2 (human-triggered)

Addressed all 13 review findings: 12 fixed (error handling gaps, perpetual degraded retention, stale docs, test coverage, data exposure, eslint-disable, API exports), 1 disagreed (fromConfig() factory pattern has no workspace precedent). Added 3 new tests (empty registry, applyMutation throw, degraded exclusion). All 46 tests pass, lint clean, tsc clean, API report regenerated.

Fixed (12):

  1. stale-behavior-description: design.md D6 describes old last-good index behavior (workspaces/ai-integrations/openspec/changes/mcp-registry-provider/design.md): Updated D6 description to reflect in-memory last-good index populated at end of each successful sync, with degraded entity exclusion and restart semantics
  2. Error handling gap: getOptionalConfig(key) throws TypeError on scalar values (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/config.ts): Wrapped registryConfig.getOptionalConfig(key) in try-catch that catches TypeError and continues to the next key
  3. scope-creep: lastGoodIndex retains degraded entities causing perpetual retention (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts): Added sync-status check in lastGoodIndex rebuild to skip entities with 'degraded' annotation, preventing perpetual retention of stale data
  4. stale-behavior-description: tasks.md 4.1 and 5.2 describe start-of-run index loading (workspaces/ai-integrations/openspec/changes/mcp-registry-provider/tasks.md): Updated task 4.1 to describe in-memory index rebuilt at end of sync with ok-only filter; updated task 5.2 to reference 'in-memory index populated at end of prior sync'
  5. stale-audit-timestamp: audit.md timestamp is 2026-09-15 (workspaces/ai-integrations/openspec/changes/mcp-registry-provider/audit.md): Updated Last audited timestamp to 2026-09-18T00:00:00Z
  6. Error handling gap: entry.server access outside try-catch (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.ts): Moved const serverDoc = entry.server inside the try block; added safe access in the catch block with null/undefined check
  7. Test adequacy: no test for empty registry scenario (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.test.ts): Added test 'commits full mutation with empty entities for empty registry' verifying full mutation with 0 entities on servers: []
  8. Test adequacy: no test verifying lastGoodIndex not updated when applyMutation throws (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/provider.test.ts): Added test 'does not update lastGoodIndex when applyMutation throws' verifying last-good from first sync survives a second sync's applyMutation throw
  9. Data Exposure: full response body in error messages (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts): Truncated response body to 256 characters max in non-2xx error messages to prevent sensitive data exposure
  10. API shape patterns: index.ts only exports default module (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/index.ts): Added re-exports for McpRegistryEntityProvider and McpRegistryProviderConfig with @public release tags; regenerated API report
  11. stale-forward-reference: proposal.md lists consumers as 'future registry entity provider' (workspaces/ai-integrations/openspec/changes/mcp-registry-server-mapping/proposal.md): Updated consumer reference from 'future registry entity provider' to 'catalog-backend-module-mcp-registry-provider entity provider'
  12. code organization: eslint-disable in production code for while(true) (workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider/src/client.ts): Refactored while(true) to while(hasMorePages) with a boolean flag, eliminating the eslint-disable-next-line no-constant-condition directive

Disagreed (1):

  1. code organization: use static fromConfig() factory pattern: No sibling entity provider in this workspace uses the static fromConfig() factory pattern. The reviewer's claim of 'established pattern' is not substantiated by the codebase. The direct constructor is consistent with existing conventions.

Tests: passed

Decision points
  • Whether to adopt the fromConfig() factory pattern (alternatives: Add static fromConfig() factory method, Keep direct constructor injection; rationale: No sibling plugins in this workspace use fromConfig(). The direct constructor is the workspace convention. Changing patterns without codebase precedent adds unnecessary churn.)
  • How to prevent perpetual retention of degraded entities (alternatives: Skip degraded entities in lastGoodIndex rebuild, Remove from index explicitly after retention, Keep all entities in index; rationale: Filtering during rebuild is the simplest approach. Degraded entities are retained once (from the prior ok entry) but not re-stored, so a persistent mapping failure eventually drops the entity after one degraded cycle. Added a dedicated test to verify this three-sync invariant.)

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

michael-valdron and others added 15 commits September 20, 2026 19:03
…ce id

Move provider options to catalog.providers.mcpRegistry.mcpRegistry so the
top-level key is a map of instances, ignoring extra ids with a warning.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Prefer the underlying cause message for network failures and avoid
embedding Error constructor names like TypeError in operator-facing logs.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Type providersConfig as JsonObject and drop an unused import so
yarn tsc:full passes in CI.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Document nested config, remotesOnly, hostAllowList, maxEntries, and
the mapping package rename for consumers.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Extract the volume mount string before JSON.stringify to address
SonarCloud feedback.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Log a defense-in-depth SSRF warning via the config warn sink when the
provider starts without a hostname allowlist.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Pass an optional lifecycle override through to the mapping so operators
can align ingested mcp-server entities with other ai-integrations defaults.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Split prerequisite, package install, and module registration so the
ai-model dependency is clear up front.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep last-good entities outside the maxEntries window in the mutation
with degraded sync status, and re-index them until they are refreshed.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… testing secret redaction

Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Sort McpRegistryProviderConfig members to match API Extractor output.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Align assertSingleRegistryConfig error keys with KNOWN_MCP_REGISTRY_KEYS,
use the short Backstage moduleId, document internal test seams, cover the
startCursor===endCursor soft-stop edge case, and note pagination reset
before applyMutation.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Drop branch-local edits under openspec/changes for mcp-registry-provider
and mcp-registry-server-mapping so this PR no longer changes those specs.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
When enabled, list requests include ?version=latest so the registry
returns only the latest version of each server; default remains unset.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>

@michael-valdron michael-valdron left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabemontero @johnmcollier This lgtm to proceed with peer review 👍

@gabemontero

Copy link
Copy Markdown
Contributor

@gabemontero @johnmcollier This lgtm to proceed with peer review 👍

how did your testing against the public MCP registry go @michael-valdron ?

@michael-valdron

Copy link
Copy Markdown
Member

@gabemontero @johnmcollier This lgtm to proceed with peer review 👍

how did your testing against the public MCP registry go @michael-valdron ?

@gabemontero Seems good now, I was able to ingest large batch (4800 entries with limits) using https://registry.modelcontextprotocol.io/ and all of them on https://staging.registry.modelcontextprotocol.io/ and local. https://registry.modelcontextprotocol.io/ has 5000+ entries so good for trying out the quota limits on the provider though I did most of my testing with the other two.

You can test it out by replacing baseUrl with https://staging.registry.modelcontextprotocol.io/ (recommended for most tries) or https://registry.modelcontextprotocol.io/ or using yarn --cwd workspaces/ai-integrations start-local-mcp-server to launch the local MCP Registry with defaults (113 entries) instead.

For testing the backend I recommend starting from the plugin directory yarn --cwd workspaces/ai-integrations/plugins/catalog-backend-module-mcp-registry-provider start but workspace yarn --cwd workspaces/ai-integrations dev works too if you want to see it populate the frontend as well.

Add a guide for pointing the MCP Registry provider at production or
staging official registries, and cross-link it from workspace and plugin
READMEs plus the local deploy doc.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Comment thread workspaces/ai-integrations/docs/using-official-mcp-registries.md Outdated
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
@sonarqubecloud

Copy link
Copy Markdown

@gabemontero
gabemontero enabled auto-merge (squash) September 21, 2026 16:35
@gabemontero
gabemontero merged commit 4bb6232 into main Sep 21, 2026
17 checks passed
JslYoon pushed a commit to JslYoon/rhdh-plugins that referenced this pull request Sep 21, 2026
…plugin (redhat-developer#4871)

* feat(redhat-developer#4815): implement MCP registry provider backend plugin

Add catalog-backend-module-mcp-registry-provider, a Backstage catalog
backend module that ingests MCP servers from one configured MCP Registry
into the RHDH catalog as mcp-server API entities.

Implementation includes:
- Config reading at catalog.providers.mcpRegistry (single object) with
  baseUrl (required), baseName, apiVersion (default v1), defaultOwner,
  pageLimit (default 10), pageSize, and schedule (default 30m/3m)
- Registry client with full cursor pagination, page cap safeguard,
  repeated-cursor detection, and typed error handling
- EntityProvider with full-mutation semantics (catalog converges to
  registry state), per-entry failure isolation with last-good retention
  (D6), and sync status annotation (ok/degraded per D8)
- Provider attribution: locationKey mcp-registry-provider,
  backstage.io/managed-by-location url:<normalizedBaseUrl>
- Delegates entirely to mcp-registry-server-mapping-common for the
  server.json to entity transform and annotation projection
- Keyed multi-registry map rejected at startup with actionable error
- 43 unit tests covering config, client, provider, and module

Closes redhat-developer#4815

Assisted-by: Claude Opus 4.6

* fix: address review feedback on PR redhat-developer#4871

- Wrap new URL(endpoint) in try/catch throwing McpRegistryClientError
  in client.ts (error handling gap)
- Add safeGetOptionalString helper and use for all config string reads
  in config.ts (error-handling idioms)
- Add URL scheme validation (http/https only) for baseUrl in config.ts
  (SSRF/input validation)
- Add pageLimit >= 1 and pageSize >= 1 bounds checks in config.ts
  (edge case/input validation)
- Update spec.md to document in-memory last-good index semantics
  (spec-implementation divergence)
- Remove redundant first provider setup in degraded-retention test
  (test adequacy)
- Document annotation key dependency in last-good index rebuild comment
  in provider.ts (architectural coherence)
- Import and use McpServerMappingDefaults type instead of inline type
  in provider.ts (API shape patterns)
- Update README.md to reference existing provider instead of future
  (stale-doc)
- Add @visibility backend annotation to schedule field in config.d.ts
  (config schema visibility)

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix: address review feedback on PR redhat-developer#4871

- Wrap getOptionalConfig() in try-catch for scalar TypeError (config.ts)
- Move entry.server access inside try block for null safety (provider.ts)
- Exclude degraded entities from lastGoodIndex to prevent perpetual retention
- Truncate error response body to 256 chars to avoid data exposure (client.ts)
- Refactor while(true) to while(hasMorePages) to remove eslint-disable
- Re-export McpRegistryEntityProvider and McpRegistryProviderConfig (index.ts)
- Add @public release tags and regenerate API report
- Update design.md D6 to describe in-memory index populated at end of sync
- Update tasks.md 4.1/5.2 to reflect end-of-sync index behavior
- Update audit.md timestamp to 2026-09-18
- Update proposal.md consumer reference from "future" to actual plugin name
- Add test: empty registry commits full mutation with 0 entities
- Add test: applyMutation throw does not update lastGoodIndex
- Add test: degraded entities excluded from lastGoodIndex on subsequent syncs

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix(redhat-developer#4815): address review comments for package.json

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* fix(redhat-developer#4815): expand more scripts in package.json files

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* fix(redhat-developer#4815): add standalone dev entry for mcp-registry-provider

Enable yarn start for the catalog module by adding a local backend
entrypoint and the catalog/backend-defaults start dependencies.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): add staging MCP Registry provider config

Point local plugin and workspace app configs at the staging registry
so the catalog provider can sync when started standalone or from the workspace.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): list ingested mcp-server APIs in the catalog

Start the refresh after the catalog connection exists, stamp the origin
location annotation, and load the ai-model catalog module so Backstage
accepts spec.type mcp-server entities that omit spec.definition.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): record supertest in the workspace lockfile

Keep the lockfile aligned with the integration test devDependencies.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): address SonarCloud feedback on mcp-registry-provider

Split high-complexity provider, config, and client paths into helpers,
replace trailing-slash regex stripping with a linear util, and add
focused unit coverage for the extracted pieces.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(redhat-developer#4815): add mapping-common changeset for provider link

Record a patch release note for documenting consumption by the MCP
Registry provider and listing it in pluginPackages.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): address review feedback on PR redhat-developer#4871

- Move fetchApi test seam out of public constructor into an internal
  options bag; run() marked @internal — both removed from public API
  surface (report.api.md)
- Add TSDoc to all McpRegistryProviderConfig fields
- Change rebuildLastGoodIndex filter from === 'degraded' to !== 'ok'
  for forward-compatible spec alignment
- Add configurable maxEntries cap (default 5000) to abort sync when
  accumulated entries exceed the threshold, guarding against oversized
  registry pages
- Rename provider.ts to McpRegistryEntityProvider.ts to match workspace
  naming conventions (class-based filenames)
- Standardize package.json scripts: lint:check/lint:fix to lint to match
  sibling catalog-backend-module-* plugins
- Mark all tasks in tasks.md as completed
- Update audit.md timestamp to 2026-09-19
- Update stale future ingestion references in mcp-registry-server-mapping
  design.md to reference the now-implemented provider
- Add maxEntries config field to config.d.ts with @visibility backend
- Add tests for maxEntries config parsing and client enforcement
- Regenerate report.api.md

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix(redhat-developer#4815): restore lint:check/lint:fix/tsc/prettier scripts in package.json

Bring back lint:check, lint:fix, tsc, prettier:check, and prettier:fix
scripts in both catalog-backend-module-mcp-registry-provider and
mcp-registry-server-mapping-common package.json files, while keeping
the standard lint script for workspace compatibility with other plugins.

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix(redhat-developer#4815): align package.json scripts with workspace convention

Remove lint:check, lint:fix, tsc, prettier:check, and prettier:fix
scripts from both mcp-registry-provider and mcp-registry-server-
mapping-common package.json files to match the standard scripts
block used by all other catalog-backend-module-* plugins in the
ai-integrations workspace.

Update mcp-registry-server-mapping audit.md timestamp to reflect
the proposal.md and design.md changes made in this PR.

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* revert: undo package.json script removal from b255a04

Revert commit b255a04 which removed lint:check, lint:fix, tsc,
prettier:check, and prettier:fix scripts from both package.json files.
Restores audit.md timestamp to its pre-b255a04 value.

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix(redhat-developer#4815): add optional hostAllowList config for SSRF defense-in-depth

Add an optional hostAllowList config field under
catalog.providers.mcpRegistry that restricts outbound requests to
explicitly permitted hostnames. When configured:

- Config parsing validates that the baseUrl hostname is in the list
- The registry client validates the endpoint hostname at runtime

Hostnames are normalized to lowercase for case-insensitive matching.
When omitted, all hosts are allowed (backward-compatible).

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix(redhat-developer#4815): add recent config fields to app-config.yaml files

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* fix(redhat-developer#4815): use registry baseUrl as the placeholder remote

Servers with no valid remotes need a catalog remote that points at the
configured MCP Registry, before falling back to websiteUrl.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(redhat-developer#4815): add non-remote instruction to README

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* docs(redhat-developer#4815): document maxEntries in the provider README

Surface the existing sync entry cap in the configuration example and
options table so operators can find it alongside the other settings.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): resume pageLimit and soft-stop at maxEntries

Large registries can span multiple sync ticks via a saved resume
cursor, and hitting maxEntries now commits the buffer with an end
cursor instead of aborting the run.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): add remotesOnly to skip non-remote MCP servers

Operators can opt in to ingest only servers with a native remote so
package-only and placeholder-remote entries never enter the catalog.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(redhat-developer#4815): rename mapping package to catalog-mcp-registry-server-mapping

Align the mapping library directory, package name, and pluginId with
workspace conventions, and set the provider module pluginId to catalog.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): snapshot seenCursors on error and validate response.url

Snapshot seenCursors before calling fetchRegistryServers and restore
the snapshot on McpRegistryClientError so a failed sync does not carry
partially mutated cursor history into the next retry.

Validate response.url against the hostAllowList after each fetch
completes in fetchRegistryPage, preventing SSRF via HTTP redirects
to disallowed hosts.

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6 (anthropic)

* fix(redhat-developer#4815): address review feedback on PR redhat-developer#4871

- Treat explicit empty hostAllowList as 'deny all' by returning an empty
  array instead of undefined (readOptionalHostAllowList -> readHostAllowList)
- Move createMockLogger, createDefaultConfig, and mockFetchForResponses
  into src/testUtils.ts and import them in each test file
- Align host-validation naming: validateHostAgainstAllowList ->
  validateHostAllowList (config.ts), validateUrlHostAllowList ->
  validateHostAllowList (client.ts)
- Add 'yarn add' install command to README before backend.add code
- Reorder formatMappingFailureMessage fragments for natural grammar:
  'Failed to map MCP Registry server entry (version "1.0.0"): boom'

Addresses redhat-developer#4871

Assisted-by: Claude Opus 4.6

* fix(redhat-developer#4815): make defaulted provider config fields optional

Align McpRegistryProviderConfig with config parsing so apiVersion,
pageLimit, maxEntries, and remotesOnly can be omitted on direct
construction; the provider resolves the same defaults at runtime.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): follow redirects manually with Location validation

Use redirect: manual and validate each Location against http(s) and
hostAllowList before following, so SSRF via redirect cannot reach a
disallowed host.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(redhat-developer#4815): rename client host allowlist guard

Rename validateHostAllowList to assertRequestHostAllowed in the client
so the runtime request guard is distinct from config-time validation.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(redhat-developer#4815): use bare hostnames in hostAllowList examples

Align app-config examples with runtime hostname matching so operators
do not copy full URLs that would never pass the allowlist.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(redhat-developer#4815): extract provider helpers to providerUtils

Move hasNativeRemote, buildLastGoodKey, readServerIdentity, and
formatMappingFailureMessage out of the entity provider, and split their
unit tests into providerUtils.test.ts.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(redhat-developer#4815): return seenCursors instead of mutating options

Treat FetchServersOptions.seenCursors as read-only input and return the
updated set on FetchServersResult so callers replace state explicitly.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(redhat-developer#4815): lower fetchRegistryServers cognitive complexity

Split pagination into applyMaxEntriesSoftStop, advanceAfterResolvedCursor,
and isAtEndCursor helpers so SonarCloud cognitive complexity stays within
the allowed limit.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(redhat-developer#4815): extract McpRegistryEntityProviderOptions

Give the provider constructor options a named public interface so the
API surface is clearer and taskRunner is documented for callers.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): fail closed when response.url is missing

When hostAllowList is configured, require response.url on every fetch
and validate it against the allowlist so SSRF checks cannot be bypassed
by an opaque response.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): add local MCP Registry deploy tooling

Provide yarn start/stop scripts, Podman/Docker compose helpers with
custom seed mounts, example seed fixtures, and docs for developing the
mcp-registry-provider against a local registry without ko.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): wire mcp-registry-provider into workspace backend

Register the catalog MCP registry provider in the local backend so
yarn dev loads it with the rest of the catalog stack.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): make app-config changes

- Replace live staging MCP Registry URL with MCP_REGISTRY_URL env var (default: localhost:8080)
- Comment baseName out and make it library default (mcp.registry)
- Replace guest defaultOwner with OWNER env var (default: default-owner) to be consistent with workspace

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* chore(redhat-developer#4815): remove unused app-config from catalog-backend-module-mcp-registry-provider plugin directory

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* fix(redhat-developer#4815): wait for MCP Registry readiness before returning

Block start-mcp-registry until the HTTP API responds so yarn dev does
not race seed import and fail with fetch failed on the first sync.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): rename hack/ to scripts/ for workspace consistency

Align local MCP Registry tooling with the scripts/ convention used by
other workspaces and update yarn/docs references.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): move MCP registry examples and type docs to workspace

Relocate server-json fixtures under examples/mcp-registry and
server-json-types.md under docs/, and update mapping package links.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): harden MCP Registry scripts for SonarCloud

Resolve binaries from fixed directories and keep checkout/temp files
under ~/.cache instead of world-writable /tmp.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): pin MCP Registry checkout and image via env vars

Make clone URL, revision, path, image name, and image tag independently
configurable, defaulting the checkout and image to the 1.8.1 release.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): make MCP Registry readiness API version configurable

Expose MCP_REGISTRY_API_VERSION for the readiness probe path, defaulting
to v0.1.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): rename local MCP Registry scripts for clarity

Rename deploy/undeploy scripts and yarn targets to include "local" so
their purpose is clearer.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): add example mcp-server API entities from registry seed

Provide catalog YAML mirroring provider output for the sample seed data
and wire it as a local file location for review.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): nest MCP Registry provider config under reserved instance id

Move provider options to catalog.providers.mcpRegistry.mcpRegistry so the
top-level key is a map of instances, ignoring extra ids with a warning.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): clarify MCP Registry fetch error messages

Prefer the underlying cause message for network failures and avoid
embedding Error constructor names like TypeError in operator-facing logs.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): add 'example' tag to mcp-server examples

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* fix(redhat-developer#4815): satisfy tsc for nested mcpRegistry config tests

Type providersConfig as JsonObject and drop an unused import so
yarn tsc:full passes in CI.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): expand MCP Registry changesets for provider features

Document nested config, remotesOnly, hostAllowList, maxEntries, and
the mapping package rename for consumers.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): avoid nested template literals in deploy script

Extract the volume mount string before JSON.stringify to address
SonarCloud feedback.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): warn when hostAllowList is unset at startup

Log a defense-in-depth SSRF warning via the config warn sink when the
provider starts without a hostname allowlist.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(redhat-developer#4815): add defaultLifecycle to MCP Registry provider config

Pass an optional lifecycle override through to the mapping so operators
can align ingested mcp-server entities with other ai-integrations defaults.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(redhat-developer#4815): reorganize MCP Registry provider installation section

Split prerequisite, package install, and module registration so the
ai-model dependency is clear up front.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(redhat-developer#4815): retain degraded MCP servers across soft-stop syncs

Keep last-good entities outside the maxEntries window in the mutation
with degraded sync status, and re-index them until they are refreshed.

Assisted-by: grok-4.6
Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(redhat-developer#4815): add secret value to seed.json entry and server.json for testing secret redaction

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* chore(redhat-developer#4815): regenerate MCP Registry provider API report

Sort McpRegistryProviderConfig members to match API Extractor output.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* fix(redhat-developer#4815): address remaining PR redhat-developer#4871 review feedback

Align assertSingleRegistryConfig error keys with KNOWN_MCP_REGISTRY_KEYS,
use the short Backstage moduleId, document internal test seams, cover the
startCursor===endCursor soft-stop edge case, and note pagination reset
before applyMutation.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* chore(redhat-developer#4815): restore mcp-registry openspec docs to main

Drop branch-local edits under openspec/changes for mcp-registry-provider
and mcp-registry-server-mapping so this PR no longer changes those specs.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* feat(redhat-developer#4815): add latestVersion MCP Registry list query option

When enabled, list requests include ?version=latest so the registry
returns only the latest version of each server; default remains unset.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* docs(redhat-developer#4815): document using official MCP registries with the provider

Add a guide for pointing the MCP Registry provider at production or
staging official registries, and cross-link it from workspace and plugin
READMEs plus the local deploy doc.

Assisted-by: grok-4.6
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Michael Valdron <mvaldron@redhat.com>

* docs(redhat-developer#4815): self-revision on wording of official live production note

Signed-off-by: Michael Valdron <mvaldron@redhat.com>

---------

Signed-off-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com>
Co-authored-by: Michael Valdron <mvaldron@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review Triggers review agent dispatch risk/moderate PR risk: moderate workspace/ai-integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ai-integrations): implement MCP registry provider backend plugin - 1 / 1 (mcp-registry-provider)

4 participants