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..7625d15809 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,21 +3777,21 @@ __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" +"@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: - "@backstage/backend-plugin-api": "npm:^1.6.2" + "@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.12" - "@backstage/plugin-permission-common": "npm:^0.9.5" + "@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/c1c0b8245c8877b2720849676c0dfeb3452caeb52ec2f9b1ef122013cf36f58dbce7ea249bb68204aea83aa757435b5f6d730c434e737478b11b0d38191ed20f + checksum: 10c0/132ba02e69621fc6e162fd3acd8f320bc67f2d6c0958af4f5386195aed822ea7bba2342ff7fc47df834d8a6aa8bcacee89cfb54bd8b9a0cb8029aba1a4d468da languageName: node linkType: hard @@ -4980,6 +5021,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 +6639,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 +7976,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 +16248,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 +17288,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 +17626,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 +17968,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 +18907,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 +18961,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 +18975,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 +20626,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 +21439,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 +21483,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 +21620,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 +21686,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 +22033,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 +22347,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 +23488,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 +23665,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 +23850,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 +24160,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 @@ -25287,10 +25510,10 @@ __metadata: languageName: node linkType: hard -"jose@npm:^6.1.0": - version: 6.1.3 - resolution: "jose@npm:6.1.3" - checksum: 10c0/b9577b4a7a5e84131011c23823db9f5951eae3ba796771a6a2401ae5dd50daf71104febc8ded9c38146aa5ebe94a92ac09c725e699e613ef26949b9f5a8bc30f +"jose@npm:^6.1.0, jose@npm:^6.1.3": + version: 6.2.1 + resolution: "jose@npm:6.2.1" + checksum: 10c0/456822e00a2ee6b1470fabadd694b237af64b9977aa8a47844aae2841298daefd79bb04ff328cbc3aa3c886761aa72b33bc5c1ad797b31b8368e77a89d09b779 languageName: node linkType: hard @@ -25552,6 +25775,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 +27257,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 +27671,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 +29498,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 +29929,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 +30752,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: @@ -30602,7 +30846,16 @@ __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": +"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/ff341078a78a991d8a48b4524d52949211447b4b1ad907f489cac0770cbc346a28e47304455c0320e5fb000f8762d64b03331e3b71865f663bf351bcba8cdb4b + languageName: node + linkType: hard + +"qs@npm:~6.14.0": version: 6.14.1 resolution: "qs@npm:6.14.1" dependencies: @@ -30771,6 +31024,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 +32572,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 +32864,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 +32944,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 +33569,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 +35044,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 +36753,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