Skip to content

Commit 4b468a5

Browse files
feat(lightspeed): add DCR authentication support for MCP servers
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3ad2ec3 commit 4b468a5

22 files changed

Lines changed: 1823 additions & 2758 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': minor
3+
'@red-hat-developer-hub/backstage-plugin-lightspeed': minor
4+
---
5+
6+
Add DCR (Dynamic Client Registration) authentication support for Backstage-internal MCP servers, allowing the Lightspeed backend to mint per-user plugin request tokens instead of requiring static tokens.

workspaces/lightspeed/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
dist
2+
dist-dynamic
23
dist-types
34
coverage
45
.vscode

workspaces/lightspeed/app-config.yaml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ organization:
1818

1919
# Disable AI Notebooks feature by default
2020
lightspeed:
21+
# MCP Servers available to the Lightspeed assistant via LCORE.
22+
# Names must match the 'name' field in lightspeed-stack.yaml mcp_servers.
23+
#
24+
# Authentication modes per server:
25+
# auth: dcr — Backstage mints a per-user token automatically (for
26+
# Backstage-internal MCP servers with DCR enabled).
27+
# The corresponding lightspeed-stack.yaml entry should use
28+
# authorization_headers: { Authorization: "client" }.
29+
# Any static 'token' field is ignored when auth: dcr is set.
30+
#
31+
# (no auth) — Static-token mode (legacy). Uses the 'token' field from
32+
# this config, or the user's personal token set in the UI.
33+
# The corresponding lightspeed-stack.yaml entry should use
34+
# authorization_headers: { Authorization: "client" }.
35+
#
36+
# Example:
37+
# mcpServers:
38+
# - name: mcp-integration-tools
39+
# auth: dcr
40+
# - name: test-mcp-server
41+
# token: ${MCP_TOKEN_1}
2142
notebooks:
2243
enabled: ${NOTEBOOKS_ENABLED:-false}
2344
queryDefaults:
@@ -31,6 +52,16 @@ backend:
3152
# auth:
3253
# keys:
3354
# - secret: ${BACKEND_SECRET}
55+
#
56+
# To expose MCP tools locally, uncomment the following. pluginSources lists
57+
# the pluginIds whose actions are exposed as MCP tools. Requires
58+
# @backstage/plugin-mcp-actions-backend and the mcp-extras plugins in index.ts.
59+
# In production RHDH the overlay renames these to 'software-catalog-mcp-tool' etc.
60+
# actions:
61+
# pluginSources:
62+
# - 'software-catalog-mcp-extras'
63+
# - 'techdocs-mcp-extras'
64+
# - 'scaffolder-mcp-extras'
3465
baseUrl: http://localhost:7007
3566
listen:
3667
port: 7007
@@ -63,6 +94,12 @@ backend:
6394
# password: ${POSTGRES_PASSWORD}
6495
# workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
6596

97+
# Disable dotted tool names (e.g. "software-catalog-mcp-extras.query-catalog-entities")
98+
# so they pass OpenAI's strict tool name pattern ^[a-zA-Z0-9_-]+$ (dots are rejected).
99+
# In production RHDH, add this under the backstage appConfig in values.yaml.
100+
# mcpActions:
101+
# namespacedToolNames: false
102+
66103
integrations:
67104
github:
68105
- host: github.com
@@ -95,6 +132,14 @@ techdocs:
95132
auth:
96133
# see https://backstage.io/docs/auth/ to learn about auth providers
97134
environment: development
135+
# Enable Dynamic Client Registration (DCR) for MCP.
136+
# This allows MCP clients (e.g. Cursor, VS Code) to register OAuth2 clients
137+
# dynamically and obtain per-user tokens via the Backstage auth system.
138+
# Requires @backstage/plugin-auth (frontend) and @backstage/plugin-mcp-actions-backend.
139+
# experimentalDynamicClientRegistration:
140+
# enabled: true
141+
# allowedRedirectUriPatterns:
142+
# - '*'
98143
providers:
99144
# See https://backstage.io/docs/auth/guest/provider
100145
guest: {}
@@ -110,6 +155,19 @@ auth:
110155
# # Since we do not have a User entity, for local development, uncomment the following line to allow sign-in without a user in the catalog
111156
# dangerouslyAllowSignInWithoutUserInCatalog: true
112157

158+
# RBAC permission configuration.
159+
# Enables fine-grained role-based access control for Lightspeed and catalog.
160+
# Requires @backstage/plugin-permission-backend and @backstage-community/plugin-rbac-backend
161+
# in packages/backend/src/index.ts, plus an rbac-policy.csv file.
162+
# permission:
163+
# enabled: true
164+
# rbac:
165+
# policies-csv-file: ../../rbac-policy.csv
166+
# policyFileReload: true
167+
# admin:
168+
# superUsers:
169+
# - name: user:default/breanna.davison
170+
113171
scaffolder: {}
114172
# see https://backstage.io/docs/features/software-templates/configuration for software template options
115173

