Skip to content

feat(lightspeed): add DCR authentication support for MCP servers#3347

Open
maysunfaisal wants to merge 5 commits into
redhat-developer:mainfrom
maysunfaisal:RHDHPLAN-390-1
Open

feat(lightspeed): add DCR authentication support for MCP servers#3347
maysunfaisal wants to merge 5 commits into
redhat-developer:mainfrom
maysunfaisal:RHDHPLAN-390-1

Conversation

@maysunfaisal

@maysunfaisal maysunfaisal commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Hey, I just made a Pull Request!

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)
    • See screenshots below

Issue

https://redhat.atlassian.net/browse/RHIDP-11657

Summary

Adds Dynamic Client Registration (DCR) authentication for MCP servers in Lightspeed, allowing the backend to mint per-user plugin request tokens instead of requiring static tokens.

What changed

Backend (lightspeed-backend)

  • New auth: dcr config option for MCP servers — when set, the backend mints a Backstage service token on behalf of the user via getPluginRequestToken
  • Added AuthService injection into the router
  • GET /mcp-servers and PATCH /mcp-servers/:name responses now include the auth field
  • DCR servers report hasToken: true always (tokens are minted per-request, no manual entry needed)
  • buildMcpHeaders sends the minted token in MCP-HEADERS for DCR servers

Frontend (lightspeed)

  • MCP Settings modal shows a read-only message for DCR servers explaining tokens are automatic
  • New translation strings for DCR status/description

Bug fixes along the way

  • Fixed kebab menu not auto-closing after clicking "MCP Settings" — caused by duplicate @patternfly/react-core versions (added resolution to force 6.4.3)
  • Removed stale attachButtonPosition prop that no longer exists in @patternfly/chatbot
  • Fixed RBAC backend v7+ requiring @backstage/plugin-permission-backend registered separately (breaking change from v5/v6)

Other

  • Added @backstage/plugin-auth to frontend for DCR OAuth2 consent page
  • Added dist-dynamic to .prettierignore
  • Reference rbac-policy.csv with Lightspeed permissions for local RBAC testing
  • Updated API reports

How to test

  1. Unit testscd workspaces/lightspeed && yarn test:all (769 tests pass)
  2. Local DCR testing — Requires:
    • @backstage/plugin-mcp-actions-backend added to index.ts
    • experimentalDynamicClientRegistration.enabled: true in app-config.yaml
    • mcpServers: [{ name: "...", auth: dcr }] in lightspeed config
    • An MCP client (Cursor/VS Code) connecting to http://localhost:7007/api/mcp-actions/v1 — DCR OAuth consent should pop up in browser
  3. OpenShift — Deploy custom OCI images with DCR changes; set auth: dcr in values.yaml under lightspeed.mcpServers

Config example

lightspeed:
  mcpServers:
    - name: mcp-integration-tools
      auth: dcr          # tokens minted automatically
    - name: legacy-server
      token: ${TOKEN}    # static token (existing behavior)

@rhdh-gh-app

rhdh-gh-app Bot commented Jun 9, 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
app workspaces/lightspeed/packages/app none v0.0.27
backend workspaces/lightspeed/packages/backend none v0.0.59
@red-hat-developer-hub/backstage-plugin-lightspeed-backend workspaces/lightspeed/plugins/lightspeed-backend minor v2.9.1
@red-hat-developer-hub/backstage-plugin-lightspeed workspaces/lightspeed/plugins/lightspeed minor v2.9.1

@gabemontero gabemontero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hey @maysunfaisal - so claude and I looked at two error scenarios:

  1. If the user's RBAC policy denies a specific tool invocation

for this, claude's analysis was that the rejection happens inside @backstage/plugin-mcp-actions-backend, not in this code. Lightspeed streams back whatever LCS returns — so the user would see whatever error or refusal the LCS/MCP Actions layer produces in the chat stream. The Lightspeed backend is fully opaque to per-tool authorization outcomes.

Assuming this is correct, I'm curious if it makes sense to catch the LCS/MCP actions layer error and display a potentially more user friendly indication of permission issues.

