From 2d6efc4b7ee48111560555e277404855ab62bb8b Mon Sep 17 00:00:00 2001 From: Eloy Coto Date: Tue, 17 Mar 2026 16:20:05 +0100 Subject: [PATCH 1/2] feat(x2a): MCP server tools This enables a way to check the current status of the project using MCP tools, and can be used inside any project that needs to have AI tooling on top of it. Because this uses OAuth2, I need to implement a quick page, because `plugin-auth` is not that easy to run within the current auth implementation. To test, I use MCP Inspector, like this: ``` DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector ``` The auth env var is important, because without it, it messes up the OAuth2 token that we set[0]. I can see room for improvements where: - The auth could maybe be moved to https://www.npmjs.com/package/@backstage/plugin-auth - The router is highly coupled; maybe we need to have an HTTP router and services that can be consumed by MCP and by the router. - The MCP actions could be expanded (follow-up PR): - Run module - Get project - Get module So the idea is just to get the basic terms of the MCP server in place, and finish all the implementation correctly by the end of the quarter. [0] https://github.com/modelcontextprotocol/inspector/issues/633 Signed-off-by: Eloy Coto --- workspaces/x2a/app-config.yaml | 11 +- .../x2a/examples/example-rbac-policy.csv | 3 +- workspaces/x2a/examples/org.yaml | 10 + workspaces/x2a/packages/app/src/App.tsx | 2 + .../app/src/components/oauth2/ConsentPage.tsx | 249 ++++++++++++ workspaces/x2a/packages/backend/package.json | 1 + workspaces/x2a/packages/backend/src/index.ts | 3 + .../plugins/x2a-backend/src/mcp/projects.ts | 89 +++++ .../x2a/plugins/x2a-backend/src/plugin.ts | 11 + .../x2a-backend/src/router/listProjects.ts | 68 ++++ .../x2a-backend/src/router/projects.ts | 30 +- workspaces/x2a/yarn.lock | 369 +++++++++++++++++- 12 files changed, 808 insertions(+), 38 deletions(-) create mode 100644 workspaces/x2a/packages/app/src/components/oauth2/ConsentPage.tsx create mode 100644 workspaces/x2a/plugins/x2a-backend/src/mcp/projects.ts create mode 100644 workspaces/x2a/plugins/x2a-backend/src/router/listProjects.ts diff --git a/workspaces/x2a/app-config.yaml b/workspaces/x2a/app-config.yaml index f8c487426a..1425d8bdf2 100644 --- a/workspaces/x2a/app-config.yaml +++ b/workspaces/x2a/app-config.yaml @@ -22,7 +22,9 @@ backend: # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference # Default Helmet Content-Security-Policy values can be removed by setting the key to false cors: - origin: http://localhost:3000 + origin: + - http://localhost:3000 + - http://localhost:6274 methods: [GET, HEAD, PATCH, POST, PUT, DELETE] credentials: true # This is for local development only, it is not recommended to use this in production @@ -30,6 +32,10 @@ backend: database: client: better-sqlite3 connection: ':memory:' + actions: + pluginSources: + - 'catalog' + - 'x2a' # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir proxy: @@ -72,6 +78,9 @@ integrations: - host: bitbucket.org auth: + experimentalDynamicClientRegistration: + # enable the feature + enabled: true # see https://backstage.io/docs/auth/ to learn about auth providers environment: development providers: diff --git a/workspaces/x2a/examples/example-rbac-policy.csv b/workspaces/x2a/examples/example-rbac-policy.csv index dec1388150..7cccb20125 100644 --- a/workspaces/x2a/examples/example-rbac-policy.csv +++ b/workspaces/x2a/examples/example-rbac-policy.csv @@ -20,6 +20,7 @@ g, user:development/guest, role:default/x2aUser g, group:default/x2a-admin-group, role:default/x2aAdmin g, user:default/elai-shalev, role:default/x2aAdmin +g, user:default/eloycoto, role:default/x2aAdmin ######################################################## # Catalog and Scaffolder templates permissions @@ -47,4 +48,4 @@ p, role:default/x2aUser, scaffolder.template.parameter.read, read, allow p, role:default/x2aUser, scaffolder.template.step.read, read, allow p, role:default/x2aUser, scaffolder.task.create, create, allow p, role:default/x2aUser, scaffolder.task.cancel, cancel, allow -p, role:default/x2aUser, scaffolder.task.read, read, allow \ No newline at end of file +p, role:default/x2aUser, scaffolder.task.read, read, allow diff --git a/workspaces/x2a/examples/org.yaml b/workspaces/x2a/examples/org.yaml index 7885622d8b..4ed5b5b79d 100644 --- a/workspaces/x2a/examples/org.yaml +++ b/workspaces/x2a/examples/org.yaml @@ -48,3 +48,13 @@ spec: displayName: Elai Shalev email: foo@bar.com memberOf: [] +--- +apiVersion: backstage.io/v1alpha1 +kind: User +metadata: + name: eloycoto +spec: + profile: + displayName: Eloy Coto + email: foo@bar.com + memberOf: [] diff --git a/workspaces/x2a/packages/app/src/App.tsx b/workspaces/x2a/packages/app/src/App.tsx index 6c82e9d9e8..3a8ee3fded 100644 --- a/workspaces/x2a/packages/app/src/App.tsx +++ b/workspaces/x2a/packages/app/src/App.tsx @@ -61,6 +61,7 @@ import { githubAuthApiRef, gitlabAuthApiRef, } from '@backstage/core-plugin-api'; +import { ConsentPage } from './components/oauth2/ConsentPage'; const app = createApp({ apis, @@ -152,6 +153,7 @@ const routes = ( } /> } /> } /> + } /> ); diff --git a/workspaces/x2a/packages/app/src/components/oauth2/ConsentPage.tsx b/workspaces/x2a/packages/app/src/components/oauth2/ConsentPage.tsx new file mode 100644 index 0000000000..7774166355 --- /dev/null +++ b/workspaces/x2a/packages/app/src/components/oauth2/ConsentPage.tsx @@ -0,0 +1,249 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useCallback, useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { + useApi, + configApiRef, + fetchApiRef, + discoveryApiRef, +} from '@backstage/core-plugin-api'; +import { + Page, + Header, + Content, + Progress, + ResponseErrorPanel, +} from '@backstage/core-components'; +import { + Card, + CardContent, + CardActions, + Button, + Typography, + Divider, + Box, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; + +interface Session { + id: string; + clientName?: string; + clientId: string; + redirectUri: string; + scopes?: string[]; +} + +type ConsentState = + | { status: 'loading' } + | { status: 'error'; error: string } + | { status: 'loaded'; session: Session } + | { status: 'submitting'; session: Session; action: 'approve' | 'reject' } + | { status: 'completed'; action: 'approve' | 'reject' }; + +const useStyles = makeStyles(theme => ({ + card: { + maxWidth: 600, + margin: `${theme.spacing(4)}px auto`, + }, + appHeader: { + display: 'flex', + alignItems: 'center', + gap: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + callbackUrl: { + fontFamily: 'monospace', + fontSize: '0.85rem', + backgroundColor: theme.palette.background.default, + padding: theme.spacing(1), + borderRadius: theme.shape.borderRadius, + wordBreak: 'break-all', + marginTop: theme.spacing(1), + }, + actions: { + display: 'flex', + justifyContent: 'flex-end', + gap: theme.spacing(1), + padding: theme.spacing(2), + }, +})); + +const getHeaderTitle = (state: ConsentState): string => { + if (state.status === 'completed' && state.action === 'approve') { + return 'Authorization Approved'; + } + if (state.status === 'completed') { + return 'Authorization Denied'; + } + return 'Authorization Request'; +}; + +export const ConsentPage = () => { + const { sessionId } = useParams<{ sessionId: string }>(); + const classes = useStyles(); + const configApi = useApi(configApiRef); + const fetchApi = useApi(fetchApiRef); + const discoveryApi = useApi(discoveryApiRef); + const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + + const [state, setState] = useState({ status: 'loading' }); + + useEffect(() => { + if (!sessionId) { + setState({ status: 'error', error: 'No session ID provided' }); + return undefined; + } + + let cancelled = false; + (async () => { + try { + const baseUrl = await discoveryApi.getBaseUrl('auth'); + const response = await fetchApi.fetch( + `${baseUrl}/v1/sessions/${sessionId}`, + ); + + if (cancelled) return; + if (!response.ok) { + const text = await response.text(); + setState({ status: 'error', error: text || response.statusText }); + return; + } + + const session: Session = await response.json(); + setState({ status: 'loaded', session }); + } catch (e: unknown) { + if (cancelled) return; + const message = e instanceof Error ? e.message : String(e); + setState({ status: 'error', error: message }); + } + })(); + + return () => { + cancelled = true; + }; + }, [sessionId, discoveryApi, fetchApi]); + + const handleAction = useCallback( + async (action: 'approve' | 'reject') => { + if (state.status !== 'loaded') return; + + setState({ status: 'submitting', session: state.session, action }); + + try { + const baseUrl = await discoveryApi.getBaseUrl('auth'); + const response = await fetchApi.fetch( + `${baseUrl}/v1/sessions/${sessionId}/${action}`, + { method: 'POST', headers: { 'Content-Type': 'application/json' } }, + ); + + if (!response.ok) { + const text = await response.text(); + setState({ status: 'loaded', session: state.session }); + throw new Error(text || response.statusText); + } + + const result = await response.json(); + setState({ status: 'completed', action }); + + if (result.redirectUrl) { + window.location.href = result.redirectUrl; + } + } catch (e: unknown) { + const message = e instanceof Error ? e.message : String(e); + setState({ status: 'error', error: message }); + } + }, + [state, sessionId, discoveryApi, fetchApi], + ); + + const headerTitle = getHeaderTitle(state); + + return ( + +
+ + {state.status === 'loading' && ( + + + + )} + + {state.status === 'error' && ( + + )} + + {state.status === 'completed' && ( + + {state.action === 'approve' + ? `You have successfully authorized the application to access your ${appTitle} account. Redirecting...` + : `You have denied the application access to your ${appTitle} account.`} + + )} + + {(state.status === 'loaded' || state.status === 'submitting') && ( + + + + + + {state.session.clientName ?? state.session.clientId} + + + wants to access your {appTitle} account + + + + + + + This will grant the application a token to access {appTitle}{' '} + on your behalf. Only authorize applications you trust. + + + {state.session.redirectUri} + + + + + + + + + )} + + + ); +}; diff --git a/workspaces/x2a/packages/backend/package.json b/workspaces/x2a/packages/backend/package.json index afe32e48a7..414fa6ad0b 100644 --- a/workspaces/x2a/packages/backend/package.json +++ b/workspaces/x2a/packages/backend/package.json @@ -35,6 +35,7 @@ "@backstage/plugin-catalog-backend-module-logs": "^0.1.16", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.14", "@backstage/plugin-kubernetes-backend": "^0.20.4", + "@backstage/plugin-mcp-actions-backend": "^0.1.9", "@backstage/plugin-notifications-backend": "^0.6.0", "@backstage/plugin-permission-backend": "^0.7.8", "@backstage/plugin-permission-common": "^0.9.3", diff --git a/workspaces/x2a/packages/backend/src/index.ts b/workspaces/x2a/packages/backend/src/index.ts index a70ce4a899..192cbf664e 100644 --- a/workspaces/x2a/packages/backend/src/index.ts +++ b/workspaces/x2a/packages/backend/src/index.ts @@ -84,6 +84,9 @@ backend.add(import('@backstage/plugin-kubernetes-backend')); backend.add(import('@backstage/plugin-notifications-backend')); backend.add(import('@backstage/plugin-signals-backend')); +// mcp plugin +backend.add(import('@backstage/plugin-mcp-actions-backend')); + backend.add(import('@red-hat-developer-hub/backstage-plugin-x2a-backend')); backend.add( diff --git a/workspaces/x2a/plugins/x2a-backend/src/mcp/projects.ts b/workspaces/x2a/plugins/x2a-backend/src/mcp/projects.ts new file mode 100644 index 0000000000..5d86dddff4 --- /dev/null +++ b/workspaces/x2a/plugins/x2a-backend/src/mcp/projects.ts @@ -0,0 +1,89 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { + BackstageCredentials, + BackstageUserPrincipal, + PermissionsService, +} from '@backstage/backend-plugin-api'; +import type { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import type { CatalogService } from '@backstage/plugin-catalog-node'; +import type { x2aDatabaseServiceRef } from '../services/X2ADatabaseService'; +import { listProjects } from '../router/listProjects'; + +interface ProjectActionsOptions { + actionsRegistry: ActionsRegistryService; + permissionsSvc: PermissionsService; + catalog: CatalogService; + x2aDatabase: typeof x2aDatabaseServiceRef.T; +} + +export function registerProjectActions({ + actionsRegistry, + permissionsSvc, + catalog, + x2aDatabase, +}: ProjectActionsOptions): void { + actionsRegistry.register({ + name: 'x2a:project:list', + title: 'List x2a Projects', + description: + 'List conversion projects with optional pagination and sorting.', + attributes: { readOnly: true }, + schema: { + input: z => + z.object({ + page: z.number().optional().describe('Page number (0-indexed)'), + pageSize: z + .number() + .optional() + .describe('Number of results per page'), + order: z.enum(['asc', 'desc']).optional().describe('Sort order'), + sort: z + .enum([ + 'createdAt', + 'name', + 'abbreviation', + 'status', + 'description', + 'createdBy', + ]) + .optional() + .describe('Field to sort by'), + }), + output: z => + z.object({ + totalCount: z.number().describe('Total number of projects'), + projects: z + .string() + .describe('JSON-serialized array of project objects'), + }), + }, + action: async ({ input, credentials, logger }) => { + logger.info('Running x2a:project:list MCP action'); + const response = await listProjects( + input, + { permissionsSvc, catalog, x2aDatabase }, + credentials as BackstageCredentials, + ); + return { + output: { + totalCount: response.totalCount ?? 0, + projects: JSON.stringify(response.items ?? []), + }, + }; + }, + }); +} diff --git a/workspaces/x2a/plugins/x2a-backend/src/plugin.ts b/workspaces/x2a/plugins/x2a-backend/src/plugin.ts index 5630f59e89..6d6ab4763f 100644 --- a/workspaces/x2a/plugins/x2a-backend/src/plugin.ts +++ b/workspaces/x2a/plugins/x2a-backend/src/plugin.ts @@ -17,11 +17,13 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { x2aDatabaseServiceRef } from './services/X2ADatabaseService'; import { createRouter } from './router'; import { migrate } from './services/dbMigrate'; import { kubeServiceRef } from './services/KubeService'; +import { registerProjectActions } from './mcp/projects'; /** * x2APlugin backend plugin @@ -43,6 +45,7 @@ export const x2APlugin = createBackendPlugin({ config: coreServices.rootConfig, x2aDatabase: x2aDatabaseServiceRef, kubeService: kubeServiceRef, + actionsRegistry: actionsRegistryServiceRef, }, async init({ httpRouter, @@ -55,6 +58,7 @@ export const x2APlugin = createBackendPlugin({ database, kubeService, config, + actionsRegistry, }) { await migrate(database); @@ -75,6 +79,13 @@ export const x2APlugin = createBackendPlugin({ config, }), ); + + registerProjectActions({ + actionsRegistry, + permissionsSvc, + catalog, + x2aDatabase, + }); }, }); }, diff --git a/workspaces/x2a/plugins/x2a-backend/src/router/listProjects.ts b/workspaces/x2a/plugins/x2a-backend/src/router/listProjects.ts new file mode 100644 index 0000000000..d6334a66c8 --- /dev/null +++ b/workspaces/x2a/plugins/x2a-backend/src/router/listProjects.ts @@ -0,0 +1,68 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + BackstageCredentials, + BackstageUserPrincipal, +} from '@backstage/backend-plugin-api'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { NotAllowedError } from '@backstage/errors'; +import { + x2aAdminViewPermission, + x2aUserPermission, +} from '@red-hat-developer-hub/backstage-plugin-x2a-common'; + +import type { RouterDeps } from './types'; +import { getGroupsOfUser, getUserRef } from './common'; +import type { ProjectsGet } from '../schema/openapi'; + +export async function listProjects( + query: ProjectsGet['query'], + deps: Pick, + credentials: BackstageCredentials, +): Promise { + const { permissionsSvc, catalog, x2aDatabase } = deps; + + const [userResult, adminViewResult] = await Promise.all([ + permissionsSvc.authorize([{ permission: x2aUserPermission }], { + credentials, + }), + permissionsSvc.authorize([{ permission: x2aAdminViewPermission }], { + credentials, + }), + ]); + + const isX2AUser = userResult[0]?.result === AuthorizeResult.ALLOW; + const canViewAll = adminViewResult[0]?.result === AuthorizeResult.ALLOW; + + if (!isX2AUser && !canViewAll) { + throw new NotAllowedError('The user is not allowed to read projects.'); + } + + const userRef = getUserRef(credentials); + const groupsOfUser = await getGroupsOfUser(userRef, { + catalog, + credentials, + }); + + const { projects, totalCount } = await x2aDatabase.listProjects(query, { + credentials, + canViewAll, + groupsOfUser, + }); + + return { totalCount, items: projects }; +} diff --git a/workspaces/x2a/plugins/x2a-backend/src/router/projects.ts b/workspaces/x2a/plugins/x2a-backend/src/router/projects.ts index 9ec647abfb..a8aa68792e 100644 --- a/workspaces/x2a/plugins/x2a-backend/src/router/projects.ts +++ b/workspaces/x2a/plugins/x2a-backend/src/router/projects.ts @@ -31,9 +31,9 @@ import { getUserRef, reconcileJobStatus, useEnforceProjectPermissions, - useEnforceX2APermissions, } from './common'; import { ProjectsGet, ProjectsPost } from '../schema/openapi'; +import { listProjects } from './listProjects'; export function registerProjectRoutes( router: express.Router, @@ -54,12 +54,7 @@ export function registerProjectRoutes( const endpoint = 'GET /projects'; logger.info(`${endpoint} request received`); - const { canViewAll } = await useEnforceX2APermissions({ - req, - readOnly: true, - permissionsSvc, - httpAuth, - }); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); // parse request query const projectsGetRequestSchema = z.object({ @@ -90,24 +85,11 @@ export function registerProjectRoutes( logger.info(`${endpoint} request received: query=${JSON.stringify(query)}`); - // list projects - const credentials = await httpAuth.credentials(req, { allow: ['user'] }); - const userRef = getUserRef(credentials); - const groupsOfUser = await getGroupsOfUser(userRef, { - catalog, - credentials, - }); - - const { projects, totalCount } = await x2aDatabase.listProjects(query, { + const response = await listProjects( + query, + { permissionsSvc, catalog, x2aDatabase }, credentials, - canViewAll, - groupsOfUser, - }); - - const response: ProjectsGet['response'] = { - totalCount, - items: projects, - }; + ); res.json(response); }); diff --git a/workspaces/x2a/yarn.lock b/workspaces/x2a/yarn.lock index fa068e333c..0bb02897a6 100644 --- a/workspaces/x2a/yarn.lock +++ b/workspaces/x2a/yarn.lock @@ -3316,6 +3316,29 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-catalog-node@npm:^2.0.0": + version: 2.0.0 + resolution: "@backstage/plugin-catalog-node@npm:2.0.0" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.7.0" + "@backstage/catalog-client": "npm:^1.13.0" + "@backstage/catalog-model": "npm:^1.7.6" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-catalog-common": "npm:^1.1.8" + "@backstage/plugin-permission-common": "npm:^0.9.6" + "@backstage/plugin-permission-node": "npm:^0.10.10" + "@backstage/types": "npm:^1.2.2" + lodash: "npm:^4.17.21" + yaml: "npm:^2.0.0" + peerDependencies: + "@backstage/backend-test-utils": ^1.11.0 + peerDependenciesMeta: + "@backstage/backend-test-utils": + optional: true + checksum: 10c0/dd418541e19f234987258d3688c98847d9c5d1256aa1ab1c5df49715c4a5c44e76f87eea9d802b2dc2a219e94681ecf76c2c379862e1f96ffa9fd84af2dcd6be + languageName: node + linkType: hard + "@backstage/plugin-catalog-react@npm:^1.20.0, @backstage/plugin-catalog-react@npm:^1.20.1, @backstage/plugin-catalog-react@npm:^1.21.0, @backstage/plugin-catalog-react@npm:^1.21.5": version: 1.21.6 resolution: "@backstage/plugin-catalog-react@npm:1.21.6" @@ -3593,6 +3616,24 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-mcp-actions-backend@npm:^0.1.9": + version: 0.1.9 + resolution: "@backstage/plugin-mcp-actions-backend@npm:0.1.9" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.7.0" + "@backstage/catalog-client": "npm:^1.13.0" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-catalog-node": "npm:^2.0.0" + "@backstage/types": "npm:^1.2.2" + "@cfworker/json-schema": "npm:^4.1.1" + "@modelcontextprotocol/sdk": "npm:^1.25.2" + express: "npm:^4.22.0" + express-promise-router: "npm:^4.1.0" + zod: "npm:^3.25.76" + checksum: 10c0/9afdc1e59e576e3dbac5f4249625c8ae6664a2badad4357b7f8d2c2ed1a7b6f7c49b1cc4dd5bd083d4c0691ad941b4ad161e15535fec4a67fac687db4dbafdea + languageName: node + linkType: hard + "@backstage/plugin-notifications-backend@npm:^0.6.0": version: 0.6.1 resolution: "@backstage/plugin-notifications-backend@npm:0.6.1" @@ -3736,6 +3777,24 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-permission-node@npm:^0.10.10": + version: 0.10.10 + resolution: "@backstage/plugin-permission-node@npm:0.10.10" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.7.0" + "@backstage/config": "npm:^1.3.6" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-auth-node": "npm:^0.6.13" + "@backstage/plugin-permission-common": "npm:^0.9.6" + "@types/express": "npm:^4.17.6" + express: "npm:^4.22.0" + express-promise-router: "npm:^4.1.0" + zod: "npm:^3.25.76" + zod-to-json-schema: "npm:^3.25.1" + checksum: 10c0/132ba02e69621fc6e162fd3acd8f320bc67f2d6c0958af4f5386195aed822ea7bba2342ff7fc47df834d8a6aa8bcacee89cfb54bd8b9a0cb8029aba1a4d468da + languageName: node + linkType: hard + "@backstage/plugin-permission-node@npm:^0.10.3, @backstage/plugin-permission-node@npm:^0.10.6, @backstage/plugin-permission-node@npm:^0.10.7, @backstage/plugin-permission-node@npm:^0.10.9": version: 0.10.9 resolution: "@backstage/plugin-permission-node@npm:0.10.9" @@ -4980,6 +5039,13 @@ __metadata: languageName: node linkType: hard +"@cfworker/json-schema@npm:^4.1.1": + version: 4.1.1 + resolution: "@cfworker/json-schema@npm:4.1.1" + checksum: 10c0/b5253486d346b7de6feec9c73954f612b11019dacb9023d710a5666df2f5fc145dd88b6b913c88726c6d97e2e258a515fa2cab177f58b18da6bac3738cbc4739 + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.14": version: 7.0.14 resolution: "@changesets/apply-release-plan@npm:7.0.14" @@ -6591,6 +6657,15 @@ __metadata: languageName: node linkType: hard +"@hono/node-server@npm:^1.19.9": + version: 1.19.11 + resolution: "@hono/node-server@npm:1.19.11" + peerDependencies: + hono: ^4 + checksum: 10c0/34b1c29c249c5cd95469980b5c359370f3cbab49b3603f324a4afbf895d68b8d5485c71f1887769eabeb3499276c49e7102084234b4feb3853edb748aaa85f50 + languageName: node + linkType: hard + "@httptoolkit/httpolyglot@npm:^2.2.1": version: 2.2.2 resolution: "@httptoolkit/httpolyglot@npm:2.2.2" @@ -7919,6 +7994,39 @@ __metadata: languageName: node linkType: hard +"@modelcontextprotocol/sdk@npm:^1.25.2": + version: 1.27.1 + resolution: "@modelcontextprotocol/sdk@npm:1.27.1" + dependencies: + "@hono/node-server": "npm:^1.19.9" + ajv: "npm:^8.17.1" + ajv-formats: "npm:^3.0.1" + content-type: "npm:^1.0.5" + cors: "npm:^2.8.5" + cross-spawn: "npm:^7.0.5" + eventsource: "npm:^3.0.2" + eventsource-parser: "npm:^3.0.0" + express: "npm:^5.2.1" + express-rate-limit: "npm:^8.2.1" + hono: "npm:^4.11.4" + jose: "npm:^6.1.3" + json-schema-typed: "npm:^8.0.2" + pkce-challenge: "npm:^5.0.0" + raw-body: "npm:^3.0.0" + zod: "npm:^3.25 || ^4.0" + zod-to-json-schema: "npm:^3.25.1" + peerDependencies: + "@cfworker/json-schema": ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + "@cfworker/json-schema": + optional: true + zod: + optional: false + checksum: 10c0/1b8ad87093c9e43174c7d65864b3d826a8dd050d5c32248f5da49fd72c51b556ebd702e3e49a7f1cc7fa25717a3f7fcee22ed89edd5bd3d8f4e1f8ca499b365e + languageName: node + linkType: hard + "@module-federation/bridge-react-webpack-plugin@npm:0.9.1": version: 0.9.1 resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.9.1" @@ -16158,6 +16266,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef + languageName: node + linkType: hard + "acorn-globals@npm:^7.0.0": version: 7.0.1 resolution: "acorn-globals@npm:7.0.1" @@ -17188,6 +17306,7 @@ __metadata: "@backstage/plugin-catalog-backend-module-logs": "npm:^0.1.16" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "npm:^0.2.14" "@backstage/plugin-kubernetes-backend": "npm:^0.20.4" + "@backstage/plugin-mcp-actions-backend": "npm:^0.1.9" "@backstage/plugin-notifications-backend": "npm:^0.6.0" "@backstage/plugin-permission-backend": "npm:^0.7.8" "@backstage/plugin-permission-common": "npm:^0.9.3" @@ -17525,6 +17644,23 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:^2.2.1": + version: 2.2.2 + resolution: "body-parser@npm:2.2.2" + dependencies: + bytes: "npm:^3.1.2" + content-type: "npm:^1.0.5" + debug: "npm:^4.4.3" + http-errors: "npm:^2.0.0" + iconv-lite: "npm:^0.7.0" + on-finished: "npm:^2.4.1" + qs: "npm:^6.14.1" + raw-body: "npm:^3.0.1" + type-is: "npm:^2.0.1" + checksum: 10c0/95a830a003b38654b75166ca765358aa92ee3d561bf0e41d6ccdde0e1a0c9783cab6b90b20eb635d23172c010b59d3563a137a738e74da4ba714463510d05137 + languageName: node + linkType: hard + "bonjour-service@npm:^1.2.1": version: 1.3.0 resolution: "bonjour-service@npm:1.3.0" @@ -17850,7 +17986,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:~3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e @@ -18789,6 +18925,13 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:^1.0.0": + version: 1.0.1 + resolution: "content-disposition@npm:1.0.1" + checksum: 10c0/bd7ff1fe8d2542d3a2b9a29428cc3591f6ac27bb5595bba2c69664408a68f9538b14cbd92479796ea835b317a09a527c8c7209c4200381dedb0c34d3b658849e + languageName: node + linkType: hard + "content-disposition@npm:~0.5.2, content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -18836,6 +18979,13 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6 + languageName: node + linkType: hard + "cookie-signature@npm:~1.0.6, cookie-signature@npm:~1.0.7": version: 1.0.7 resolution: "cookie-signature@npm:1.0.7" @@ -18843,7 +18993,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:~0.7.1, cookie@npm:~0.7.2": +"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:~0.7.1, cookie@npm:~0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 @@ -20494,7 +20644,7 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:~2.0.0": +"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb @@ -21307,7 +21457,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:~1.8.1": +"etag@npm:^1.8.1, etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 @@ -21351,6 +21501,22 @@ __metadata: languageName: node linkType: hard +"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1": + version: 3.0.6 + resolution: "eventsource-parser@npm:3.0.6" + checksum: 10c0/70b8ccec7dac767ef2eca43f355e0979e70415701691382a042a2df8d6a68da6c2fca35363669821f3da876d29c02abe9b232964637c1b6635c940df05ada78a + languageName: node + linkType: hard + +"eventsource@npm:^3.0.2": + version: 3.0.7 + resolution: "eventsource@npm:3.0.7" + dependencies: + eventsource-parser: "npm:^3.0.1" + checksum: 10c0/c48a73c38f300e33e9f11375d4ee969f25cbb0519608a12378a38068055ae8b55b6e0e8a49c3f91c784068434efe1d9f01eb49b6315b04b0da9157879ce2f67d + languageName: node + linkType: hard + "evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": version: 1.0.3 resolution: "evp_bytestokey@npm:1.0.3" @@ -21472,6 +21638,17 @@ __metadata: languageName: node linkType: hard +"express-rate-limit@npm:^8.2.1": + version: 8.3.1 + resolution: "express-rate-limit@npm:8.3.1" + dependencies: + ip-address: "npm:10.1.0" + peerDependencies: + express: ">= 4.11" + checksum: 10c0/e3229938457cec617460c54ef4e90c8c254facc884729325d20ea35e3838bd273e4c611fc1f4a76f6d14c411e30d31b15e88eb4be87408615ff0aacc142511a2 + languageName: node + linkType: hard + "express-session@npm:^1.17.1": version: 1.19.0 resolution: "express-session@npm:1.19.0" @@ -21527,6 +21704,42 @@ __metadata: languageName: node linkType: hard +"express@npm:^5.2.1": + version: 5.2.1 + resolution: "express@npm:5.2.1" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.2.1" + content-disposition: "npm:^1.0.0" + content-type: "npm:^1.0.5" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.7" + qs: "npm:^6.14.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10c0/45e8c841ad188a41402ddcd1294901e861ee0819f632fb494f2ed344ef9c43315d294d443fb48d594e6586a3b779785120f43321417adaef8567316a55072949 + languageName: node + linkType: hard + "extend@npm:3.0.2, extend@npm:^3.0.0, extend@npm:^3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -21838,6 +22051,20 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:^2.1.0": + version: 2.1.1 + resolution: "finalhandler@npm:2.1.1" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10c0/6bd664e21b7b2e79efcaace7d1a427169f61cce048fae68eb56290e6934e676b78e55d89f5998c5508871345bc59a61f47002dc505dc7288be68cceac1b701e2 + languageName: node + linkType: hard + "finalhandler@npm:~1.3.1": version: 1.3.2 resolution: "finalhandler@npm:1.3.2" @@ -22138,6 +22365,13 @@ __metadata: languageName: node linkType: hard +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc + languageName: node + linkType: hard + "fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" @@ -23272,6 +23506,13 @@ __metadata: languageName: node linkType: hard +"hono@npm:^4.11.4": + version: 4.12.8 + resolution: "hono@npm:4.12.8" + checksum: 10c0/10b8a5012e362824d97d0f11895da6e7ba3195320399684f07b3743801986647aeb8395f189a8fefbe8d4567f943c23dc0ba73834ce9a848640a5d37d50f29b6 + languageName: node + linkType: hard + "hookified@npm:^1.10.0": version: 1.15.0 resolution: "hookified@npm:1.15.0" @@ -23442,7 +23683,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" dependencies: @@ -23627,7 +23868,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2": +"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": version: 0.7.2 resolution: "iconv-lite@npm:0.7.2" dependencies: @@ -23937,7 +24178,7 @@ __metadata: languageName: node linkType: hard -"ip-address@npm:^10.0.1": +"ip-address@npm:10.1.0, ip-address@npm:^10.0.1": version: 10.1.0 resolution: "ip-address@npm:10.1.0" checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 @@ -25294,6 +25535,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^6.1.3": + version: 6.2.1 + resolution: "jose@npm:6.2.1" + checksum: 10c0/456822e00a2ee6b1470fabadd694b237af64b9977aa8a47844aae2841298daefd79bb04ff328cbc3aa3c886761aa72b33bc5c1ad797b31b8368e77a89d09b779 + languageName: node + linkType: hard + "js-base64@npm:^3.6.0": version: 3.7.8 resolution: "js-base64@npm:3.7.8" @@ -25552,6 +25800,13 @@ __metadata: languageName: node linkType: hard +"json-schema-typed@npm:^8.0.2": + version: 8.0.2 + resolution: "json-schema-typed@npm:8.0.2" + checksum: 10c0/89f5e2fb1495483b705c027203c07277ee6bf2665165ad25a9cb55de5af7f72570326d13d32565180781e4083ad5c9688102f222baed7b353c2f39c1e02b0428 + languageName: node + linkType: hard + "json-schema@npm:^0.4.0": version: 0.4.0 resolution: "json-schema@npm:0.4.0" @@ -27027,6 +27282,13 @@ __metadata: languageName: node linkType: hard +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -27434,7 +27696,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.1": +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1, mime-types@npm:^3.0.2": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -29261,7 +29523,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.2, parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 @@ -29692,6 +29954,13 @@ __metadata: languageName: node linkType: hard +"pkce-challenge@npm:^5.0.0": + version: 5.0.1 + resolution: "pkce-challenge@npm:5.0.1" + checksum: 10c0/207f4cb976682f27e8324eb49cf71937c98fbb8341a0b8f6142bc6f664825b30e049a54a21b5c034e823ee3c3d412f10d74bd21de78e17452a6a496c2991f57c + languageName: node + linkType: hard + "pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" @@ -30508,7 +30777,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:~2.0.7": +"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -30611,6 +30880,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.14.0": + version: 6.15.0 + resolution: "qs@npm:6.15.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/ff341078a78a991d8a48b4524d52949211447b4b1ad907f489cac0770cbc346a28e47304455c0320e5fb000f8762d64b03331e3b71865f663bf351bcba8cdb4b + languageName: node + linkType: hard + "quansync@npm:^0.2.7": version: 0.2.11 resolution: "quansync@npm:0.2.11" @@ -30771,6 +31049,18 @@ __metadata: languageName: node linkType: hard +"raw-body@npm:^3.0.0, raw-body@npm:^3.0.1": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" + dependencies: + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.7.0" + unpipe: "npm:~1.0.0" + checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29 + languageName: node + linkType: hard + "raw-loader@npm:^4.0.2": version: 4.0.2 resolution: "raw-loader@npm:4.0.2" @@ -32307,6 +32597,19 @@ __metadata: languageName: node linkType: hard +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" + dependencies: + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10c0/3279de7450c8eae2f6e095e9edacbdeec0abb5cb7249c6e719faa0db2dba43574b4fff5892d9220631c9abaff52dd3cad648cfea2aaace845e1a071915ac8867 + languageName: node + linkType: hard + "rtl-css-js@npm:^1.16.1": version: 1.16.1 resolution: "rtl-css-js@npm:1.16.1" @@ -32586,6 +32889,25 @@ __metadata: languageName: node linkType: hard +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.1 + resolution: "send@npm:1.2.1" + dependencies: + debug: "npm:^4.4.3" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.2" + checksum: 10c0/fbbbbdc902a913d65605274be23f3d604065cfc3ee3d78bf9fc8af1dc9fc82667c50d3d657f5e601ac657bac9b396b50ee97bd29cd55436320cf1cddebdcec72 + languageName: node + linkType: hard + "send@npm:~0.19.0, send@npm:~0.19.1": version: 0.19.2 resolution: "send@npm:0.19.2" @@ -32647,6 +32969,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:^2.2.0": + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" + dependencies: + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10c0/37986096e8572e2dfaad35a3925fa8da0c0969f8814fd7788e84d4d388bc068cf0c06d1658509788e55bed942a6b6d040a8a267fa92bb9ffb1179f8bacde5fd7 + languageName: node + linkType: hard + "serve-static@npm:~1.16.2": version: 1.16.3 resolution: "serve-static@npm:1.16.3" @@ -33260,7 +33594,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:~2.0.1, statuses@npm:~2.0.2": +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.1, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f @@ -34735,6 +35069,17 @@ __metadata: languageName: node linkType: hard +"type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" + dependencies: + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10c0/7f7ec0a060b16880bdad36824ab37c26019454b67d73e8a465ed5a3587440fbe158bc765f0da68344498235c877e7dbbb1600beccc94628ed05599d667951b99 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.3": version: 1.0.3 resolution: "typed-array-buffer@npm:1.0.3" @@ -36433,7 +36778,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:^4.1.11": +"zod@npm:^3.25 || ^4.0, zod@npm:^4.1.11": version: 4.3.6 resolution: "zod@npm:4.3.6" checksum: 10c0/860d25a81ab41d33aa25f8d0d07b091a04acb426e605f396227a796e9e800c44723ed96d0f53a512b57be3d1520f45bf69c0cb3b378a232a00787a2609625307 From 921b0471853bc82b5dc83b2805dfc6279b299030 Mon Sep 17 00:00:00 2001 From: Eloy Coto Date: Tue, 17 Mar 2026 17:33:50 +0100 Subject: [PATCH 2/2] dedupe --- workspaces/x2a/yarn.lock | 45 +++++++++------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/workspaces/x2a/yarn.lock b/workspaces/x2a/yarn.lock index 0bb02897a6..7625d15809 100644 --- a/workspaces/x2a/yarn.lock +++ b/workspaces/x2a/yarn.lock @@ -3777,7 +3777,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-node@npm:^0.10.10": +"@backstage/plugin-permission-node@npm:^0.10.10, @backstage/plugin-permission-node@npm:^0.10.3, @backstage/plugin-permission-node@npm:^0.10.6, @backstage/plugin-permission-node@npm:^0.10.7, @backstage/plugin-permission-node@npm:^0.10.9": version: 0.10.10 resolution: "@backstage/plugin-permission-node@npm:0.10.10" dependencies: @@ -3795,24 +3795,6 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-node@npm:^0.10.3, @backstage/plugin-permission-node@npm:^0.10.6, @backstage/plugin-permission-node@npm:^0.10.7, @backstage/plugin-permission-node@npm:^0.10.9": - version: 0.10.9 - resolution: "@backstage/plugin-permission-node@npm:0.10.9" - dependencies: - "@backstage/backend-plugin-api": "npm:^1.6.2" - "@backstage/config": "npm:^1.3.6" - "@backstage/errors": "npm:^1.2.7" - "@backstage/plugin-auth-node": "npm:^0.6.12" - "@backstage/plugin-permission-common": "npm:^0.9.5" - "@types/express": "npm:^4.17.6" - express: "npm:^4.22.0" - express-promise-router: "npm:^4.1.0" - zod: "npm:^3.25.76" - zod-to-json-schema: "npm:^3.25.1" - checksum: 10c0/c1c0b8245c8877b2720849676c0dfeb3452caeb52ec2f9b1ef122013cf36f58dbce7ea249bb68204aea83aa757435b5f6d730c434e737478b11b0d38191ed20f - languageName: node - linkType: hard - "@backstage/plugin-permission-react@npm:0.4.36": version: 0.4.36 resolution: "@backstage/plugin-permission-react@npm:0.4.36" @@ -25528,14 +25510,7 @@ __metadata: languageName: node linkType: hard -"jose@npm:^6.1.0": - version: 6.1.3 - resolution: "jose@npm:6.1.3" - checksum: 10c0/b9577b4a7a5e84131011c23823db9f5951eae3ba796771a6a2401ae5dd50daf71104febc8ded9c38146aa5ebe94a92ac09c725e699e613ef26949b9f5a8bc30f - languageName: node - linkType: hard - -"jose@npm:^6.1.3": +"jose@npm:^6.1.0, jose@npm:^6.1.3": version: 6.2.1 resolution: "jose@npm:6.2.1" checksum: 10c0/456822e00a2ee6b1470fabadd694b237af64b9977aa8a47844aae2841298daefd79bb04ff328cbc3aa3c886761aa72b33bc5c1ad797b31b8368e77a89d09b779 @@ -30871,21 +30846,21 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.1, qs@npm:^6.9.4, qs@npm:~6.14.0": - version: 6.14.1 - resolution: "qs@npm:6.14.1" +"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:^6.9.4": + version: 6.15.0 + resolution: "qs@npm:6.15.0" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/0e3b22dc451f48ce5940cbbc7c7d9068d895074f8c969c0801ac15c1313d1859c4d738e46dc4da2f498f41a9ffd8c201bd9fb12df67799b827db94cc373d2613 + checksum: 10c0/ff341078a78a991d8a48b4524d52949211447b4b1ad907f489cac0770cbc346a28e47304455c0320e5fb000f8762d64b03331e3b71865f663bf351bcba8cdb4b languageName: node linkType: hard -"qs@npm:^6.14.0": - version: 6.15.0 - resolution: "qs@npm:6.15.0" +"qs@npm:~6.14.0": + version: 6.14.1 + resolution: "qs@npm:6.14.1" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/ff341078a78a991d8a48b4524d52949211447b4b1ad907f489cac0770cbc346a28e47304455c0320e5fb000f8762d64b03331e3b71865f663bf351bcba8cdb4b + checksum: 10c0/0e3b22dc451f48ce5940cbbc7c7d9068d895074f8c969c0801ac15c1313d1859c4d738e46dc4da2f498f41a9ffd8c201bd9fb12df67799b827db94cc373d2613 languageName: node linkType: hard