Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions workspaces/lightspeed/.changeset/famous-vans-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': minor
'@red-hat-developer-hub/backstage-plugin-lightspeed': minor
---

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.
1 change: 1 addition & 0 deletions workspaces/lightspeed/.prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dist
dist-dynamic
dist-types
coverage
.vscode
Expand Down
58 changes: 58 additions & 0 deletions workspaces/lightspeed/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ organization:

# Disable AI Notebooks feature by default
intelligent-assistant:
# MCP Servers available to the Intelligent Assistant via LCORE.
# Names must match the 'name' field in lightspeed-stack.yaml mcp_servers.
#
# Authentication modes per server:
# auth: dcr — Backstage mints a per-user token automatically (for
# Backstage-internal MCP servers with DCR enabled).
# The corresponding lightspeed-stack.yaml entry should use
# authorization_headers: { Authorization: "client" }.
# Any static 'token' field is ignored when auth: dcr is set.
#
# (no auth) — Static-token mode (legacy). Uses the 'token' field from
# this config, or the user's personal token set in the UI.
# The corresponding lightspeed-stack.yaml entry should use
# authorization_headers: { Authorization: "client" }.
#
# Example:
# mcpServers:
# - name: mcp-integration-tools
# auth: dcr
# - name: test-mcp-server
# token: ${MCP_TOKEN_1}
notebooks:
enabled: ${NOTEBOOKS_ENABLED:-false}
queryDefaults:
Expand All @@ -29,6 +50,16 @@ backend:
# auth:
# keys:
# - secret: ${BACKEND_SECRET}
#
# To expose MCP tools locally, uncomment the following. pluginSources lists
# the pluginIds whose actions are exposed as MCP tools. Requires
# @backstage/plugin-mcp-actions-backend and the mcp-extras plugins in index.ts.
# In production RHDH the overlay renames these to 'software-catalog-mcp-tool' etc.
# actions:
# pluginSources:
# - 'software-catalog-mcp-extras'
# - 'techdocs-mcp-extras'
# - 'scaffolder-mcp-extras'
baseUrl: http://localhost:7007
listen:
port: 7007
Expand Down Expand Up @@ -61,6 +92,12 @@ backend:
# password: ${POSTGRES_PASSWORD}
# workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir

# Disable dotted tool names (e.g. "software-catalog-mcp-extras.query-catalog-entities")
# so they pass OpenAI's strict tool name pattern ^[a-zA-Z0-9_-]+$ (dots are rejected).
# In production RHDH, add this under the backstage appConfig in values.yaml.
# mcpActions:
# namespacedToolNames: false

integrations:
github:
- host: github.com
Expand Down Expand Up @@ -93,6 +130,14 @@ techdocs:
auth:
# see https://backstage.io/docs/auth/ to learn about auth providers
environment: development
# Enable Dynamic Client Registration (DCR) for MCP.
# This allows MCP clients (e.g. Cursor, VS Code) to register OAuth2 clients
# dynamically and obtain per-user tokens via the Backstage auth system.
# Requires @backstage/plugin-auth (frontend) and @backstage/plugin-mcp-actions-backend.
# experimentalDynamicClientRegistration:
# enabled: true
# allowedRedirectUriPatterns:
# - '*'
providers:
# See https://backstage.io/docs/auth/guest/provider
guest: {}
Expand All @@ -108,6 +153,19 @@ auth:
# # 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
# dangerouslyAllowSignInWithoutUserInCatalog: true

# RBAC permission configuration.
# Enables fine-grained role-based access control for Lightspeed and catalog.
# Requires @backstage/plugin-permission-backend and @backstage-community/plugin-rbac-backend
# in packages/backend/src/index.ts, plus an rbac-policy.csv file.
# permission:
# enabled: true
# rbac:
# policies-csv-file: ../../rbac-policy.csv
# policyFileReload: true
# admin:
# superUsers:
# - name: user:default/breanna.davison

scaffolder: {}
# see https://backstage.io/docs/features/software-templates/configuration for software template options

