From 2cf453f32835dd979b85429e6a602926c71f6875 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 21 Apr 2026 11:13:27 +0200 Subject: [PATCH] refactor(catalog-model): rename API v1beta2 to v1alpha2 Signed-off-by: benjdlambert --- .changeset/api-entity-v1alpha2.md | 5 ++ .changeset/api-entity-v1beta2.md | 5 -- packages/catalog-model/report.api.md | 22 ++++---- .../src/kinds/ApiEntityV1alpha1.ts | 12 ++--- ...eta2.test.ts => ApiEntityV1alpha2.test.ts} | 52 +++++++++---------- ...iEntityV1beta2.ts => ApiEntityV1alpha2.ts} | 38 +++++++------- .../src/kinds/apiEntityModel.test.ts | 8 +-- packages/catalog-model/src/kinds/index.ts | 14 ++--- ...on => API.v1alpha2.mcp-server.schema.json} | 6 +-- ...2.schema.json => API.v1alpha2.schema.json} | 6 +-- 10 files changed, 84 insertions(+), 84 deletions(-) create mode 100644 .changeset/api-entity-v1alpha2.md delete mode 100644 .changeset/api-entity-v1beta2.md rename packages/catalog-model/src/kinds/{ApiEntityV1beta2.test.ts => ApiEntityV1alpha2.test.ts} (70%) rename packages/catalog-model/src/kinds/{ApiEntityV1beta2.ts => ApiEntityV1alpha2.ts} (66%) rename packages/catalog-model/src/schema/kinds/{API.v1beta2.mcp-server.schema.json => API.v1alpha2.mcp-server.schema.json} (95%) rename packages/catalog-model/src/schema/kinds/{API.v1beta2.schema.json => API.v1alpha2.schema.json} (95%) diff --git a/.changeset/api-entity-v1alpha2.md b/.changeset/api-entity-v1alpha2.md new file mode 100644 index 0000000000..49e5455a16 --- /dev/null +++ b/.changeset/api-entity-v1alpha2.md @@ -0,0 +1,5 @@ +--- +'@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. diff --git a/.changeset/api-entity-v1beta2.md b/.changeset/api-entity-v1beta2.md deleted file mode 100644 index f26d46f035..0000000000 --- a/.changeset/api-entity-v1beta2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -Added `backstage.io/v1beta2` 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: `ApiEntityV1beta2`, `ApiEntityV1beta2Default`, `McpServerApiEntityV1beta2`, `McpServerRemote`, `apiEntityV1beta2Validator`, `mcpServerApiEntityV1beta2Validator`, and the `isMcpServerApiEntity` type guard. diff --git a/packages/catalog-model/report.api.md b/packages/catalog-model/report.api.md index bcb161bdff..1f1716136a 100644 --- a/packages/catalog-model/report.api.md +++ b/packages/catalog-model/report.api.md @@ -54,14 +54,14 @@ export { ApiEntityV1alpha1 }; export const apiEntityV1alpha1Validator: KindValidator; // @public -export type ApiEntityV1beta2 = - | ApiEntityV1beta2Default - | McpServerApiEntityV1beta2; +export type ApiEntityV1alpha2 = + | ApiEntityV1alpha2Default + | McpServerApiEntityV1alpha2; // @public -export interface ApiEntityV1beta2Default extends Entity { +export interface ApiEntityV1alpha2Default extends Entity { // (undocumented) - apiVersion: 'backstage.io/v1beta2'; + apiVersion: 'backstage.io/v1alpha2'; // (undocumented) kind: 'API'; // (undocumented) @@ -75,7 +75,7 @@ export interface ApiEntityV1beta2Default extends Entity { } // @public -export const apiEntityV1beta2Validator: KindValidator; +export const apiEntityV1alpha2Validator: KindValidator; // @public export class CommonValidatorFunctions { @@ -299,8 +299,8 @@ export function isLocationEntity( // @public export function isMcpServerApiEntity( - entity: ApiEntityV1beta2, -): entity is McpServerApiEntityV1beta2; + entity: ApiEntityV1alpha2, +): entity is McpServerApiEntityV1alpha2; // @public (undocumented) export function isResourceEntity( @@ -362,9 +362,9 @@ export const locationEntityV1alpha1Validator: KindValidator; export function makeValidator(overrides?: Partial): Validators; // @public -export interface McpServerApiEntityV1beta2 extends Entity { +export interface McpServerApiEntityV1alpha2 extends Entity { // (undocumented) - apiVersion: 'backstage.io/v1beta2'; + apiVersion: 'backstage.io/v1alpha2'; // (undocumented) kind: 'API'; // (undocumented) @@ -378,7 +378,7 @@ export interface McpServerApiEntityV1beta2 extends Entity { } // @public -export const mcpServerApiEntityV1beta2Validator: KindValidator; +export const mcpServerApiEntityV1alpha2Validator: KindValidator; // @public export type McpServerRemote = { diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts index 0ddf108253..2208bd66ec 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts @@ -17,8 +17,8 @@ import { createCatalogModelLayer } from '../model/createCatalogModelLayer'; import type { Entity } from '../entity/Entity'; import jsonSchema from '../schema/kinds/API.v1alpha1.schema.json'; -import defaultSchemaV1beta2 from '../schema/kinds/API.v1beta2.schema.json'; -import mcpServerSchemaV1beta2 from '../schema/kinds/API.v1beta2.mcp-server.schema.json'; +import defaultSchemaV1alpha2 from '../schema/kinds/API.v1alpha2.schema.json'; +import mcpServerSchemaV1alpha2 from '../schema/kinds/API.v1alpha2.mcp-server.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; /** @@ -90,17 +90,17 @@ export const apiEntityModel = createCatalogModelLayer({ schema: { jsonSchema }, }, { - name: 'v1beta2', + name: 'v1alpha2', relationFields: apiRelationFields, - schema: { jsonSchema: defaultSchemaV1beta2 }, + schema: { jsonSchema: defaultSchemaV1alpha2 }, }, { - name: 'v1beta2', + name: 'v1alpha2', specType: 'mcp-server', description: 'An MCP (Model Context Protocol) server exposed as an API entity.', relationFields: apiRelationFields, - schema: { jsonSchema: mcpServerSchemaV1beta2 }, + schema: { jsonSchema: mcpServerSchemaV1alpha2 }, }, ], }); diff --git a/packages/catalog-model/src/kinds/ApiEntityV1beta2.test.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha2.test.ts similarity index 70% rename from packages/catalog-model/src/kinds/ApiEntityV1beta2.test.ts rename to packages/catalog-model/src/kinds/ApiEntityV1alpha2.test.ts index d4dd4f21cb..acf7ccffb8 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1beta2.test.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha2.test.ts @@ -15,19 +15,19 @@ */ import { - ApiEntityV1beta2Default, - McpServerApiEntityV1beta2, - apiEntityV1beta2Validator, - mcpServerApiEntityV1beta2Validator, + ApiEntityV1alpha2Default, + McpServerApiEntityV1alpha2, + apiEntityV1alpha2Validator, + mcpServerApiEntityV1alpha2Validator, isMcpServerApiEntity, -} from './ApiEntityV1beta2'; +} from './ApiEntityV1alpha2'; -describe('apiEntityV1beta2Validator (default specType)', () => { - let entity: ApiEntityV1beta2Default; +describe('apiEntityV1alpha2Validator (default specType)', () => { + let entity: ApiEntityV1alpha2Default; beforeEach(() => { entity = { - apiVersion: 'backstage.io/v1beta2', + apiVersion: 'backstage.io/v1alpha2', kind: 'API', metadata: { name: 'test' }, spec: { @@ -40,36 +40,36 @@ describe('apiEntityV1beta2Validator (default specType)', () => { }; }); - it('accepts a valid v1beta2 string-definition entity', async () => { - await expect(apiEntityV1beta2Validator.check(entity)).resolves.toBe(true); + 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(apiEntityV1beta2Validator.check(entity)).resolves.toBe(false); + await expect(apiEntityV1alpha2Validator.check(entity)).resolves.toBe(false); }); it('rejects missing definition', async () => { delete (entity as any).spec.definition; - await expect(apiEntityV1beta2Validator.check(entity)).rejects.toThrow( + await expect(apiEntityV1alpha2Validator.check(entity)).rejects.toThrow( /definition/, ); }); it('rejects missing lifecycle', async () => { delete (entity as any).spec.lifecycle; - await expect(apiEntityV1beta2Validator.check(entity)).rejects.toThrow( + await expect(apiEntityV1alpha2Validator.check(entity)).rejects.toThrow( /lifecycle/, ); }); }); -describe('mcpServerApiEntityV1beta2Validator', () => { - let entity: McpServerApiEntityV1beta2; +describe('mcpServerApiEntityV1alpha2Validator', () => { + let entity: McpServerApiEntityV1alpha2; beforeEach(() => { entity = { - apiVersion: 'backstage.io/v1beta2', + apiVersion: 'backstage.io/v1alpha2', kind: 'API', metadata: { name: 'test-mcp' }, spec: { @@ -88,50 +88,50 @@ describe('mcpServerApiEntityV1beta2Validator', () => { it('accepts a valid mcp-server entity', async () => { await expect( - mcpServerApiEntityV1beta2Validator.check(entity), + mcpServerApiEntityV1alpha2Validator.check(entity), ).resolves.toBe(true); }); it('rejects wrong spec.type value', async () => { (entity as any).spec.type = 'openapi'; await expect( - mcpServerApiEntityV1beta2Validator.check(entity), + mcpServerApiEntityV1alpha2Validator.check(entity), ).rejects.toThrow(/type/); }); it('rejects missing remotes', async () => { delete (entity as any).spec.remotes; await expect( - mcpServerApiEntityV1beta2Validator.check(entity), + mcpServerApiEntityV1alpha2Validator.check(entity), ).rejects.toThrow(/remotes/); }); it('rejects empty remotes array', async () => { (entity as any).spec.remotes = []; await expect( - mcpServerApiEntityV1beta2Validator.check(entity), + mcpServerApiEntityV1alpha2Validator.check(entity), ).rejects.toThrow(/remotes/); }); it('rejects remote missing url', async () => { (entity as any).spec.remotes[0] = { type: 'stdio' }; await expect( - mcpServerApiEntityV1beta2Validator.check(entity), + mcpServerApiEntityV1alpha2Validator.check(entity), ).rejects.toThrow(/url/); }); it('rejects remote missing type', async () => { (entity as any).spec.remotes[0] = { url: 'http://x' }; await expect( - mcpServerApiEntityV1beta2Validator.check(entity), + mcpServerApiEntityV1alpha2Validator.check(entity), ).rejects.toThrow(/type/); }); }); describe('isMcpServerApiEntity', () => { it('returns true for an mcp-server entity', () => { - const entity: McpServerApiEntityV1beta2 = { - apiVersion: 'backstage.io/v1beta2', + const entity: McpServerApiEntityV1alpha2 = { + apiVersion: 'backstage.io/v1alpha2', kind: 'API', metadata: { name: 'm' }, spec: { @@ -145,8 +145,8 @@ describe('isMcpServerApiEntity', () => { }); it('returns false for a default entity', () => { - const entity: ApiEntityV1beta2Default = { - apiVersion: 'backstage.io/v1beta2', + const entity: ApiEntityV1alpha2Default = { + apiVersion: 'backstage.io/v1alpha2', kind: 'API', metadata: { name: 'a' }, spec: { diff --git a/packages/catalog-model/src/kinds/ApiEntityV1beta2.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha2.ts similarity index 66% rename from packages/catalog-model/src/kinds/ApiEntityV1beta2.ts rename to packages/catalog-model/src/kinds/ApiEntityV1alpha2.ts index 8eafedffb7..08be91243f 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1beta2.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha2.ts @@ -15,29 +15,29 @@ */ import type { Entity } from '../entity/Entity'; -import defaultSchema from '../schema/kinds/API.v1beta2.schema.json'; -import mcpServerSchema from '../schema/kinds/API.v1beta2.mcp-server.schema.json'; +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, v1beta2. Introduces structured subtypes via + * 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 ApiEntityV1beta2 = - | ApiEntityV1beta2Default - | McpServerApiEntityV1beta2; +export type ApiEntityV1alpha2 = + | ApiEntityV1alpha2Default + | McpServerApiEntityV1alpha2; /** - * The default (string-definition) shape for v1beta2 API entities. Applies + * The default (string-definition) shape for v1alpha2 API entities. Applies * when spec.type is anything other than a declared structured subtype. * * @public */ -export interface ApiEntityV1beta2Default extends Entity { - apiVersion: 'backstage.io/v1beta2'; +export interface ApiEntityV1alpha2Default extends Entity { + apiVersion: 'backstage.io/v1alpha2'; kind: 'API'; spec: { type: string; @@ -50,12 +50,12 @@ export interface ApiEntityV1beta2Default extends Entity { /** * An MCP (Model Context Protocol) server represented as an API entity - * (v1beta2, spec.type: 'mcp-server'). + * (v1alpha2, spec.type: 'mcp-server'). * * @public */ -export interface McpServerApiEntityV1beta2 extends Entity { - apiVersion: 'backstage.io/v1beta2'; +export interface McpServerApiEntityV1alpha2 extends Entity { + apiVersion: 'backstage.io/v1alpha2'; kind: 'API'; spec: { type: 'mcp-server'; @@ -77,28 +77,28 @@ export type McpServerRemote = { }; /** - * {@link KindValidator} for the default specType of {@link ApiEntityV1beta2}. + * {@link KindValidator} for the default specType of {@link ApiEntityV1alpha2}. * * @public */ -export const apiEntityV1beta2Validator = +export const apiEntityV1alpha2Validator = ajvCompiledJsonSchemaValidator(defaultSchema); /** - * {@link KindValidator} for the `mcp-server` specType of {@link ApiEntityV1beta2}. + * {@link KindValidator} for the `mcp-server` specType of {@link ApiEntityV1alpha2}. * * @public */ -export const mcpServerApiEntityV1beta2Validator = +export const mcpServerApiEntityV1alpha2Validator = ajvCompiledJsonSchemaValidator(mcpServerSchema); /** - * Type guard: narrows a v1beta2 API entity to the MCP server subtype. + * Type guard: narrows a v1alpha2 API entity to the MCP server subtype. * * @public */ export function isMcpServerApiEntity( - entity: ApiEntityV1beta2, -): entity is McpServerApiEntityV1beta2 { + entity: ApiEntityV1alpha2, +): entity is McpServerApiEntityV1alpha2 { return entity.spec.type === 'mcp-server'; } diff --git a/packages/catalog-model/src/kinds/apiEntityModel.test.ts b/packages/catalog-model/src/kinds/apiEntityModel.test.ts index c78f162c3e..7e58b6bd2d 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 v1beta2 dispatch', () => { +describe('apiEntityModel v1alpha2 dispatch', () => { const model = compileCatalogModel([defaultCatalogEntityModel]); - it('routes mcp-server and non-mcp-server v1beta2 entities to different schemas', () => { + it('routes mcp-server and non-mcp-server v1alpha2 entities to different schemas', () => { const mcp = model.getKind({ kind: 'API', - apiVersion: 'backstage.io/v1beta2', + apiVersion: 'backstage.io/v1alpha2', spec: { type: 'mcp-server' }, }); const openapi = model.getKind({ kind: 'API', - apiVersion: 'backstage.io/v1beta2', + apiVersion: 'backstage.io/v1alpha2', spec: { type: 'openapi' }, }); diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index 1898c7900e..596671e327 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -20,16 +20,16 @@ export type { ApiEntityV1alpha1, } from './ApiEntityV1alpha1'; export { - apiEntityV1beta2Validator, + apiEntityV1alpha2Validator, isMcpServerApiEntity, - mcpServerApiEntityV1beta2Validator, -} from './ApiEntityV1beta2'; + mcpServerApiEntityV1alpha2Validator, +} from './ApiEntityV1alpha2'; export type { - ApiEntityV1beta2, - ApiEntityV1beta2Default, - McpServerApiEntityV1beta2, + ApiEntityV1alpha2, + ApiEntityV1alpha2Default, + McpServerApiEntityV1alpha2, McpServerRemote, -} from './ApiEntityV1beta2'; +} from './ApiEntityV1alpha2'; export { componentEntityV1alpha1Validator } from './ComponentEntityV1alpha1'; export type { ComponentEntityV1alpha1 as ComponentEntity, diff --git a/packages/catalog-model/src/schema/kinds/API.v1beta2.mcp-server.schema.json b/packages/catalog-model/src/schema/kinds/API.v1alpha2.mcp-server.schema.json similarity index 95% rename from packages/catalog-model/src/schema/kinds/API.v1beta2.mcp-server.schema.json rename to packages/catalog-model/src/schema/kinds/API.v1alpha2.mcp-server.schema.json index 0567a9d0c5..7155171e83 100644 --- a/packages/catalog-model/src/schema/kinds/API.v1beta2.mcp-server.schema.json +++ b/packages/catalog-model/src/schema/kinds/API.v1alpha2.mcp-server.schema.json @@ -1,10 +1,10 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "$id": "ApiV1beta2McpServer", + "$id": "ApiV1alpha2McpServer", "description": "An MCP (Model Context Protocol) server exposed as an API entity. See RFC backstage/backstage#32062.", "examples": [ { - "apiVersion": "backstage.io/v1beta2", + "apiVersion": "backstage.io/v1alpha2", "kind": "API", "metadata": { "name": "backstage-mcp-actions", @@ -33,7 +33,7 @@ "required": ["spec"], "properties": { "apiVersion": { - "enum": ["backstage.io/v1beta2"] + "enum": ["backstage.io/v1alpha2"] }, "kind": { "enum": ["API"] diff --git a/packages/catalog-model/src/schema/kinds/API.v1beta2.schema.json b/packages/catalog-model/src/schema/kinds/API.v1alpha2.schema.json similarity index 95% rename from packages/catalog-model/src/schema/kinds/API.v1beta2.schema.json rename to packages/catalog-model/src/schema/kinds/API.v1alpha2.schema.json index 2ce7b5bc0e..848af6627e 100644 --- a/packages/catalog-model/src/schema/kinds/API.v1beta2.schema.json +++ b/packages/catalog-model/src/schema/kinds/API.v1alpha2.schema.json @@ -1,10 +1,10 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "$id": "ApiV1beta2", + "$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/v1beta2", + "apiVersion": "backstage.io/v1alpha2", "kind": "API", "metadata": { "name": "artist-api", @@ -34,7 +34,7 @@ "required": ["spec"], "properties": { "apiVersion": { - "enum": ["backstage.io/v1beta2"] + "enum": ["backstage.io/v1alpha2"] }, "kind": { "enum": ["API"]