Skip to content

Commit c826100

Browse files
feat(lightspeed): harden DCR token minting and improve type safety
- 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>
1 parent 1dab9e6 commit c826100

4 files changed

Lines changed: 43 additions & 24 deletions

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ 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-
auth?: string;
28+
auth?: McpServerAuth;
2629
// (undocumented)
2730
enabled: boolean;
2831
// (undocumented)

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,

workspaces/lightspeed/plugins/lightspeed-backend/src/service/mcp-server-types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export interface McpUserSettingsRow {
3232
*/
3333
export type McpServerStatus = 'connected' | 'error' | 'unknown';
3434

35+
/**
36+
* Authentication mode for MCP servers.
37+
* @public
38+
*/
39+
export type McpServerAuth = 'dcr';
40+
3541
/**
3642
* Public-facing response for an MCP server with user settings merged.
3743
* @public
@@ -45,7 +51,7 @@ export interface McpServerResponse {
4551
hasToken: boolean;
4652
hasUserToken: boolean;
4753
/** Authentication mode — `'dcr'` means tokens are minted automatically. */
48-
auth?: string;
54+
auth?: McpServerAuth;
4955
}
5056

5157
/**

workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
} from './constant';
4646
import { McpUserSettingsStore } from './mcp-server-store';
4747
import {
48+
McpServerAuth,
4849
McpServerResponse,
4950
McpServerStatus,
5051
McpValidationResult,
@@ -78,8 +79,6 @@ import { validateCompletionsRequest } from './validation';
7879
* app-config. If neither exists, the server is omitted from MCP-HEADERS.
7980
* Users can also set their own tokens via the Lightspeed UI.
8081
*/
81-
type McpServerAuth = 'dcr';
82-
8382
interface StaticMcpServer {
8483
name: string;
8584
token?: string;
@@ -106,8 +105,7 @@ async function buildMcpHeaders(
106105
store: McpUserSettingsStore,
107106
userEntityRef: string,
108107
options?: {
109-
authService?: AuthService;
110-
credentials?: BackstageCredentials;
108+
dcrAuth?: { authService: AuthService; credentials: BackstageCredentials };
111109
logger?: LoggerService;
112110
},
113111
): Promise<string> {
@@ -121,18 +119,19 @@ async function buildMcpHeaders(
121119
if (!enabled) continue;
122120

123121
if (server.auth === 'dcr') {
124-
if (server.token && options?.logger) {
125-
options.logger.warn(
126-
`MCP server '${server.name}' has both auth: dcr and a static token; ` +
127-
`the static token is ignored when auth is dcr`,
128-
);
129-
}
130-
if (options?.authService && options?.credentials) {
131-
const { token } = await options.authService.getPluginRequestToken({
132-
onBehalfOf: options.credentials,
133-
targetPluginId: 'mcp-actions',
134-
});
135-
headers[server.name] = { Authorization: `${token}` };
122+
if (options?.dcrAuth) {
123+
try {
124+
const { token } =
125+
await options.dcrAuth.authService.getPluginRequestToken({
126+
onBehalfOf: options.dcrAuth.credentials,
127+
targetPluginId: 'mcp-actions',
128+
});
129+
headers[server.name] = { Authorization: `${token}` };
130+
} catch (err) {
131+
options?.logger?.error(
132+
`Failed to mint DCR token for MCP server '${server.name}': ${err}`,
133+
);
134+
}
136135
} else {
137136
options?.logger?.warn(
138137
`MCP server '${server.name}' has auth: dcr but AuthService is not available; skipping`,
@@ -400,11 +399,21 @@ export async function createRouter(
400399

401400
let effectiveToken: string | undefined;
402401
if (server.auth === 'dcr') {
403-
const { token: dcrToken } = await auth.getPluginRequestToken({
404-
onBehalfOf: credentials,
405-
targetPluginId: 'mcp-actions',
406-
});
407-
effectiveToken = dcrToken;
402+
try {
403+
const { token: dcrToken } = await auth.getPluginRequestToken({
404+
onBehalfOf: credentials,
405+
targetPluginId: 'mcp-actions',
406+
});
407+
effectiveToken = dcrToken;
408+
} catch (err) {
409+
logger.error(
410+
`Failed to mint DCR token for server '${name}': ${err}`,
411+
);
412+
res.status(502).json({
413+
error: `Failed to mint authentication token for DCR server '${name}' — check Backstage auth configuration`,
414+
});
415+
return;
416+
}
408417
} else {
409418
const setting = await settingsStore.get(name, userEntityRef);
410419
effectiveToken = setting?.token || server.token;
@@ -715,7 +724,7 @@ export async function createRouter(
715724
staticServers,
716725
settingsStore,
717726
userEntityRef,
718-
{ authService: auth, credentials, logger },
727+
{ dcrAuth: { authService: auth, credentials }, logger },
719728
);
720729

721730
const abortController = new AbortController();

0 commit comments

Comments
 (0)