Expand Down
6 changes: 5 additions & 1 deletion workspaces/lightspeed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@jest/environment-jsdom-abstract": "^30.3.0",
"@playwright/test": "1.60.0",
"@react-stately/layout": "^4.7.1",
"@react-stately/overlays": "^3.7.1",
"@react-types/table": "^3.14.0",
"@spotify/prettier-config": "^12.0.0",
"@types/jest": "^30.0.0",
"@types/jsdom": "^27.0.0",
Expand Down Expand Up @@ -82,7 +85,8 @@
"langsmith": "^0.6.0",
"react-router": "6.30.4",
"react-router-dom": "6.30.4",
"@remix-run/router": "1.23.3"
"@remix-run/router": "1.23.3",
"@patternfly/react-core": "6.4.3"
},
"prettier": "@spotify/prettier-config",
"lint-staged": {
Expand Down
1 change: 1 addition & 0 deletions workspaces/lightspeed/packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@backstage/frontend-plugin-api": "^0.15.1",
"@backstage/integration-react": "^1.2.16",
"@backstage/plugin-app-react": "^0.2.1",
"@backstage/plugin-auth": "^0.1.7",
"@backstage/plugin-catalog": "^2.0.1",
"@backstage/plugin-scaffolder": "^1.36.1",
"@backstage/plugin-search": "^1.7.0",
Expand Down
2 changes: 2 additions & 0 deletions workspaces/lightspeed/packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { createApp } from '@backstage/frontend-defaults';
import authPlugin from '@backstage/plugin-auth';
import { appDrawerModule } from '@red-hat-developer-hub/backstage-plugin-app-react/alpha';
import {
lightspeedFABModule,
Expand All @@ -28,6 +29,7 @@ import { signInModule } from './modules/signIn';

export default createApp({
features: [
authPlugin,
appDrawerModule,
lightspeedFABModule,
lightspeedRedirectModule,
Expand Down
3 changes: 2 additions & 1 deletion workspaces/lightspeed/packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ backend.add(
// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors
backend.add(import('@backstage/plugin-catalog-backend-module-logs'));

// permission plugin — RBAC backend v7+ no longer bundles this internally
// (breaking change from v5/v6 where it self-registered as the permission provider).
backend.add(import('@backstage/plugin-permission-backend'));

backend.add(import('@backstage-community/plugin-rbac-backend'));

// search plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export interface Config {
* @visibility secret
*/
token?: string;
/**
* Authentication mode for this MCP server.
* Set to 'dcr' for Dynamic Client Registration (user-bound tokens minted per-request).
Comment thread
maysunfaisal marked this conversation as resolved.
* When omitted, falls back to static-token mode.
* @visibility frontend
*/
auth?: 'dcr';
}>;
/**
* Per-user rate limiting for Lightspeed API endpoints.
Expand Down
72 changes: 38 additions & 34 deletions workspaces/lightspeed/plugins/lightspeed-backend/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import type { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import type { Config } from '@backstage/config';
import type { DatabaseService } from '@backstage/backend-plugin-api';
Expand All @@ -20,57 +20,61 @@ export function createRouter(options: RouterOptions): Promise<express.Router>;
const lightspeedPlugin: BackendFeature;
export default lightspeedPlugin;

// @public
export type McpServerAuth = 'dcr';

// @public
export interface McpServerResponse {
// (undocumented)
enabled: boolean;
// (undocumented)
hasToken: boolean;
// (undocumented)
hasUserToken: boolean;
// (undocumented)
name: string;
// (undocumented)
status: McpServerStatus;
// (undocumented)
toolCount: number;
// (undocumented)
url?: string;
auth?: McpServerAuth;
// (undocumented)
enabled: boolean;
// (undocumented)
hasToken: boolean;
// (undocumented)
hasUserToken: boolean;
// (undocumented)
name: string;
// (undocumented)
status: McpServerStatus;
// (undocumented)
toolCount: number;
// (undocumented)
url?: string;
}

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

// @public (undocumented)
export interface McpToolInfo {
// (undocumented)
description: string;
// (undocumented)
name: string;
// (undocumented)
description: string;
// (undocumented)
name: string;
}

// @public (undocumented)
export interface McpValidationResult {
// (undocumented)
error?: string;
// (undocumented)
toolCount: number;
// (undocumented)
tools: McpToolInfo[];
// (undocumented)
valid: boolean;
// (undocumented)
error?: string;
// (undocumented)
toolCount: number;
// (undocumented)
tools: McpToolInfo[];
// (undocumented)
valid: boolean;
}

// @public
export type RouterOptions = {
logger: LoggerService;
config: Config;
database: DatabaseService;
httpAuth: HttpAuthService;
userInfo: UserInfoService;
permissions: PermissionsService;
logger: LoggerService;
config: Config;
database: DatabaseService;
httpAuth: HttpAuthService;
userInfo: UserInfoService;
permissions: PermissionsService;
auth: AuthService;
};

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './service/router';

export type { RouterOptions } from './service/types';
export type {
McpServerAuth,
McpServerResponse,
McpServerStatus,
McpToolInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const lightspeedPlugin = createBackendPlugin({
config: coreServices.rootConfig,
http: coreServices.httpRouter,
httpAuth: coreServices.httpAuth,
auth: coreServices.auth,
userInfo: coreServices.userInfo,
permissions: coreServices.permissions,
database: coreServices.database,
Expand All @@ -45,6 +46,7 @@ export const lightspeedPlugin = createBackendPlugin({
config,
http,
httpAuth,
auth,
userInfo,
permissions,
database,
Expand Down Expand Up @@ -103,6 +105,7 @@ export const lightspeedPlugin = createBackendPlugin({
logger,
database,
httpAuth,
Comment thread
maysunfaisal marked this conversation as resolved.
auth,
userInfo,
permissions,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export interface McpUserSettingsRow {
*/
export type McpServerStatus = 'connected' | 'error' | 'unknown';

/**
* Authentication mode for MCP servers.
* @public
*/
export type McpServerAuth = 'dcr';

/**
* Public-facing response for an MCP server with user settings merged.
* @public
Expand All @@ -44,6 +50,8 @@ export interface McpServerResponse {
toolCount: number;
hasToken: boolean;
hasUserToken: boolean;
/** Authentication mode — `'dcr'` means tokens are minted automatically. */
auth?: McpServerAuth;
}

/**
Expand Down
Loading
Loading