WDYT?

  1. Unexpected error minting a Backstage token (getPluginRequestToken throws)

Claude claims the entire chat query fails (I'll post the details in a moment). It then goes on that is a chat query results in querying multiple MCP servers, none of those servers gets called.

I suppose a user prompt could result in multiple queries, but even if so, it seems unpredictable whether calling the other mcp servers is productive i.e. how dependent would the output of the backstage mcp server be.

I'm inclined to leave this be, but thought I would surface it in the review as due diligence to get your and everyone else's take.

Here is the code analysis from claude on gitPluginRequestToken

  There are two call sites for getPluginRequestToken, and neither has a local try/catch around it:

  A) During /v1/query (chat flow) — buildMcpHeaders() calls getPluginRequestToken at ~line 133. If it throws, the exception propagates unhandled out of buildMcpHeaders and is caught by the outer catch in the /v1/query handler (~line 775):

  } catch (error) {
      const errormsg = `Error fetching completions from ${provider}: ${error}`;
      logger.error(errormsg);
      response.status(500).json({ error: errormsg });
  }

  The entire chat query fails with a 500. Even if only one of several MCP servers uses auth: dcr, a token-minting failure for that one server aborts the whole request — the error is not isolated per-server. The other MCP servers (static-token or other DCR servers) never get their headers built.

  B) During POST /mcp-servers/:name/validate (~line 393) — same pattern: no local catch, the outer handler returns 500 with "Validation failed".

  Note: there is a guard for when authService/credentials are absent (logs a warning, skips the server), but that only handles the case where the services weren't injected — not a runtime failure in getPluginRequestToken itself.

@maysunfaisal maysunfaisal force-pushed the RHDHPLAN-390-1 branch 4 times, most recently from 763129e to 077a7a0 Compare June 10, 2026 21:22
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 47.16981% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.28%. Comparing base (d903ed7) to head (52739c2).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3347   +/-   ##
=======================================
  Coverage   54.28%   54.28%           
=======================================
  Files        2345     2345           
  Lines       89626    89648   +22     
  Branches    25111    25106    -5     
=======================================
+ Hits        48650    48665   +15     
- Misses      40673    40680    +7     
  Partials      303      303           
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 4d83df4
ai-integrations 67.95% <ø> (ø) Carriedforward from 4d83df4
app-defaults 69.79% <ø> (ø) Carriedforward from 4d83df4
augment 46.39% <ø> (ø) Carriedforward from 4d83df4
boost 74.68% <ø> (ø) Carriedforward from 4d83df4
bulk-import 72.46% <ø> (ø) Carriedforward from 4d83df4
cost-management 14.10% <ø> (ø) Carriedforward from 4d83df4
dcm 61.81% <ø> (ø) Carriedforward from 4d83df4
extensions 61.53% <ø> (ø) Carriedforward from 4d83df4
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 4d83df4
global-header 59.71% <ø> (ø) Carriedforward from 4d83df4
homepage 49.92% <ø> (ø) Carriedforward from 4d83df4
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from 4d83df4
konflux 91.49% <ø> (ø) Carriedforward from 4d83df4
lightspeed 68.81% <47.16%> (-0.01%) ⬇️
mcp-integrations 85.46% <ø> (ø) Carriedforward from 4d83df4
orchestrator 39.51% <ø> (ø) Carriedforward from 4d83df4
quickstart 65.63% <ø> (ø) Carriedforward from 4d83df4
sandbox 79.56% <ø> (ø) Carriedforward from 4d83df4
scorecard 82.67% <ø> (ø) Carriedforward from 4d83df4
theme 61.26% <ø> (ø) Carriedforward from 4d83df4
translations 7.25% <ø> (ø) Carriedforward from 4d83df4
x2a 78.68% <ø> (ø) Carriedforward from 4d83df4

*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 d903ed7...52739c2. 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.

Comment thread workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts Outdated
Comment thread workspaces/lightspeed/plugins/lightspeed-backend/src/service/mcp-server-types.ts Outdated
Comment thread workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts Outdated
Comment thread workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts Outdated
Comment thread workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts Outdated
@github-actions

