Skip to content

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

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

feat(lightspeed): add DCR authentication support for MCP servers#3347
maysunfaisal wants to merge 1 commit 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)

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)

Co-authored-by: Cursor <cursoragent@cursor.com>
@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.26
backend workspaces/lightspeed/packages/backend none v0.0.58
@red-hat-developer-hub/backstage-plugin-lightspeed-backend workspaces/lightspeed/plugins/lightspeed-backend minor v2.9.0
@red-hat-developer-hub/backstage-plugin-lightspeed workspaces/lightspeed/plugins/lightspeed minor v2.9.0

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants