@backstage/plugin-mcp-actions-backend 0.1.14-next.0 → 0.1.14-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @backstage/plugin-mcp-actions-backend
|
|
2
2
|
|
|
3
|
+
## 0.1.14-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ed1be73: Validate each action against the MCP tool schema when responding to `tools/list`, and skip any action that doesn't conform instead of failing the entire response. A single misbehaving action with a malformed input schema will now be logged as a warning and omitted from the tool list, letting the remaining actions continue to be served.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/catalog-client@1.16.0-next.1
|
|
10
|
+
- @backstage/backend-plugin-api@1.9.2-next.1
|
|
11
|
+
|
|
3
12
|
## 0.1.14-next.0
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/dist/plugin.cjs.js
CHANGED
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { json } from 'express';\nimport Router from 'express-promise-router';\nimport { McpService } from './services/McpService';\nimport { createStreamableRouter } from './routers/createStreamableRouter';\nimport { createSseRouter } from './routers/createSseRouter';\nimport {\n actionsRegistryServiceRef,\n actionsServiceRef,\n metricsServiceRef,\n tracingServiceRef,\n} from '@backstage/backend-plugin-api/alpha';\nimport { parseServerConfigs } from './config';\n\n/**\n * mcpPlugin backend plugin\n *\n * @public\n */\nexport const mcpPlugin = createBackendPlugin({\n pluginId: 'mcp-actions',\n register(env) {\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n actions: actionsServiceRef,\n registry: actionsRegistryServiceRef,\n rootRouter: coreServices.rootHttpRouter,\n discovery: coreServices.discovery,\n config: coreServices.rootConfig,\n metrics: metricsServiceRef,\n tracing: tracingServiceRef,\n },\n async init({\n actions,\n logger,\n httpRouter,\n httpAuth,\n rootRouter,\n discovery,\n config,\n metrics,\n tracing,\n }) {\n const serverConfigs = parseServerConfigs(config);\n const namespacedToolNames = config.getOptionalBoolean(\n 'mcpActions.namespacedToolNames',\n );\n const captureToolPayloads =\n config.getOptionalBoolean('mcpActions.tracing.capture.toolPayload') ??\n false;\n\n const mcpService = await McpService.create({\n actions,\n metrics,\n namespacedToolNames,\n tracingService: tracing,\n captureToolPayloads,\n });\n\n const router = Router();\n router.use(json());\n\n if (serverConfigs && serverConfigs.size > 0) {\n for (const [key, serverConfig] of serverConfigs) {\n const streamableRouter = createStreamableRouter({\n mcpService,\n httpAuth,\n logger,\n metrics,\n tracing,\n serverConfig,\n });\n\n router.use(`/v1/${key}`, streamableRouter);\n }\n } else {\n const serverConfig = {\n name: config.getOptionalString('mcpActions.name') ?? 'backstage',\n description: config.getOptionalString('mcpActions.description'),\n includeRules: [],\n excludeRules: [],\n };\n\n const sseRouter = createSseRouter({\n mcpService,\n httpAuth,\n tracing,\n serverConfig,\n });\n\n const streamableRouter = createStreamableRouter({\n mcpService,\n httpAuth,\n logger,\n metrics,\n tracing,\n serverConfig,\n });\n\n router.use('/v1/sse', sseRouter);\n router.use('/v1', streamableRouter);\n }\n\n httpRouter.use(router);\n\n const oauthEnabled =\n config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n ) ||\n config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (oauthEnabled) {\n // OAuth Authorization Server Metadata (RFC 8414)\n // This should be replaced with throwing a WWW-Authenticate header, but that doesn't seem to be supported by\n // many of the MCP clients as of yet. So this seems to be the oldest version of the spec that's implemented.\n rootRouter.use(\n '/.well-known/oauth-authorization-server',\n async (_, res) => {\n const authBaseUrl = await discovery.getBaseUrl('auth');\n const oidcResponse = await fetch(\n `${authBaseUrl}/.well-known/openid-configuration`,\n );\n res.json(await oidcResponse.json());\n },\n );\n\n // Protected Resource Metadata (RFC 9728)\n // https://datatracker.ietf.org/doc/html/rfc9728\n // This allows MCP clients to discover the authorization server for this resource\n const serverSuffixes = serverConfigs?.size\n ? [...serverConfigs.keys()].map(key => `/v1/${key}`)\n : ['/v1'];\n\n for (const suffix of serverSuffixes) {\n const mcpBasePath = `/api/mcp-actions${suffix}`;\n\n rootRouter.use(\n `/.well-known/oauth-protected-resource${mcpBasePath}`,\n async (_req, res) => {\n const [authBaseUrl, mcpBaseUrl] = await Promise.all([\n discovery.getExternalBaseUrl('auth'),\n discovery.getExternalBaseUrl('mcp-actions'),\n ]);\n\n res.json({\n resource: `${mcpBaseUrl}${suffix}`,\n authorization_servers: [authBaseUrl],\n });\n },\n );\n }\n }\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","actionsServiceRef","actionsRegistryServiceRef","metricsServiceRef","tracingServiceRef","config","parseServerConfigs","McpService","Router","json","createStreamableRouter","createSseRouter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { json } from 'express';\nimport Router from 'express-promise-router';\nimport { McpService } from './services/McpService';\nimport { createStreamableRouter } from './routers/createStreamableRouter';\nimport { createSseRouter } from './routers/createSseRouter';\nimport {\n actionsRegistryServiceRef,\n actionsServiceRef,\n metricsServiceRef,\n tracingServiceRef,\n} from '@backstage/backend-plugin-api/alpha';\nimport { parseServerConfigs } from './config';\n\n/**\n * mcpPlugin backend plugin\n *\n * @public\n */\nexport const mcpPlugin = createBackendPlugin({\n pluginId: 'mcp-actions',\n register(env) {\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n auth: coreServices.auth,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n actions: actionsServiceRef,\n registry: actionsRegistryServiceRef,\n rootRouter: coreServices.rootHttpRouter,\n discovery: coreServices.discovery,\n config: coreServices.rootConfig,\n metrics: metricsServiceRef,\n tracing: tracingServiceRef,\n },\n async init({\n actions,\n logger,\n httpRouter,\n httpAuth,\n rootRouter,\n discovery,\n config,\n metrics,\n tracing,\n }) {\n const serverConfigs = parseServerConfigs(config);\n const namespacedToolNames = config.getOptionalBoolean(\n 'mcpActions.namespacedToolNames',\n );\n const captureToolPayloads =\n config.getOptionalBoolean('mcpActions.tracing.capture.toolPayload') ??\n false;\n\n const mcpService = await McpService.create({\n actions,\n metrics,\n logger,\n namespacedToolNames,\n tracingService: tracing,\n captureToolPayloads,\n });\n\n const router = Router();\n router.use(json());\n\n if (serverConfigs && serverConfigs.size > 0) {\n for (const [key, serverConfig] of serverConfigs) {\n const streamableRouter = createStreamableRouter({\n mcpService,\n httpAuth,\n logger,\n metrics,\n tracing,\n serverConfig,\n });\n\n router.use(`/v1/${key}`, streamableRouter);\n }\n } else {\n const serverConfig = {\n name: config.getOptionalString('mcpActions.name') ?? 'backstage',\n description: config.getOptionalString('mcpActions.description'),\n includeRules: [],\n excludeRules: [],\n };\n\n const sseRouter = createSseRouter({\n mcpService,\n httpAuth,\n tracing,\n serverConfig,\n });\n\n const streamableRouter = createStreamableRouter({\n mcpService,\n httpAuth,\n logger,\n metrics,\n tracing,\n serverConfig,\n });\n\n router.use('/v1/sse', sseRouter);\n router.use('/v1', streamableRouter);\n }\n\n httpRouter.use(router);\n\n const oauthEnabled =\n config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n ) ||\n config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (oauthEnabled) {\n // OAuth Authorization Server Metadata (RFC 8414)\n // This should be replaced with throwing a WWW-Authenticate header, but that doesn't seem to be supported by\n // many of the MCP clients as of yet. So this seems to be the oldest version of the spec that's implemented.\n rootRouter.use(\n '/.well-known/oauth-authorization-server',\n async (_, res) => {\n const authBaseUrl = await discovery.getBaseUrl('auth');\n const oidcResponse = await fetch(\n `${authBaseUrl}/.well-known/openid-configuration`,\n );\n res.json(await oidcResponse.json());\n },\n );\n\n // Protected Resource Metadata (RFC 9728)\n // https://datatracker.ietf.org/doc/html/rfc9728\n // This allows MCP clients to discover the authorization server for this resource\n const serverSuffixes = serverConfigs?.size\n ? [...serverConfigs.keys()].map(key => `/v1/${key}`)\n : ['/v1'];\n\n for (const suffix of serverSuffixes) {\n const mcpBasePath = `/api/mcp-actions${suffix}`;\n\n rootRouter.use(\n `/.well-known/oauth-protected-resource${mcpBasePath}`,\n async (_req, res) => {\n const [authBaseUrl, mcpBaseUrl] = await Promise.all([\n discovery.getExternalBaseUrl('auth'),\n discovery.getExternalBaseUrl('mcp-actions'),\n ]);\n\n res.json({\n resource: `${mcpBaseUrl}${suffix}`,\n authorization_servers: [authBaseUrl],\n });\n },\n );\n }\n }\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","actionsServiceRef","actionsRegistryServiceRef","metricsServiceRef","tracingServiceRef","config","parseServerConfigs","McpService","Router","json","createStreamableRouter","createSseRouter"],"mappings":"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"}
|
|
@@ -36,12 +36,15 @@ function baggageAttributes(tracingService) {
|
|
|
36
36
|
}
|
|
37
37
|
class McpService {
|
|
38
38
|
actions;
|
|
39
|
+
logger;
|
|
39
40
|
namespacedToolNames;
|
|
40
41
|
tracingService;
|
|
41
42
|
captureToolPayloads;
|
|
42
43
|
operationDuration;
|
|
43
|
-
|
|
44
|
+
warnedSkippedActionIds = /* @__PURE__ */ new Set();
|
|
45
|
+
constructor(actions, metrics$1, tracingService, logger, namespacedToolNames, captureToolPayloads) {
|
|
44
46
|
this.actions = actions;
|
|
47
|
+
this.logger = logger;
|
|
45
48
|
this.namespacedToolNames = namespacedToolNames ?? true;
|
|
46
49
|
this.tracingService = tracingService;
|
|
47
50
|
this.captureToolPayloads = captureToolPayloads ?? false;
|
|
@@ -58,6 +61,7 @@ class McpService {
|
|
|
58
61
|
actions,
|
|
59
62
|
metrics,
|
|
60
63
|
tracingService,
|
|
64
|
+
logger,
|
|
61
65
|
namespacedToolNames,
|
|
62
66
|
captureToolPayloads
|
|
63
67
|
}) {
|
|
@@ -65,6 +69,7 @@ class McpService {
|
|
|
65
69
|
actions,
|
|
66
70
|
metrics,
|
|
67
71
|
tracingService,
|
|
72
|
+
logger,
|
|
68
73
|
namespacedToolNames,
|
|
69
74
|
captureToolPayloads
|
|
70
75
|
);
|
|
@@ -91,8 +96,9 @@ class McpService {
|
|
|
91
96
|
credentials
|
|
92
97
|
});
|
|
93
98
|
const actions = serverConfig ? this.filterActions(allActions, serverConfig) : allActions;
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
const tools = [];
|
|
100
|
+
for (const action of actions) {
|
|
101
|
+
const tool = {
|
|
96
102
|
inputSchema: action.schema.input,
|
|
97
103
|
name: this.getToolName(action),
|
|
98
104
|
description: action.description,
|
|
@@ -103,8 +109,20 @@ class McpService {
|
|
|
103
109
|
readOnlyHint: action.attributes.readOnly,
|
|
104
110
|
openWorldHint: false
|
|
105
111
|
}
|
|
106
|
-
}
|
|
107
|
-
|
|
112
|
+
};
|
|
113
|
+
const parsed = types_js.ToolSchema.safeParse(tool);
|
|
114
|
+
if (!parsed.success) {
|
|
115
|
+
if (!this.warnedSkippedActionIds.has(action.id)) {
|
|
116
|
+
this.warnedSkippedActionIds.add(action.id);
|
|
117
|
+
this.logger?.warn(
|
|
118
|
+
`Skipping MCP tool for action "${action.id}": ${parsed.error.message}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
tools.push(parsed.data);
|
|
124
|
+
}
|
|
125
|
+
return { tools };
|
|
108
126
|
} catch (err) {
|
|
109
127
|
errorType = err instanceof Error ? err.name : "Error";
|
|
110
128
|
throw err;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpService.cjs.js","sources":["../../src/services/McpService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { BackstageCredentials } from '@backstage/backend-plugin-api';\nimport { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { JsonObject } from '@backstage/types';\nimport {\n ActionsService,\n ActionsServiceAction,\n MetricsServiceHistogram,\n MetricsService,\n TracingService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { version } from '@backstage/plugin-mcp-actions-backend/package.json';\nimport { NotFoundError } from '@backstage/errors';\nimport { performance } from 'node:perf_hooks';\n\nimport { handleErrors } from './handleErrors';\nimport { bucketBoundaries, McpServerOperationAttributes } from '../metrics';\nimport { FilterRule, McpServerConfig } from '../config';\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n// Baggage is propagated from untrusted callers, so we forward only an\n// explicit allowlist of low-cardinality identifier keys from the OTel\n// `gen_ai.*` registry.\nconst PROPAGATED_BAGGAGE_ATTRIBUTES: ReadonlySet<string> = new Set([\n 'gen_ai.agent.id',\n 'gen_ai.agent.name',\n 'gen_ai.conversation.id',\n 'gen_ai.provider.name',\n 'gen_ai.request.model',\n]);\n\n// Cap each forwarded baggage value before it lands on a span attribute.\n// Baggage values are caller-controlled strings of unbounded length;\n// allowlisting keys protects against arbitrary attribute names but not\n// against pathologically large values inflating exported span sizes.\nconst BAGGAGE_ATTRIBUTE_VALUE_MAX_LENGTH = 256;\n\nfunction baggageAttributes(\n tracingService: TracingService,\n): Record<string, string> {\n const baggage = tracingService.propagation.getActiveBaggage();\n if (!baggage) return {};\n const attrs: Record<string, string> = {};\n for (const [key, entry] of baggage.getAllEntries()) {\n if (PROPAGATED_BAGGAGE_ATTRIBUTES.has(key)) {\n attrs[key] = entry.value.slice(0, BAGGAGE_ATTRIBUTE_VALUE_MAX_LENGTH);\n }\n }\n return attrs;\n}\n\nexport class McpService {\n private readonly actions: ActionsService;\n private readonly namespacedToolNames: boolean;\n private readonly tracingService: TracingService;\n private readonly captureToolPayloads: boolean;\n private readonly operationDuration: MetricsServiceHistogram<McpServerOperationAttributes>;\n\n constructor(\n actions: ActionsService,\n metrics: MetricsService,\n tracingService: TracingService,\n namespacedToolNames?: boolean,\n captureToolPayloads?: boolean,\n ) {\n this.actions = actions;\n this.namespacedToolNames = namespacedToolNames ?? true;\n this.tracingService = tracingService;\n this.captureToolPayloads = captureToolPayloads ?? false;\n this.operationDuration =\n metrics.createHistogram<McpServerOperationAttributes>(\n 'mcp.server.operation.duration',\n {\n description: 'MCP request duration as observed on the receiver',\n unit: 's',\n advice: { explicitBucketBoundaries: bucketBoundaries },\n },\n );\n }\n\n static async create({\n actions,\n metrics,\n tracingService,\n namespacedToolNames,\n captureToolPayloads,\n }: {\n actions: ActionsService;\n metrics: MetricsService;\n tracingService: TracingService;\n namespacedToolNames?: boolean;\n captureToolPayloads?: boolean;\n }) {\n return new McpService(\n actions,\n metrics,\n tracingService,\n namespacedToolNames,\n captureToolPayloads,\n );\n }\n\n getServer({\n credentials,\n serverConfig,\n }: {\n credentials: BackstageCredentials;\n serverConfig?: McpServerConfig;\n }) {\n const server = new McpServer(\n {\n name: serverConfig?.name ?? 'backstage',\n version,\n ...(serverConfig?.description && {\n description: serverConfig.description,\n }),\n },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const startTime = performance.now();\n let errorType: string | undefined;\n\n try {\n const { actions: allActions } = await this.actions.list({\n credentials,\n });\n const actions = serverConfig\n ? this.filterActions(allActions, serverConfig)\n : allActions;\n\n return {\n tools: actions.map(action => ({\n inputSchema: action.schema.input,\n name: this.getToolName(action),\n description: action.description,\n annotations: {\n title: action.title,\n destructiveHint: action.attributes.destructive,\n idempotentHint: action.attributes.idempotent,\n readOnlyHint: action.attributes.readOnly,\n openWorldHint: false,\n },\n })),\n };\n } catch (err) {\n errorType = err instanceof Error ? err.name : 'Error';\n throw err;\n } finally {\n const durationSeconds = (performance.now() - startTime) / 1000;\n\n this.operationDuration.record(durationSeconds, {\n 'mcp.method.name': 'tools/list',\n ...(errorType && { 'error.type': errorType }),\n });\n }\n });\n\n server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {\n const startTime = performance.now();\n let errorType: string | undefined;\n let isError = false;\n\n try {\n return await this.tracingService.startActiveSpan(\n `tools/call ${params.name}`,\n {\n kind: 'server',\n credentials,\n attributes: {\n ...baggageAttributes(this.tracingService),\n 'mcp.method.name': 'tools/call',\n 'gen_ai.tool.name': params.name,\n 'gen_ai.operation.name': 'execute_tool',\n ...(this.captureToolPayloads && {\n 'gen_ai.tool.call.arguments': safeStringify(params.arguments),\n }),\n },\n },\n async span => {\n const result = await handleErrors(async () => {\n const { actions: allActions } = await this.actions.list({\n credentials,\n });\n const actions = serverConfig\n ? this.filterActions(allActions, serverConfig)\n : allActions;\n\n const action = actions.find(\n a => this.getToolName(a) === params.name,\n );\n\n if (!action) {\n throw new NotFoundError(`Action \"${params.name}\" not found`);\n }\n\n // Re-attribute the span to the plugin that owns the action.\n // This runs after the span has started, so head-based samplers\n // still see the default `mcp-actions` value when deciding\n // whether to record the span. The pluginId is only known after\n // resolving the action via `actions.list`, so the reattribution\n // is unavoidable.\n span.setAttribute('backstage.plugin.id', action.pluginId);\n\n const { output } = await this.actions.invoke({\n id: action.id,\n input: params.arguments as JsonObject,\n credentials,\n });\n\n if (this.captureToolPayloads) {\n span.setAttribute(\n 'gen_ai.tool.call.result',\n safeStringify(output),\n );\n }\n\n return {\n content: [\n {\n type: 'text',\n text: safeStringify(output),\n },\n ],\n structuredContent: output,\n };\n });\n\n isError = !!(result as { isError?: boolean })?.isError;\n if (isError) {\n span.setAttribute('error.type', 'tool_error');\n span.setStatus({ code: 'error', message: 'tool_error' });\n }\n return result;\n },\n );\n } catch (err) {\n errorType = err instanceof Error ? err.name : 'Error';\n throw err;\n } finally {\n const durationSeconds = (performance.now() - startTime) / 1000;\n\n // Determine error.type per OTel MCP spec:\n // - Thrown exceptions use the error name\n // - CallToolResult with isError=true uses 'tool_error'\n let errorAttribute: string | undefined = errorType;\n if (!errorAttribute && isError) {\n errorAttribute = 'tool_error';\n }\n\n this.operationDuration.record(durationSeconds, {\n 'mcp.method.name': 'tools/call',\n 'gen_ai.tool.name': params.name,\n 'gen_ai.operation.name': 'execute_tool',\n ...(errorAttribute && { 'error.type': errorAttribute }),\n });\n }\n });\n\n return server;\n }\n\n private filterActions(\n actions: ActionsServiceAction[],\n serverConfig: McpServerConfig,\n ): ActionsServiceAction[] {\n const { includeRules, excludeRules } = serverConfig;\n if (includeRules.length === 0 && excludeRules.length === 0) {\n return actions;\n }\n\n return actions.filter(action => {\n if (excludeRules.some(rule => this.matchesRule(action, rule))) {\n return false;\n }\n\n if (includeRules.length === 0) {\n return true;\n }\n\n return includeRules.some(rule => this.matchesRule(action, rule));\n });\n }\n\n private getToolName(action: ActionsServiceAction): string {\n if (this.namespacedToolNames) {\n return `${action.pluginId}.${action.name}`;\n }\n return action.name;\n }\n\n private matchesRule(action: ActionsServiceAction, rule: FilterRule): boolean {\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n if (rule.attributes) {\n for (const [key, value] of Object.entries(rule.attributes)) {\n if (\n action.attributes[\n key as 'destructive' | 'readOnly' | 'idempotent'\n ] !== value\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n}\n"],"names":["metrics","bucketBoundaries","McpServer","version","ListToolsRequestSchema","performance","CallToolRequestSchema","handleErrors","NotFoundError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"McpService.cjs.js","sources":["../../src/services/McpService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n Tool,\n ToolSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { JsonObject } from '@backstage/types';\nimport {\n ActionsService,\n ActionsServiceAction,\n MetricsServiceHistogram,\n MetricsService,\n TracingService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { version } from '@backstage/plugin-mcp-actions-backend/package.json';\nimport { NotFoundError } from '@backstage/errors';\nimport { performance } from 'node:perf_hooks';\n\nimport { handleErrors } from './handleErrors';\nimport { bucketBoundaries, McpServerOperationAttributes } from '../metrics';\nimport { FilterRule, McpServerConfig } from '../config';\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n// Baggage is propagated from untrusted callers, so we forward only an\n// explicit allowlist of low-cardinality identifier keys from the OTel\n// `gen_ai.*` registry.\nconst PROPAGATED_BAGGAGE_ATTRIBUTES: ReadonlySet<string> = new Set([\n 'gen_ai.agent.id',\n 'gen_ai.agent.name',\n 'gen_ai.conversation.id',\n 'gen_ai.provider.name',\n 'gen_ai.request.model',\n]);\n\n// Cap each forwarded baggage value before it lands on a span attribute.\n// Baggage values are caller-controlled strings of unbounded length;\n// allowlisting keys protects against arbitrary attribute names but not\n// against pathologically large values inflating exported span sizes.\nconst BAGGAGE_ATTRIBUTE_VALUE_MAX_LENGTH = 256;\n\nfunction baggageAttributes(\n tracingService: TracingService,\n): Record<string, string> {\n const baggage = tracingService.propagation.getActiveBaggage();\n if (!baggage) return {};\n const attrs: Record<string, string> = {};\n for (const [key, entry] of baggage.getAllEntries()) {\n if (PROPAGATED_BAGGAGE_ATTRIBUTES.has(key)) {\n attrs[key] = entry.value.slice(0, BAGGAGE_ATTRIBUTE_VALUE_MAX_LENGTH);\n }\n }\n return attrs;\n}\n\nexport class McpService {\n private readonly actions: ActionsService;\n private readonly logger: LoggerService | undefined;\n private readonly namespacedToolNames: boolean;\n private readonly tracingService: TracingService;\n private readonly captureToolPayloads: boolean;\n private readonly operationDuration: MetricsServiceHistogram<McpServerOperationAttributes>;\n private readonly warnedSkippedActionIds = new Set<string>();\n\n constructor(\n actions: ActionsService,\n metrics: MetricsService,\n tracingService: TracingService,\n logger: LoggerService | undefined,\n namespacedToolNames?: boolean,\n captureToolPayloads?: boolean,\n ) {\n this.actions = actions;\n this.logger = logger;\n this.namespacedToolNames = namespacedToolNames ?? true;\n this.tracingService = tracingService;\n this.captureToolPayloads = captureToolPayloads ?? false;\n this.operationDuration =\n metrics.createHistogram<McpServerOperationAttributes>(\n 'mcp.server.operation.duration',\n {\n description: 'MCP request duration as observed on the receiver',\n unit: 's',\n advice: { explicitBucketBoundaries: bucketBoundaries },\n },\n );\n }\n\n static async create({\n actions,\n metrics,\n tracingService,\n logger,\n namespacedToolNames,\n captureToolPayloads,\n }: {\n actions: ActionsService;\n metrics: MetricsService;\n tracingService: TracingService;\n logger?: LoggerService;\n namespacedToolNames?: boolean;\n captureToolPayloads?: boolean;\n }) {\n return new McpService(\n actions,\n metrics,\n tracingService,\n logger,\n namespacedToolNames,\n captureToolPayloads,\n );\n }\n\n getServer({\n credentials,\n serverConfig,\n }: {\n credentials: BackstageCredentials;\n serverConfig?: McpServerConfig;\n }) {\n const server = new McpServer(\n {\n name: serverConfig?.name ?? 'backstage',\n version,\n ...(serverConfig?.description && {\n description: serverConfig.description,\n }),\n },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const startTime = performance.now();\n let errorType: string | undefined;\n\n try {\n const { actions: allActions } = await this.actions.list({\n credentials,\n });\n const actions = serverConfig\n ? this.filterActions(allActions, serverConfig)\n : allActions;\n\n const tools: Tool[] = [];\n for (const action of actions) {\n const tool = {\n inputSchema: action.schema.input,\n name: this.getToolName(action),\n description: action.description,\n annotations: {\n title: action.title,\n destructiveHint: action.attributes.destructive,\n idempotentHint: action.attributes.idempotent,\n readOnlyHint: action.attributes.readOnly,\n openWorldHint: false,\n },\n };\n\n // Validate each tool against the MCP Tool schema so that a single\n // malformed action (e.g. an inputSchema that isn't a JSON Schema\n // object at the root) doesn't poison the whole tools/list response.\n const parsed = ToolSchema.safeParse(tool);\n if (!parsed.success) {\n if (!this.warnedSkippedActionIds.has(action.id)) {\n this.warnedSkippedActionIds.add(action.id);\n this.logger?.warn(\n `Skipping MCP tool for action \"${action.id}\": ${parsed.error.message}`,\n );\n }\n continue;\n }\n\n tools.push(parsed.data);\n }\n\n return { tools };\n } catch (err) {\n errorType = err instanceof Error ? err.name : 'Error';\n throw err;\n } finally {\n const durationSeconds = (performance.now() - startTime) / 1000;\n\n this.operationDuration.record(durationSeconds, {\n 'mcp.method.name': 'tools/list',\n ...(errorType && { 'error.type': errorType }),\n });\n }\n });\n\n server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {\n const startTime = performance.now();\n let errorType: string | undefined;\n let isError = false;\n\n try {\n return await this.tracingService.startActiveSpan(\n `tools/call ${params.name}`,\n {\n kind: 'server',\n credentials,\n attributes: {\n ...baggageAttributes(this.tracingService),\n 'mcp.method.name': 'tools/call',\n 'gen_ai.tool.name': params.name,\n 'gen_ai.operation.name': 'execute_tool',\n ...(this.captureToolPayloads && {\n 'gen_ai.tool.call.arguments': safeStringify(params.arguments),\n }),\n },\n },\n async span => {\n const result = await handleErrors(async () => {\n const { actions: allActions } = await this.actions.list({\n credentials,\n });\n const actions = serverConfig\n ? this.filterActions(allActions, serverConfig)\n : allActions;\n\n const action = actions.find(\n a => this.getToolName(a) === params.name,\n );\n\n if (!action) {\n throw new NotFoundError(`Action \"${params.name}\" not found`);\n }\n\n // Re-attribute the span to the plugin that owns the action.\n // This runs after the span has started, so head-based samplers\n // still see the default `mcp-actions` value when deciding\n // whether to record the span. The pluginId is only known after\n // resolving the action via `actions.list`, so the reattribution\n // is unavoidable.\n span.setAttribute('backstage.plugin.id', action.pluginId);\n\n const { output } = await this.actions.invoke({\n id: action.id,\n input: params.arguments as JsonObject,\n credentials,\n });\n\n if (this.captureToolPayloads) {\n span.setAttribute(\n 'gen_ai.tool.call.result',\n safeStringify(output),\n );\n }\n\n return {\n content: [\n {\n type: 'text',\n text: safeStringify(output),\n },\n ],\n structuredContent: output,\n };\n });\n\n isError = !!(result as { isError?: boolean })?.isError;\n if (isError) {\n span.setAttribute('error.type', 'tool_error');\n span.setStatus({ code: 'error', message: 'tool_error' });\n }\n return result;\n },\n );\n } catch (err) {\n errorType = err instanceof Error ? err.name : 'Error';\n throw err;\n } finally {\n const durationSeconds = (performance.now() - startTime) / 1000;\n\n // Determine error.type per OTel MCP spec:\n // - Thrown exceptions use the error name\n // - CallToolResult with isError=true uses 'tool_error'\n let errorAttribute: string | undefined = errorType;\n if (!errorAttribute && isError) {\n errorAttribute = 'tool_error';\n }\n\n this.operationDuration.record(durationSeconds, {\n 'mcp.method.name': 'tools/call',\n 'gen_ai.tool.name': params.name,\n 'gen_ai.operation.name': 'execute_tool',\n ...(errorAttribute && { 'error.type': errorAttribute }),\n });\n }\n });\n\n return server;\n }\n\n private filterActions(\n actions: ActionsServiceAction[],\n serverConfig: McpServerConfig,\n ): ActionsServiceAction[] {\n const { includeRules, excludeRules } = serverConfig;\n if (includeRules.length === 0 && excludeRules.length === 0) {\n return actions;\n }\n\n return actions.filter(action => {\n if (excludeRules.some(rule => this.matchesRule(action, rule))) {\n return false;\n }\n\n if (includeRules.length === 0) {\n return true;\n }\n\n return includeRules.some(rule => this.matchesRule(action, rule));\n });\n }\n\n private getToolName(action: ActionsServiceAction): string {\n if (this.namespacedToolNames) {\n return `${action.pluginId}.${action.name}`;\n }\n return action.name;\n }\n\n private matchesRule(action: ActionsServiceAction, rule: FilterRule): boolean {\n if (rule.idMatcher && !rule.idMatcher.match(action.id)) {\n return false;\n }\n\n if (rule.attributes) {\n for (const [key, value] of Object.entries(rule.attributes)) {\n if (\n action.attributes[\n key as 'destructive' | 'readOnly' | 'idempotent'\n ] !== value\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n}\n"],"names":["metrics","bucketBoundaries","McpServer","version","ListToolsRequestSchema","performance","ToolSchema","CallToolRequestSchema","handleErrors","NotFoundError"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-mcp-actions-backend",
|
|
3
|
-
"version": "0.1.14-next.
|
|
3
|
+
"version": "0.1.14-next.1",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "backend-plugin",
|
|
6
6
|
"pluginId": "mcp-actions",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"test": "backstage-cli package test"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@backstage/backend-plugin-api": "1.9.2-next.
|
|
42
|
-
"@backstage/catalog-client": "1.16.0-next.
|
|
41
|
+
"@backstage/backend-plugin-api": "1.9.2-next.1",
|
|
42
|
+
"@backstage/catalog-client": "1.16.0-next.1",
|
|
43
43
|
"@backstage/config": "1.3.8",
|
|
44
44
|
"@backstage/errors": "1.3.1",
|
|
45
45
|
"@backstage/plugin-catalog-node": "2.2.2-next.0",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"zod": "^3.25.76 || ^4.0.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@backstage/backend-defaults": "0.17.
|
|
56
|
-
"@backstage/backend-test-utils": "1.11.4-next.
|
|
57
|
-
"@backstage/cli": "0.36.3-next.
|
|
55
|
+
"@backstage/backend-defaults": "0.17.3-next.1",
|
|
56
|
+
"@backstage/backend-test-utils": "1.11.4-next.1",
|
|
57
|
+
"@backstage/cli": "0.36.3-next.1",
|
|
58
58
|
"@types/express": "^4.17.6",
|
|
59
59
|
"@types/supertest": "^2.0.8",
|
|
60
60
|
"supertest": "^7.0.0"
|