This comment was marked as off-topic.

@maysunfaisal maysunfaisal force-pushed the RHDHPLAN-390-1 branch 2 times, most recently from 3b2db5a to 5f5b935 Compare June 15, 2026 22:19
@maysunfaisal

maysunfaisal commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

hey @maysunfaisal - so claude and I looked at two error scenarios:

  1. If the user's RBAC policy denies a specific tool invocation

for this, claude's analysis was that the rejection happens inside @backstage/plugin-mcp-actions-backend, not in this code. Lightspeed streams back whatever LCS returns — so the user would see whatever error or refusal the LCS/MCP Actions layer produces in the chat stream. The Lightspeed backend is fully opaque to per-tool authorization outcomes.

Assuming this is correct, I'm curious if it makes sense to catch the LCS/MCP actions layer error and display a potentially more user friendly indication of permission issues.

WDYT?

  1. Unexpected error minting a Backstage token (getPluginRequestToken throws)

Claude claims the entire chat query fails (I'll post the details in a moment). It then goes on that is a chat query results in querying multiple MCP servers, none of those servers gets called.

I suppose a user prompt could result in multiple queries, but even if so, it seems unpredictable whether calling the other mcp servers is productive i.e. how dependent would the output of the backstage mcp server be.

I'm inclined to leave this be, but thought I would surface it in the review as due diligence to get your and everyone else's take.

Here is the code analysis from claude on gitPluginRequestToken

  There are two call sites for getPluginRequestToken, and neither has a local try/catch around it:

  A) During /v1/query (chat flow) — buildMcpHeaders() calls getPluginRequestToken at ~line 133. If it throws, the exception propagates unhandled out of buildMcpHeaders and is caught by the outer catch in the /v1/query handler (~line 775):

  } catch (error) {
      const errormsg = `Error fetching completions from ${provider}: ${error}`;
      logger.error(errormsg);
      response.status(500).json({ error: errormsg });
  }

  The entire chat query fails with a 500. Even if only one of several MCP servers uses auth: dcr, a token-minting failure for that one server aborts the whole request — the error is not isolated per-server. The other MCP servers (static-token or other DCR servers) never get their headers built.

  B) During POST /mcp-servers/:name/validate (~line 393) — same pattern: no local catch, the outer handler returns 500 with "Validation failed".

  Note: there is a guard for when authService/credentials are absent (logs a warning, skips the server), but that only handles the case where the services weren't injected — not a runtime failure in getPluginRequestToken itself.

@gabemontero Thanks for flagging this. You're correct, RBAC tool-level denials happen inside @backstage/plugin-mcp-actions-backend, and Lightspeed is opaque to those outcomes; it streams back whatever LCS returns. Surfacing a user-friendly permission error would require parsing the LCS response stream for authorization-specific error patterns and rendering them differently in the chat UI and that touches the frontend streaming logic and response parsing, which is a bigger scope than this PR. I'll capture it as a follow-up enhancement (parsing MCP/RBAC errors from LCS and rendering actionable messages in the Lightspeed chat). For now, the user will see whatever LCS surfaces in the streamed response.

Addressed your second concern

@gabemontero

Copy link
Copy Markdown
Contributor

@gabemontero Thanks for flagging this. You're correct, RBAC tool-level denials happen inside @backstage/plugin-mcp-actions-backend, and Lightspeed is opaque to those outcomes; it streams back whatever LCS returns. Surfacing a user-friendly permission error would require parsing the LCS response stream for authorization-specific error patterns and rendering them differently in the chat UI and that touches the frontend streaming logic and response parsing, which is a bigger scope than this PR. I'll capture it as a follow-up enhancement (parsing MCP/RBAC errors from LCS and rendering actionable messages in the Lightspeed chat). For now, the user will see whatever LCS surfaces in the streamed response.

Addressed your second concern

sounds good @maysunfaisal thanks

@maysunfaisal

Copy link
Copy Markdown
Contributor Author