workspaces/lightspeed/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
5656
"@jest/environment-jsdom-abstract": "^30.3.0",
5757
"@playwright/test": "1.60.0",
58+
"@react-stately/layout": "^4.7.1",
59+
"@react-stately/overlays": "^3.7.1",
60+
"@react-types/table": "^3.14.0",
5861
"@spotify/prettier-config": "^12.0.0",
5962
"@types/jest": "^30.0.0",
6063
"@types/jsdom": "^27.0.0",
@@ -76,7 +79,8 @@
7679
"@backstage/frontend-plugin-api": "0.15.1",
7780
"infinispan": "0.13.0",
7881
"@protobufjs/inquire": "1.1.0",
79-
"langsmith": "^0.6.0"
82+
"langsmith": "^0.6.0",
83+
"@patternfly/react-core": "6.4.3"
8084
},
8185
"prettier": "@spotify/prettier-config",
8286
"lint-staged": {

workspaces/lightspeed/packages/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@backstage/frontend-plugin-api": "^0.15.1",
2828
"@backstage/integration-react": "^1.2.16",
2929
"@backstage/plugin-app-react": "^0.2.1",
30+
"@backstage/plugin-auth": "^0.1.7",
3031
"@backstage/plugin-catalog": "^2.0.1",
3132
"@backstage/plugin-scaffolder": "^1.36.1",
3233
"@backstage/plugin-search": "^1.7.0",

workspaces/lightspeed/packages/app/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import { createApp } from '@backstage/frontend-defaults';
18+
import authPlugin from '@backstage/plugin-auth';
1819
import { appDrawerModule } from '@red-hat-developer-hub/backstage-plugin-app-react/alpha';
1920
import {
2021
lightspeedFABModule,
@@ -26,6 +27,7 @@ import { signInModule } from './modules/signIn';
2627

2728
export default createApp({
2829
features: [
30+
authPlugin,
2931
appDrawerModule,
3032
lightspeedFABModule,
3133
lightspeedTranslationsModule,

workspaces/lightspeed/packages/backend/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ backend.add(
3838
// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors
3939
backend.add(import('@backstage/plugin-catalog-backend-module-logs'));
4040

41-
// RBAC backend (registers as "permission" and provides /rbac; do not add plugin-permission-backend separately)
41+
// permission plugin — RBAC backend v7+ no longer bundles this internally
42+
// (breaking change from v5/v6 where it self-registered as the permission provider).
43+
backend.add(import('@backstage/plugin-permission-backend'));
4244
backend.add(import('@backstage-community/plugin-rbac-backend'));
4345

4446
// search plugin

workspaces/lightspeed/plugins/lightspeed-backend/config.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export interface Config {
4848
* @visibility secret
4949
*/
5050
token?: string;
51+
/**
52+
* Authentication mode for this MCP server.
53+
* Set to 'dcr' for Dynamic Client Registration (user-bound tokens minted per-request).
54+
* When omitted, falls back to static-token mode.
55+
* @visibility backend
56+
*/
57+
auth?: string;
5158
}>;
5259
/**
5360
* Configuration for AI Notebooks (Developer Preview)

workspaces/lightspeed/plugins/lightspeed-backend/report.api.md

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
44
55
```ts
6-
6+
import type { AuthService } from '@backstage/backend-plugin-api';
77
import { BackendFeature } from '@backstage/backend-plugin-api';
88
import type { Config } from '@backstage/config';
99
import type { DatabaseService } from '@backstage/backend-plugin-api';
@@ -20,57 +20,61 @@ export function createRouter(options: RouterOptions): Promise<express.Router>;
2020
const lightspeedPlugin: BackendFeature;
2121
export default lightspeedPlugin;
2222

23+
// @public
24+
export type McpServerAuth = 'dcr';
25+
2326
// @public
2427
export interface McpServerResponse {
25-
// (undocumented)
26-
enabled: boolean;
27-
// (undocumented)
28-
hasToken: boolean;
29-
// (undocumented)
30-
hasUserToken: boolean;
31-
// (undocumented)
32-
name: string;
33-
// (undocumented)
34-
status: McpServerStatus;
35-
// (undocumented)
36-
toolCount: number;
37-
// (undocumented)
38-
url?: string;
28+
auth?: McpServerAuth;
29+
// (undocumented)
30+
enabled: boolean;
31+
// (undocumented)
32+
hasToken: boolean;
33+
// (undocumented)
34+
hasUserToken: boolean;
35+
// (undocumented)
36+
name: string;
37+
// (undocumented)
38+
status: McpServerStatus;
39+
// (undocumented)
40+
toolCount: number;
41+
// (undocumented)
42+
url?: string;
3943
}
4044

4145
// @public (undocumented)
4246
export type McpServerStatus = 'connected' | 'error' | 'unknown';
4347

4448
// @public (undocumented)
4549
export interface McpToolInfo {
46-
// (undocumented)
47-
description: string;
48-
// (undocumented)
49-
name: string;
50+
// (undocumented)
51+
description: string;
52+
// (undocumented)
53+
name: string;
5054
}
5155

5256
// @public (undocumented)
5357
export interface McpValidationResult {
54-
// (undocumented)
55-
error?: string;
56-
// (undocumented)
57-
toolCount: number;
58-
// (undocumented)
59-
tools: McpToolInfo[];
60-
// (undocumented)
61-
valid: boolean;
58+
// (undocumented)
59+
error?: string;
60+
// (undocumented)
61+
toolCount: number;
62+
// (undocumented)
63+
tools: McpToolInfo[];
64+
// (undocumented)
65+
valid: boolean;
6266
}
6367

6468
// @public
6569
export type RouterOptions = {
66-
logger: LoggerService;
67-
config: Config;
68-
database: DatabaseService;
69-
httpAuth: HttpAuthService;
70-
userInfo: UserInfoService;
71-
permissions: PermissionsService;
70+
logger: LoggerService;
71+
config: Config;
72+
database: DatabaseService;
73+
httpAuth: HttpAuthService;
74+
userInfo: UserInfoService;
75+
permissions: PermissionsService;
76+
auth: AuthService;
7277
};
7378

7479
// (No @packageDocumentation comment for this package)
75-
7680
```

workspaces/lightspeed/plugins/lightspeed-backend/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export * from './service/router';
1919

2020
export type { RouterOptions } from './service/types';
2121
export type {
22+
McpServerAuth,
2223
McpServerResponse,
2324
McpServerStatus,
2425
McpToolInfo,

0 commit comments

Comments
 (0)