diff --git a/.changeset/api-entity-v1alpha2.md b/.changeset/api-entity-v1alpha2.md index 49e5455a16..070ecbfcde 100644 --- a/.changeset/api-entity-v1alpha2.md +++ b/.changeset/api-entity-v1alpha2.md @@ -2,4 +2,4 @@ '@backstage/catalog-model': minor --- -Added `backstage.io/v1alpha2` of the `API` kind. It behaves identically to `v1alpha1` / `v1beta1` for the existing string-`definition` shape, and additionally supports a new `spec.type: 'mcp-server'` subtype that carries a structured `spec.remotes` list for representing Model Context Protocol (MCP) servers in the catalog. See RFC [#32062](https://github.com/backstage/backstage/issues/32062). New public exports: `ApiEntityV1alpha2`, `ApiEntityV1alpha2Default`, `McpServerApiEntityV1alpha2`, `McpServerRemote`, `apiEntityV1alpha2Validator`, `mcpServerApiEntityV1alpha2Validator`, and the `isMcpServerApiEntity` type guard. +Added `spec.type: 'mcp-server'` as a structured subtype of the `API` kind under `v1alpha1`/`v1beta1`. MCP server entities carry a `spec.remotes` list instead of a string `definition`, for representing Model Context Protocol servers in the catalog. See RFC [#32062](https://github.com/backstage/backstage/issues/32062). New public exports: `McpServerApiEntity`, `McpServerRemote`, `mcpServerApiEntityValidator`, and `isMcpServerApiEntity`. Also adds `addKindVersion` to `CatalogModelLayerBuilder` (alpha) so layers can add new versions or spec types to existing kinds. diff --git a/packages/catalog-model/examples/apis/backstage-mcp-server-api.yaml b/packages/catalog-model/examples/apis/backstage-mcp-server-api.yaml index e4ab5478f3..9a4a986118 100644 --- a/packages/catalog-model/examples/apis/backstage-mcp-server-api.yaml +++ b/packages/catalog-model/examples/apis/backstage-mcp-server-api.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1alpha2 +apiVersion: backstage.io/v1alpha1 kind: API metadata: name: backstage-mcp-server diff --git a/packages/catalog-model/report.api.md b/packages/catalog-model/report.api.md index 1f1716136a..be42c33e7c 100644 --- a/packages/catalog-model/report.api.md +++ b/packages/catalog-model/report.api.md @@ -53,30 +53,6 @@ export { ApiEntityV1alpha1 }; // @public export const apiEntityV1alpha1Validator: KindValidator; -// @public -export type ApiEntityV1alpha2 = - | ApiEntityV1alpha2Default - | McpServerApiEntityV1alpha2; - -// @public -export interface ApiEntityV1alpha2Default extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha2'; - // (undocumented) - kind: 'API'; - // (undocumented) - spec: { - type: string; - lifecycle: string; - owner: string; - system?: string; - definition: string; - }; -} - -// @public -export const apiEntityV1alpha2Validator: KindValidator; - // @public export class CommonValidatorFunctions { static isJsonSafe(value: unknown): boolean; @@ -299,8 +275,8 @@ export function isLocationEntity( // @public export function isMcpServerApiEntity( - entity: ApiEntityV1alpha2, -): entity is McpServerApiEntityV1alpha2; + entity: Entity, +): entity is McpServerApiEntity; // @public (undocumented) export function isResourceEntity( @@ -362,9 +338,9 @@ export const locationEntityV1alpha1Validator: KindValidator; export function makeValidator(overrides?: Partial): Validators; // @public -export interface McpServerApiEntityV1alpha2 extends Entity { +export interface McpServerApiEntity extends Entity { // (undocumented) - apiVersion: 'backstage.io/v1alpha2'; + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; // (undocumented) kind: 'API'; // (undocumented) @@ -378,7 +354,7 @@ export interface McpServerApiEntityV1alpha2 extends Entity { } // @public -export const mcpServerApiEntityV1alpha2Validator: KindValidator; +export const mcpServerApiEntityValidator: KindValidator; // @public export type McpServerRemote = { diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha2.test.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha2.test.ts deleted file mode 100644 index acf7ccffb8..0000000000 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha2.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2026 The Backstage Authors - * - * 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 { - ApiEntityV1alpha2Default, - McpServerApiEntityV1alpha2, - apiEntityV1alpha2Validator, - mcpServerApiEntityV1alpha2Validator, - isMcpServerApiEntity, -} from './ApiEntityV1alpha2'; - -describe('apiEntityV1alpha2Validator (default specType)', () => { - let entity: ApiEntityV1alpha2Default; - - beforeEach(() => { - entity = { - apiVersion: 'backstage.io/v1alpha2', - kind: 'API', - metadata: { name: 'test' }, - spec: { - type: 'openapi', - lifecycle: 'production', - owner: 'me', - definition: 'openapi: "3.0.0"', - system: 'system', - }, - }; - }); - - it('accepts a valid v1alpha2 string-definition entity', async () => { - await expect(apiEntityV1alpha2Validator.check(entity)).resolves.toBe(true); - }); - - it('ignores v1alpha1', async () => { - (entity as any).apiVersion = 'backstage.io/v1alpha1'; - await expect(apiEntityV1alpha2Validator.check(entity)).resolves.toBe(false); - }); - - it('rejects missing definition', async () => { - delete (entity as any).spec.definition; - await expect(apiEntityV1alpha2Validator.check(entity)).rejects.toThrow( - /definition/, - ); - }); - - it('rejects missing lifecycle', async () => { - delete (entity as any).spec.lifecycle; - await expect(apiEntityV1alpha2Validator.check(entity)).rejects.toThrow( - /lifecycle/, - ); - }); -}); - -describe('mcpServerApiEntityV1alpha2Validator', () => { - let entity: McpServerApiEntityV1alpha2; - - beforeEach(() => { - entity = { - apiVersion: 'backstage.io/v1alpha2', - kind: 'API', - metadata: { name: 'test-mcp' }, - spec: { - type: 'mcp-server', - lifecycle: 'experimental', - owner: 'backstage', - remotes: [ - { - type: 'streamable-http', - url: 'http://localhost:7007/api/mcp', - }, - ], - }, - }; - }); - - it('accepts a valid mcp-server entity', async () => { - await expect( - mcpServerApiEntityV1alpha2Validator.check(entity), - ).resolves.toBe(true); - }); - - it('rejects wrong spec.type value', async () => { - (entity as any).spec.type = 'openapi'; - await expect( - mcpServerApiEntityV1alpha2Validator.check(entity), - ).rejects.toThrow(/type/); - }); - - it('rejects missing remotes', async () => { - delete (entity as any).spec.remotes; - await expect( - mcpServerApiEntityV1alpha2Validator.check(entity), - ).rejects.toThrow(/remotes/); - }); - - it('rejects empty remotes array', async () => { - (entity as any).spec.remotes = []; - await expect( - mcpServerApiEntityV1alpha2Validator.check(entity), - ).rejects.toThrow(/remotes/); - }); - - it('rejects remote missing url', async () => { - (entity as any).spec.remotes[0] = { type: 'stdio' }; - await expect( - mcpServerApiEntityV1alpha2Validator.check(entity), - ).rejects.toThrow(/url/); - }); - - it('rejects remote missing type', async () => { - (entity as any).spec.remotes[0] = { url: 'http://x' }; - await expect( - mcpServerApiEntityV1alpha2Validator.check(entity), - ).rejects.toThrow(/type/); - }); -}); - -describe('isMcpServerApiEntity', () => { - it('returns true for an mcp-server entity', () => { - const entity: McpServerApiEntityV1alpha2 = { - apiVersion: 'backstage.io/v1alpha2', - kind: 'API', - metadata: { name: 'm' }, - spec: { - type: 'mcp-server', - lifecycle: 'production', - owner: 'me', - remotes: [{ type: 'stdio', url: 'cmd' }], - }, - }; - expect(isMcpServerApiEntity(entity)).toBe(true); - }); - - it('returns false for a default entity', () => { - const entity: ApiEntityV1alpha2Default = { - apiVersion: 'backstage.io/v1alpha2', - kind: 'API', - metadata: { name: 'a' }, - spec: { - type: 'openapi', - lifecycle: 'production', - owner: 'me', - definition: 'x', - }, - }; - expect(isMcpServerApiEntity(entity)).toBe(false); - }); -}); diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha2.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha2.ts deleted file mode 100644 index ce2296982f..0000000000 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha2.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2026 The Backstage Authors - * - * 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 { createCatalogModelLayer } from '../model/createCatalogModelLayer'; -import type { Entity } from '../entity/Entity'; -import defaultSchema from '../schema/kinds/API.v1alpha2.schema.json'; -import mcpServerSchema from '../schema/kinds/API.v1alpha2.mcp-server.schema.json'; -import { ajvCompiledJsonSchemaValidator } from './util'; - -/** - * Backstage API kind entity, v1alpha2. Introduces structured subtypes via - * spec.type, starting with 'mcp-server'. Other values of spec.type continue - * to use the string-definition shape. - * - * @public - */ -export type ApiEntityV1alpha2 = - | ApiEntityV1alpha2Default - | McpServerApiEntityV1alpha2; - -/** - * The default (string-definition) shape for v1alpha2 API entities. Applies - * when spec.type is anything other than a declared structured subtype. - * - * @public - */ -export interface ApiEntityV1alpha2Default extends Entity { - apiVersion: 'backstage.io/v1alpha2'; - kind: 'API'; - spec: { - type: string; - lifecycle: string; - owner: string; - system?: string; - definition: string; - }; -} - -/** - * An MCP (Model Context Protocol) server represented as an API entity - * (v1alpha2, spec.type: 'mcp-server'). - * - * @public - */ -export interface McpServerApiEntityV1alpha2 extends Entity { - apiVersion: 'backstage.io/v1alpha2'; - kind: 'API'; - spec: { - type: 'mcp-server'; - lifecycle: string; - owner: string; - system?: string; - remotes: McpServerRemote[]; - }; -} - -/** - * A transport endpoint for an MCP server. - * - * @public - */ -export type McpServerRemote = { - type: string; - url: string; -}; - -/** - * {@link KindValidator} for the default specType of {@link ApiEntityV1alpha2}. - * - * @public - */ -export const apiEntityV1alpha2Validator = - ajvCompiledJsonSchemaValidator(defaultSchema); - -/** - * {@link KindValidator} for the `mcp-server` specType of {@link ApiEntityV1alpha2}. - * - * @public - */ -export const mcpServerApiEntityV1alpha2Validator = - ajvCompiledJsonSchemaValidator(mcpServerSchema); - -/** - * Type guard: narrows a v1alpha2 API entity to the MCP server subtype. - * - * @public - */ -export function isMcpServerApiEntity( - entity: ApiEntityV1alpha2, -): entity is McpServerApiEntityV1alpha2 { - return entity.spec.type === 'mcp-server'; -} - -const apiRelationFields = [ - { - selector: { path: 'spec.owner' }, - relation: 'ownedBy', - defaultKind: 'Group', - defaultNamespace: 'inherit' as const, - allowedKinds: ['Group', 'User'], - }, - { - selector: { path: 'spec.system' }, - relation: 'partOf', - defaultKind: 'System', - defaultNamespace: 'inherit' as const, - }, -]; - -/** - * Extends the catalog model with v1alpha2 versions of the API kind. - * - * @alpha - */ -export const apiEntityV1alpha2Model = createCatalogModelLayer({ - layerId: 'catalog.backstage.io/kind-api-v1alpha2', - builder: model => { - model.addKindVersion({ - kind: 'API', - versions: [ - { - name: 'v1alpha2', - relationFields: apiRelationFields, - schema: { jsonSchema: defaultSchema }, - }, - { - name: 'v1alpha2', - specType: 'mcp-server', - description: - 'An MCP (Model Context Protocol) server exposed as an API entity.', - relationFields: apiRelationFields, - schema: { jsonSchema: mcpServerSchema }, - }, - ], - }); - }, -}); diff --git a/packages/catalog-model/src/kinds/McpServerApiEntity.test.ts b/packages/catalog-model/src/kinds/McpServerApiEntity.test.ts new file mode 100644 index 0000000000..60498b1c3d --- /dev/null +++ b/packages/catalog-model/src/kinds/McpServerApiEntity.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright 2026 The Backstage Authors + * + * 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 { + McpServerApiEntity, + mcpServerApiEntityValidator, + isMcpServerApiEntity, +} from './McpServerApiEntity'; + +describe('mcpServerApiEntityValidator', () => { + let entity: McpServerApiEntity; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { name: 'test-mcp' }, + spec: { + type: 'mcp-server', + lifecycle: 'experimental', + owner: 'backstage', + remotes: [ + { + type: 'streamable-http', + url: 'http://localhost:7007/api/mcp', + }, + ], + }, + }; + }); + + it('accepts a valid mcp-server entity', async () => { + await expect(mcpServerApiEntityValidator.check(entity)).resolves.toBe(true); + }); + + it('accepts v1beta1', async () => { + entity.apiVersion = 'backstage.io/v1beta1'; + await expect(mcpServerApiEntityValidator.check(entity)).resolves.toBe(true); + }); + + it('rejects wrong spec.type value', async () => { + (entity as any).spec.type = 'openapi'; + await expect(mcpServerApiEntityValidator.check(entity)).rejects.toThrow( + /type/, + ); + }); + + it('rejects missing remotes', async () => { + delete (entity as any).spec.remotes; + await expect(mcpServerApiEntityValidator.check(entity)).rejects.toThrow( + /remotes/, + ); + }); + + it('rejects empty remotes array', async () => { + (entity as any).spec.remotes = []; + await expect(mcpServerApiEntityValidator.check(entity)).rejects.toThrow( + /remotes/, + ); + }); + + it('rejects remote missing url', async () => { + (entity as any).spec.remotes[0] = { type: 'stdio' }; + await expect(mcpServerApiEntityValidator.check(entity)).rejects.toThrow( + /url/, + ); + }); + + it('rejects remote missing type', async () => { + (entity as any).spec.remotes[0] = { url: 'http://x' }; + await expect(mcpServerApiEntityValidator.check(entity)).rejects.toThrow( + /type/, + ); + }); +}); + +describe('isMcpServerApiEntity', () => { + it('returns true for an mcp-server entity', () => { + const entity: McpServerApiEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { name: 'm' }, + spec: { + type: 'mcp-server', + lifecycle: 'production', + owner: 'me', + remotes: [{ type: 'stdio', url: 'cmd' }], + }, + }; + expect(isMcpServerApiEntity(entity)).toBe(true); + }); + + it('returns false for a non-mcp-server API entity', () => { + expect( + isMcpServerApiEntity({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { name: 'a' }, + spec: { + type: 'openapi', + lifecycle: 'production', + owner: 'me', + definition: 'x', + }, + }), + ).toBe(false); + }); + + it('returns false for a non-API entity', () => { + expect( + isMcpServerApiEntity({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'c' }, + spec: { type: 'service', lifecycle: 'production', owner: 'me' }, + }), + ).toBe(false); + }); +}); diff --git a/packages/catalog-model/src/kinds/McpServerApiEntity.ts b/packages/catalog-model/src/kinds/McpServerApiEntity.ts new file mode 100644 index 0000000000..4d6ea662f7 --- /dev/null +++ b/packages/catalog-model/src/kinds/McpServerApiEntity.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2026 The Backstage Authors + * + * 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 { createCatalogModelLayer } from '../model/createCatalogModelLayer'; +import type { Entity } from '../entity/Entity'; +import mcpServerSchema from '../schema/kinds/API.v1alpha1.mcp-server.schema.json'; +import { ajvCompiledJsonSchemaValidator } from './util'; + +/** + * An MCP (Model Context Protocol) server represented as an API entity + * (spec.type: 'mcp-server'). + * + * @public + */ +export interface McpServerApiEntity extends Entity { + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + kind: 'API'; + spec: { + type: 'mcp-server'; + lifecycle: string; + owner: string; + system?: string; + remotes: McpServerRemote[]; + }; +} + +/** + * A transport endpoint for an MCP server. + * + * @public + */ +export type McpServerRemote = { + type: string; + url: string; +}; + +/** + * {@link KindValidator} for the `mcp-server` specType of API entities. + * + * @public + */ +export const mcpServerApiEntityValidator = + ajvCompiledJsonSchemaValidator(mcpServerSchema); + +/** + * Type guard: narrows an entity to the MCP server API subtype. + * + * @public + */ +export function isMcpServerApiEntity( + entity: Entity, +): entity is McpServerApiEntity { + return ( + entity.kind === 'API' && + (entity as McpServerApiEntity).spec?.type === 'mcp-server' + ); +} + +/** + * Extends the API kind with the mcp-server specType. + * + * @alpha + */ +export const mcpServerApiEntityModel = createCatalogModelLayer({ + layerId: 'catalog.backstage.io/kind-api-mcp-server', + builder: model => { + model.addKindVersion({ + kind: 'API', + versions: [ + { + name: ['v1alpha1', 'v1beta1'], + specType: 'mcp-server', + description: + 'An MCP (Model Context Protocol) server exposed as an API entity.', + relationFields: [ + { + selector: { path: 'spec.owner' }, + relation: 'ownedBy', + defaultKind: 'Group', + defaultNamespace: 'inherit' as const, + allowedKinds: ['Group', 'User'], + }, + { + selector: { path: 'spec.system' }, + relation: 'partOf', + defaultKind: 'System', + defaultNamespace: 'inherit' as const, + }, + ], + schema: { jsonSchema: mcpServerSchema }, + }, + ], + }); + }, +}); diff --git a/packages/catalog-model/src/kinds/apiEntityModel.test.ts b/packages/catalog-model/src/kinds/apiEntityModel.test.ts index 9189d0a29a..3df75fdd53 100644 --- a/packages/catalog-model/src/kinds/apiEntityModel.test.ts +++ b/packages/catalog-model/src/kinds/apiEntityModel.test.ts @@ -17,18 +17,18 @@ import { compileCatalogModel } from '../model/compileCatalogModel'; import { defaultCatalogEntityModel } from '../model/defaultCatalogEntityModel'; -describe('apiEntityModel v1alpha2 dispatch', () => { +describe('apiEntityModel mcp-server dispatch', () => { const model = compileCatalogModel([defaultCatalogEntityModel]); - it('routes mcp-server and non-mcp-server v1alpha2 entities to different schemas', () => { + it('routes mcp-server and non-mcp-server v1alpha1 entities to different schemas', () => { const mcp = model.getKind({ kind: 'API', - apiVersion: 'backstage.io/v1alpha2', + apiVersion: 'backstage.io/v1alpha1', spec: { type: 'mcp-server' }, }); const openapi = model.getKind({ kind: 'API', - apiVersion: 'backstage.io/v1alpha2', + apiVersion: 'backstage.io/v1alpha1', spec: { type: 'openapi' }, }); @@ -47,15 +47,15 @@ describe('apiEntityModel v1alpha2 dispatch', () => { expect(openapiSpecRequired).not.toContain('remotes'); }); - it('routes a v1alpha1 entity to the existing v1alpha1 schema', () => { - const kind = model.getKind({ + it('routes mcp-server under v1beta1 too', () => { + const mcp = model.getKind({ kind: 'API', - apiVersion: 'backstage.io/v1alpha1', - spec: { type: 'openapi' }, + apiVersion: 'backstage.io/v1beta1', + spec: { type: 'mcp-server' }, }); - expect(kind).toBeDefined(); - const required = (kind!.jsonSchema.properties as any).spec + expect(mcp).toBeDefined(); + const required = (mcp!.jsonSchema.properties as any).spec .required as string[]; - expect(required).toContain('definition'); + expect(required).toContain('remotes'); }); }); diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index 596671e327..d8b70832f0 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -20,16 +20,10 @@ export type { ApiEntityV1alpha1, } from './ApiEntityV1alpha1'; export { - apiEntityV1alpha2Validator, isMcpServerApiEntity, - mcpServerApiEntityV1alpha2Validator, -} from './ApiEntityV1alpha2'; -export type { - ApiEntityV1alpha2, - ApiEntityV1alpha2Default, - McpServerApiEntityV1alpha2, - McpServerRemote, -} from './ApiEntityV1alpha2'; + mcpServerApiEntityValidator, +} from './McpServerApiEntity'; +export type { McpServerApiEntity, McpServerRemote } from './McpServerApiEntity'; export { componentEntityV1alpha1Validator } from './ComponentEntityV1alpha1'; export type { ComponentEntityV1alpha1 as ComponentEntity, diff --git a/packages/catalog-model/src/model/defaultCatalogEntityModel.ts b/packages/catalog-model/src/model/defaultCatalogEntityModel.ts index a54b0cde54..2d0c1f7f7b 100644 --- a/packages/catalog-model/src/model/defaultCatalogEntityModel.ts +++ b/packages/catalog-model/src/model/defaultCatalogEntityModel.ts @@ -15,7 +15,7 @@ */ import { apiEntityModel } from '../kinds/ApiEntityV1alpha1'; -import { apiEntityV1alpha2Model } from '../kinds/ApiEntityV1alpha2'; +import { mcpServerApiEntityModel } from '../kinds/McpServerApiEntity'; import { componentEntityModel } from '../kinds/ComponentEntityV1alpha1'; import { domainEntityModel } from '../kinds/DomainEntityV1alpha1'; import { groupEntityModel } from '../kinds/GroupEntityV1alpha1'; @@ -37,7 +37,7 @@ export const defaultCatalogEntityModel = createCatalogModelLayer({ layerId: 'catalog.backstage.io/default-entity-model', builder: model => { model.import(apiEntityModel); - model.import(apiEntityV1alpha2Model); + model.import(mcpServerApiEntityModel); model.import(componentEntityModel); model.import(domainEntityModel); model.import(groupEntityModel); diff --git a/packages/catalog-model/src/schema/kinds/API.v1alpha2.mcp-server.schema.json b/packages/catalog-model/src/schema/kinds/API.v1alpha1.mcp-server.schema.json similarity index 95% rename from packages/catalog-model/src/schema/kinds/API.v1alpha2.mcp-server.schema.json rename to packages/catalog-model/src/schema/kinds/API.v1alpha1.mcp-server.schema.json index 7155171e83..062a7fb929 100644 --- a/packages/catalog-model/src/schema/kinds/API.v1alpha2.mcp-server.schema.json +++ b/packages/catalog-model/src/schema/kinds/API.v1alpha1.mcp-server.schema.json @@ -1,10 +1,10 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "$id": "ApiV1alpha2McpServer", + "$id": "ApiV1alpha1McpServer", "description": "An MCP (Model Context Protocol) server exposed as an API entity. See RFC backstage/backstage#32062.", "examples": [ { - "apiVersion": "backstage.io/v1alpha2", + "apiVersion": "backstage.io/v1alpha1", "kind": "API", "metadata": { "name": "backstage-mcp-actions", @@ -33,7 +33,7 @@ "required": ["spec"], "properties": { "apiVersion": { - "enum": ["backstage.io/v1alpha2"] + "enum": ["backstage.io/v1alpha1", "backstage.io/v1beta1"] }, "kind": { "enum": ["API"] diff --git a/packages/catalog-model/src/schema/kinds/API.v1alpha2.schema.json b/packages/catalog-model/src/schema/kinds/API.v1alpha2.schema.json deleted file mode 100644 index 848af6627e..0000000000 --- a/packages/catalog-model/src/schema/kinds/API.v1alpha2.schema.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "ApiV1alpha2", - "description": "An API describes an interface that can be exposed by a component. The API can be defined in different formats, like OpenAPI, AsyncAPI, GraphQL, gRPC, or other formats.", - "examples": [ - { - "apiVersion": "backstage.io/v1alpha2", - "kind": "API", - "metadata": { - "name": "artist-api", - "description": "Retrieve artist details", - "labels": { - "product_name": "Random value Generator" - }, - "annotations": { - "docs": "https://github.com/..../tree/develop/doc" - } - }, - "spec": { - "type": "openapi", - "lifecycle": "production", - "owner": "artist-relations-team", - "system": "artist-engagement-portal", - "definition": "openapi: \"3.0.0\"\ninfo:..." - } - } - ], - "allOf": [ - { - "$ref": "Entity" - }, - { - "type": "object", - "required": ["spec"], - "properties": { - "apiVersion": { - "enum": ["backstage.io/v1alpha2"] - }, - "kind": { - "enum": ["API"] - }, - "spec": { - "type": "object", - "required": ["type", "lifecycle", "owner", "definition"], - "properties": { - "type": { - "type": "string", - "description": "The type of the API definition.", - "examples": ["openapi", "asyncapi", "graphql", "grpc", "trpc"], - "minLength": 1 - }, - "lifecycle": { - "type": "string", - "description": "The lifecycle state of the API.", - "examples": ["experimental", "production", "deprecated"], - "minLength": 1 - }, - "owner": { - "type": "string", - "description": "An entity reference to the owner of the API.", - "examples": ["artist-relations-team", "user:john.johnson"], - "minLength": 1 - }, - "system": { - "type": "string", - "description": "An entity reference to the system that the API belongs to.", - "minLength": 1 - }, - "definition": { - "type": "string", - "description": "The definition of the API, based on the format defined by the type.", - "minLength": 1 - } - } - } - } - } - ] -}