@gabemontero Here is the issue to address surfacing user friendly MCP permission denial msgs https://redhat.atlassian.net/browse/RHIDP-14907

@maysunfaisal

maysunfaisal commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Screenshots - Local

RBAC Example - catalog.example.read = deny

rbac policy file

Screenshot 2026-06-08 at 5 08 32 PM

Lightspeed (prior to rebrand) interacting with Backstage/RHDH MCP with DCR - does not return Catalog resources

Screenshot 2026-06-08 at 5 06 47 PM Screenshot 2026-06-08 at 5 08 21 PM

MCP Settings for DCR auth server does not let you edit token from the UI

Screenshot 2026-06-08 at 5 06 33 PM

Cursor MCP Settings - backstage-actions pointing to Backstage/RHDH MCP with DCR

Screenshot 2026-06-08 at 5 11 01 PM

Redirected to Backstage/RHDH for DCR auth and successful auth

Screenshot 2026-06-08 at 5 10 48 PM

Cursor MCP backstage-actions now shows a successful connection

Screenshot 2026-06-08 at 5 11 17 PM Screenshot 2026-06-08 at 5 11 11 PM

Query the Backstage/RHDH Catalog, denied due to rbac policy

Screenshot 2026-06-08 at 5 12 19 PM

RBAC Example - catalog.example.read = allow

rbac policy file

Screenshot 2026-06-08 at 5 13 38 PM

Lightspeed query now passes for the same prompt using MCP with DCR

Screenshot 2026-06-08 at 5 13 44 PM

Cursor query now passes for the same prompt using MCP with DCR

Screenshot 2026-06-08 at 5 14 27 PM

@yangcao77 yangcao77 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

generally lgtm

@maysunfaisal

Copy link
Copy Markdown
Contributor Author

@redhat-developer/rhdh-ui @redhat-developer/rhdh-plugins-maintainers Could you PTAL at this PR? Thanks!

@HusneShabbir HusneShabbir 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.

cc: @ciiay

@HusneShabbir HusneShabbir 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.

maysunfaisal#1 covers the E2E tests for the MCP–DCR integration. One observation from the testing is that the new DCR alert is not fully localized yet.

Image

'mcp.settings.status.unknown': 'Unknown',
'mcp.settings.modalDescriptionDcr':
'This server uses Dynamic Client Registration (DCR). Tokens are minted automatically using your Backstage identity — no manual token is needed.',
'mcp.settings.toggleServerAriaLabel': 'Toggle {{serverName}}',

@its-mitesh-kumar its-mitesh-kumar Jun 25, 2026

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.

The new mcp.settings.modalDescriptionDcr key is only added to the English reference file (ref.ts). Please add the corresponding translated strings in all other supported language files as well:

  • de.ts (German)
  • es.ts (Spanish)
  • fr.ts (French)
  • it.ts (Italian)
  • ja.ts (Japanese)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@its-mitesh-kumar how would one acquire the translation for the other languages? Is there a process?

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.

Please do add these strings in other language(can be generated through claude/cursor). Later UI team sent it to TMS(translation team) for verification. And if required we do update it.

Language File Translated String
German de.ts Dieser Server verwendet Dynamic Client Registration (DCR). Token werden automatisch mit Ihrer Backstage-Identität erstellt — kein manuelles Token erforderlich.
Spanish es.ts Este servidor utiliza Dynamic Client Registration (DCR). Los tokens se generan automáticamente usando tu identidad de Backstage — no se necesita un token manual.
French fr.ts Ce serveur utilise Dynamic Client Registration (DCR). Les jetons sont générés automatiquement à partir de votre identité Backstage — aucun jeton manuel n'est nécessaire.
Italian it.ts Questo server utilizza Dynamic Client Registration (DCR). I token vengono generati automaticamente utilizzando la tua identità Backstage — non è necessario alcun token manuale.
Japanese ja.ts このサーバーは Dynamic Client Registration (DCR) を使用しています。トークンはあなたの Backstage ID を使用して自動的に発行されるため、手動でのトークン入力は不要です。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@its-mitesh-kumar thanks for the explanation!

@HusneShabbir HusneShabbir 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.

/lgtm

maysunfaisal and others added 4 commits July 2, 2026 16:46
Co-authored-by: Cursor <cursoragent@cursor.com>
- Wrap getPluginRequestToken in per-server try/catch so one failing DCR
  server does not break all MCP integration
- Return 502 with clear error on token mint failure in /validate endpoint
- Bundle authService+credentials into dcrAuth object to prevent partial
  provision at compile time
- Export McpServerAuth type and use it instead of raw string
- Remove redundant per-request warning for dual auth+token config

Co-authored-by: Cursor <cursoragent@cursor.com>
- Remove duplicate migrate(database) call in plugin.ts
- Remove unused 'mcp.settings.status.autoManaged' translation key
- Regenerate API report

Co-authored-by: Cursor <cursoragent@cursor.com>
…me in tests

Co-authored-by: Cursor <cursoragent@cursor.com>
@openshift-ci

openshift-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

New changes are detected. LGTM label has been removed.

@its-mitesh-kumar

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:29 PM UTC · Completed 11:42 PM UTC
Commit: d903ed7 · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

Medium

  • [api-contract] workspaces/lightspeed/plugins/lightspeed-backend/src/plugin.tsRouterOptions.auth is now a required field (auth: AuthService), which is a breaking change for any external consumer of createRouter() that does not pass it. Within the Backstage plugin system, coreServices.auth is always injected by the framework, so internal usage is safe. However, JavaScript consumers bypassing TypeScript checks could hit a runtime TypeError. The changeset declares a minor version bump, which may be insufficient for a breaking API change.
    Remediation: Consider making auth optional (auth?: AuthService) with a runtime guard, or document as a breaking change.

  • [test-inadequate] workspaces/lightspeed/plugins/lightspeed-backend/src/service/mcp-server.test.ts — The new DCR tests cover GET /mcp-servers (auth field presence) and the /v1/query flow (token minting and mixed DCR/static headers). However, two code paths remain untested: (1) POST /mcp-servers/:name/validate with a DCR-configured server, and (2) the PATCH /mcp-servers/:name endpoint's 502 error path when auth.getPluginRequestToken throws.
    Remediation: Add tests for the validate endpoint's DCR path and the PATCH 502 error path.

Low

  • [edge-case] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts — In buildMcpHeaders, when a DCR server is configured but dcrAuth is unavailable, the function logs a warning and silently skips the server. The /v1/query request proceeds without that server's authorization header, and LCORE will likely return a 401. In practice, this path only triggers if createRouter is used by an external caller without the auth service.

  • [api-contract] workspaces/lightspeed/plugins/lightspeed-backend/config.d.ts — The auth field is typed as string rather than the literal type 'dcr'. While runtime validation exists (unsupported values trigger a warning and fall back to static-token mode), the schema could be tightened for better developer experience.

  • [data-exposure] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts — The auth field is returned in the GET /mcp-servers API response, but config.d.ts marks it with @visibility backend. The field value is non-sensitive (just the string 'dcr' or undefined) and is needed by the frontend to render the correct modal UI. The @visibility annotation should be updated to frontend for consistency.

  • [authorization] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.tstargetPluginId is hardcoded to 'mcp-actions' for all DCR servers. This is by design — DCR mode targets Backstage-internal MCP servers that all route through the mcp-actions backend plugin, which enforces per-resource permissions via RBAC. A documentation note clarifying this design decision would be helpful.

  • [input-validation] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts — The auth config value is checked via strict equality (authValue === 'dcr'). A typo like 'DCR' or 'dcr ' falls back to static-token mode with a warning log, which is reasonable. Case-insensitive comparison would be a minor UX improvement.


Labels: PR adds a new DCR authentication feature for MCP servers with security-relevant auth changes and test additions.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread workspaces/lightspeed/plugins/lightspeed-backend/src/plugin.ts
Comment thread workspaces/lightspeed/plugins/lightspeed-backend/config.d.ts
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment enhancement New feature or request labels Jul 2, 2026
- Tighten config.d.ts auth type from string to literal 'dcr'
- Fix @visibility annotation to 'frontend' (field is used by UI)
- Add case-insensitive comparison for auth config value
- Add test for DCR validate endpoint path

Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

@maysunfaisal

Copy link
Copy Markdown
Contributor Author

Review

Findings

Medium

  • [api-contract] workspaces/lightspeed/plugins/lightspeed-backend/src/plugin.tsRouterOptions.auth is now a required field (auth: AuthService), which is a breaking change for any external consumer of createRouter() that does not pass it. Within the Backstage plugin system, coreServices.auth is always injected by the framework, so internal usage is safe. However, JavaScript consumers bypassing TypeScript checks could hit a runtime TypeError. The changeset declares a minor version bump, which may be insufficient for a breaking API change.
    Remediation: Consider making auth optional (auth?: AuthService) with a runtime guard, or document as a breaking change.

This is an internal plugin — coreServices.auth is always injected by the Backstage plugin framework. Making it optional would add unnecessary null checks for a code path that can't occur in practice.

  • [test-inadequate] workspaces/lightspeed/plugins/lightspeed-backend/src/service/mcp-server.test.ts — The new DCR tests cover GET /mcp-servers (auth field presence) and the /v1/query flow (token minting and mixed DCR/static headers). However, two code paths remain untested: (1) POST /mcp-servers/:name/validate with a DCR-configured server, and (2) the PATCH /mcp-servers/:name endpoint's 502 error path when auth.getPluginRequestToken throws.
    Remediation: Add tests for the validate endpoint's DCR path and the PATCH 502 error path.

Partially addressed. Added test for the DCR validate endpoint path. The 502 error case (auth service throws) requires overriding coreServices.auth mock internals which is non-trivial with the current test infrastructure.

Low

  • [edge-case] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts — In buildMcpHeaders, when a DCR server is configured but dcrAuth is unavailable, the function logs a warning and silently skips the server. The /v1/query request proceeds without that server's authorization header, and LCORE will likely return a 401. In practice, this path only triggers if createRouter is used by an external caller without the auth service.

Intentional design — graceful degradation. The warning log is sufficient; crashing the entire request when one server's auth is unavailable would be worse UX.

  • [api-contract] workspaces/lightspeed/plugins/lightspeed-backend/config.d.ts — The auth field is typed as string rather than the literal type 'dcr'. While runtime validation exists (unsupported values trigger a warning and fall back to static-token mode), the schema could be tightened for better developer experience.

Addressed. Changed type from string to 'dcr'.

  • [data-exposure] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts — The auth field is returned in the GET /mcp-servers API response, but config.d.ts marks it with @visibility backend. The field value is non-sensitive (just the string 'dcr' or undefined) and is needed by the frontend to render the correct modal UI. The @visibility annotation should be updated to frontend for consistency.

Addressed. Changed @visibility backend to @visibility frontend.

  • [authorization] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.tstargetPluginId is hardcoded to 'mcp-actions' for all DCR servers. This is by design — DCR mode targets Backstage-internal MCP servers that all route through the mcp-actions backend plugin, which enforces per-resource permissions via RBAC. A documentation note clarifying this design decision would be helpful.

By design — all DCR-enabled MCP servers in RHDH are proxied through @backstage/plugin-mcp-actions-backend, so the target is always mcp-actions.

  • [input-validation] workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts — The auth config value is checked via strict equality (authValue === 'dcr'). A typo like 'DCR' or 'dcr ' falls back to static-token mode with a warning log, which is reasonable. Case-insensitive comparison would be a minor UX improvement.

Addressed. Added .toLowerCase() so DCR, Dcr, etc. all work.

Labels: PR adds a new DCR authentication feature for MCP servers with security-relevant auth changes and test additions.

@maysunfaisal

Copy link
Copy Markdown
Contributor Author

@its-mitesh-kumar Addressed the fullsend reviews

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request requires-manual-review Review requires human judgment workspace/lightspeed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants