Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4c786c8
feat(#4795): implement MCP registry annotation projection
fullsend-ai-coder[bot] Sep 17, 2026
b26c23b
fix(#4795): address review feedback on annotation projection
fullsend-ai-coder[bot] Sep 17, 2026
511f7c9
fix(#4795): address review feedback on annotation projection
fullsend-ai-coder[bot] Sep 17, 2026
4511619
refactor(#4795): split annotation projection scalar walker
michael-valdron Sep 17, 2026
03647c4
refactor(#4795): split projectAnnotations resolution pipeline
michael-valdron Sep 17, 2026
b2c8575
test(#4795): use toHaveLength for collision key count assertion
michael-valdron Sep 17, 2026
4e98c17
fix(#4795): address review feedback on annotation projection
michael-valdron Sep 17, 2026
0636e0a
fix(#4795): address review feedback on annotation projection
fullsend-ai-coder[bot] Sep 17, 2026
5593869
fix(#4795): share URL parse helper and document D9 schema refs
michael-valdron Sep 17, 2026
821d6c1
fix(#4795): address SonarCloud findings in util helpers
michael-valdron Sep 18, 2026
3cdcf74
test(#4795): add rewritten MCP registry server.json example fixtures
michael-valdron Sep 18, 2026
ed34c0b
fix(#4795): validate server.json via $schema on mapping entrypoints
michael-valdron Sep 18, 2026
b8a4988
feat(#4795): allow D8 placeholder URL override before websiteUrl
michael-valdron Sep 18, 2026
e215cf0
fix(#4795): remove 'Step XX:' prefixes from annotationProjection.ts
michael-valdron Sep 18, 2026
10e0d21
feat(#4795): structurally validate closed McpServerDocument shapes
michael-valdron Sep 18, 2026
16f626c
docs(#4795): expand annotation projection changeset with follow-on fe…
michael-valdron Sep 18, 2026
0f745ba
revert(#4795): restore mcp-registry-server-mapping openspec to pre-br…
michael-valdron Sep 18, 2026
c475f12
refactor(#4795): group annotation projection module constants at file…
michael-valdron Sep 18, 2026
3e5c9fc
fix(#4795): address SonarCloud findings in server.json util validators
michael-valdron Sep 18, 2026
fbda723
fix(#4795): address review feedback on PR #4859
fullsend-ai-coder[bot] Sep 18, 2026
d34a1b9
docs(#4795): add mcp-registry-server-mapping-common plugin README
michael-valdron Sep 18, 2026
fde889d
docs(#4795): pin server.json schema refs to MCP Registry v1.8.1
michael-valdron Sep 18, 2026
ce3fd2d
docs(#4795): document server.json TypeScript shapes
michael-valdron Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common': minor
Comment thread
michael-valdron marked this conversation as resolved.
---

Add MCP Registry annotation projection: recursive scalar-leaf walker that projects unmapped server.json attributes into `modelcontextprotocol.io/*` annotations with D3 key sanitization, D9 secret redaction, D11 URL gating, and D12 null/empty omission. Also add closed schema-aligned `McpServerDocument` typing, structural `assertServerJsonSchema` validation on mapping/projection entrypoints (including required `$schema` with `server.schema.json` basename), and an optional D8 `placeholderRemoteUrl` override (D11-checked before `websiteUrl` fallback).
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# @red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common

Deterministic transform from [MCP Registry](https://github.com/modelcontextprotocol/registry)
**v1.8.1**
[`server.json`](https://raw.githubusercontent.com/modelcontextprotocol/registry/v1.8.1/docs/reference/server-json/draft/server.schema.json)
documents to Backstage `API` entities with `spec.type: mcp-server`.

This common library is a pure mapping contract (no I/O, no registry client).
It is intended for consumers such as a future `mcp-registry-provider` catalog
entity provider.

## Install

```bash
yarn add @red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common
```

## Usage

### Map MCP `server.json` → Backstage `McpServerApiEntity` entity

```ts
import {
mapServerToEntity,
projectAnnotations,
} from '@red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common';
import type { McpServerDocument } from '@red-hat-developer-hub/backstage-plugin-mcp-registry-server-mapping-common';

const doc: McpServerDocument = {
$schema:
'https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json',
name: 'io.github.example/weather',
description: 'Weather MCP server',
version: '1.0.0',
remotes: [{ type: 'streamable-http', url: 'https://example.com/mcp' }],
};

const { entity, consumedPaths, reservedAnnotationKeys } = mapServerToEntity(
doc,
{
owner: 'group:default/platform',
lifecycle: 'production',
// Optional D8 placeholder when no valid remotes remain (tried before websiteUrl)
// placeholderRemoteUrl: 'https://example.com/placeholder',
},
);

const projected = projectAnnotations(
doc,
consumedPaths,
reservedAnnotationKeys,
);

entity.metadata.annotations = {
...entity.metadata.annotations,
...projected,
};
```

`mapServerToEntity` performs direct field mapping (identity, remotes, links,
dedicated annotations) and returns hand-off data for projection.
`projectAnnotations` emits remaining scalars as `modelcontextprotocol.io/*`
annotations without overwriting reserved keys.

### Input requirements

Documents must be structurally valid `McpServerDocument` values:

- `$schema` is required and must be an absolute URL whose basename is
`server.schema.json`
- `name`, `description`, and `version` are required
- Only ServerDetail fields from the draft schema are allowed (closed shape)
- Nested transports, packages, icons, and inputs are type-checked at runtime

Invalid input throws `TypeError` (or `Error` for missing required mapping
fields) with an actionable message.

## `server.json` types

Field-level breakdowns of the MCP Registry **v1.8.1** `server.json` TypeScript
shapes live in [`docs/server-json-types.md`](./docs/server-json-types.md)
(source of truth: [`src/types.ts`](./src/types.ts)).

### Caller defaults (`McpServerMappingDefaults`)

| Field | Default | Purpose |
| ---------------------- | -------------- | ----------------------------------------------- |
| `prefix` | `mcp.registry` | Prefix for derived `metadata.name` |
| `owner` | `unknown` | `spec.owner` |
| `lifecycle` | `production` | `spec.lifecycle` |
| `placeholderRemoteUrl` | _(none)_ | D8 placeholder URL before `websiteUrl` fallback |

## Mapping behavior (summary)

- **Identity** — `metadata.name` from sanitized `name` + `version` (+ optional prefix); `modelcontextprotocol.io/name` and `modelcontextprotocol.io/version` reserved
- **Remotes (D8/D11)** — copy `http`/`https` remotes with non-empty `type`; otherwise synthesize `{ type: "undefined", url }` from `placeholderRemoteUrl` then `websiteUrl`
- **URLs (D11)** — only absolute `http`/`https` are emitted on links, remotes, and projected URL annotations
- **Repository (D10)** — SCM-aware Source Code link + `backstage.io/source-location`; original URL on `modelcontextprotocol.io/repository.url`
- **Projection** — unmapped scalars under `modelcontextprotocol.io/<path>` with key sanitization, collision hashing, D9 secret redaction, and D12 null/empty omission

Design decisions and scenarios live under
[`openspec/changes/mcp-registry-server-mapping/`](../../openspec/changes/mcp-registry-server-mapping/).

## Examples

See [`examples/server-json/`](./examples/server-json/) for rewritten MCP Registry
`server.json` fixtures useful for local testing.

## Development

From the `workspaces/ai-integrations` workspace root:

```bash
# Type-check
yarn tsc

# Unit tests for this package
yarn test -- plugins/mcp-registry-server-mapping-common/src

# Lint / API report (when public exports change)
yarn lint:all
yarn build:api-reports:only
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# `server.json` types

TypeScript shapes for MCP Registry **v1.8.1**
[`server.schema.json`](https://raw.githubusercontent.com/modelcontextprotocol/registry/v1.8.1/docs/reference/server-json/draft/server.schema.json)
live in [`src/types.ts`](../src/types.ts). Each table below mirrors one exported
type; headings link to the declaration in source.

## [`McpServerDocument`](../src/types.ts#L279)

Root `server.json` document (`ServerDetail`). Closed shape — only these fields
are allowed.

| Field | Type | Required | Description |
| ------------- | ---------------------------------------------- | -------- | --------------------------------------------------------------- |
| `$schema` | `string` | yes | Absolute JSON Schema URI whose basename is `server.schema.json` |
| `name` | `string` | yes | Reverse-DNS server name (`namespace/name`, exactly one `/`) |
| `title` | `string` | no | Optional human-readable display name |
| `description` | `string` | yes | Human-readable explanation of server capabilities |
| `version` | `string` | yes | Server version (semver preferred; ranges rejected) |
| `websiteUrl` | `string` | no | Homepage / docs / project website URL |
| `repository` | [`McpServerRepository`](../src/types.ts#L176) | no | Source repository metadata |
| `remotes` | [`McpRegistryRemote`](../src/types.ts#L161)[] | no | Remote transports (`streamable-http` / `sse`) |
| `icons` | [`McpRegistryIcon`](../src/types.ts#L208)[] | no | UI icons |
| `packages` | [`McpRegistryPackage`](../src/types.ts#L230)[] | no | Installable package entries |
| `_meta` | [`McpServerMeta`](../src/types.ts#L260) | no | Reverse-DNS extension metadata |

## [`McpServerRepository`](../src/types.ts#L176)

Repository metadata for browsing and cloning source (`Repository`).

| Field | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------------------------------------------ |
| `url` | `string` | yes | Repository URL (web browse and git clone) |
| `source` | `string` | yes | Hosting service id (`github`, `gitlab`, `bitbucket`, …) |
| `id` | `string` | no | Hosting-service repo id (stable across renames) |
| `subfolder` | `string` | no | Clean relative path from repo root to the server (monorepos) |

## [`McpRegistryRemote`](../src/types.ts#L161)

Remote transport entry (`RemoteTransport`): `streamable-http` or `sse`, plus
optional URL template variables.

| Field | Type | Required | Description |
| ----------- | -------------------------------------------------------------- | -------- | --------------------------------- |
| `type` | `'streamable-http' \| 'sse'` | yes | Remote transport kind |
| `url` | `string` | yes | Endpoint URL template |
| `headers` | [`McpKeyValueInput`](../src/types.ts#L62)[] | no | Optional HTTP headers |
| `variables` | `Record<string, McpInput>` ([`McpInput`](../src/types.ts#L26)) | no | URL template variable definitions |

## [`McpRegistryIcon`](../src/types.ts#L208)

Icon resource for client UIs (`Icon`).

| Field | Type | Required | Description |
| ---------- | ------------------------------------------------------------------------------- | -------- | -------------------------------- |
| `src` | `string` | yes | URI of the icon resource |
| `mimeType` | `'image/png' \| 'image/jpeg' \| 'image/jpg' \| 'image/svg+xml' \| 'image/webp'` | no | MIME type override |
| `sizes` | `string[]` | no | Size hints (e.g. `48x48`, `any`) |
| `theme` | `'light' \| 'dark'` | no | Theme the icon is designed for |

## [`McpRegistryPackage`](../src/types.ts#L230)

Installable package entry (`Package`).

| Field | Type | Required | Description |
| ---------------------- | ------------------------------------------- | -------- | ------------------------------------------------ |
| `registryType` | `string` | yes | Registry kind (`npm`, `pypi`, `cargo`, `oci`, …) |
| `identifier` | `string` | yes | Package name or download URL |
| `transport` | [`McpLocalTransport`](../src/types.ts#L147) | yes | Local / package transport config |
| `version` | `string` | no | Specific package version (no ranges) |
| `registryBaseUrl` | `string` | no | Base URL of the package registry |
| `runtimeHint` | `string` | no | Runtime hint (`npx`, `uvx`, `docker`, …) |
| `fileSha256` | `string` | no | SHA-256 of the package file |
| `environmentVariables` | [`McpKeyValueInput`](../src/types.ts#L62)[] | no | Environment variables for the package |
| `packageArguments` | [`McpArgument`](../src/types.ts#L101)[] | no | Arguments for the package binary |
| `runtimeArguments` | [`McpArgument`](../src/types.ts#L101)[] | no | Arguments for the runtime command |

## [`McpServerMeta`](../src/types.ts#L260)

Extension metadata (`ServerDetail._meta`) with reverse-DNS keys.

| Field | Type | Required | Description |
| ----------------------------------------------------- | ------------------------- | -------- | -------------------------------------------- |
| `io.modelcontextprotocol.registry/publisher-provided` | `Record<string, unknown>` | no | Publisher metadata for downstream registries |
| `[key: string]` | `unknown` | no | Additional reverse-DNS namespaced extensions |

## [`McpLocalTransport`](../src/types.ts#L147)

Local / package transport union (`LocalTransport`).

| Variant | `type` | Description |
| ---------------------------------------------------- | ------------------- | ---------------------------- |
| [`McpStdioTransport`](../src/types.ts#L109) | `'stdio'` | Stdio local transport |
| [`McpStreamableHttpTransport`](../src/types.ts#L119) | `'streamable-http'` | Streamable HTTP transport |
| [`McpSseTransport`](../src/types.ts#L133) | `'sse'` | Server-Sent Events transport |

### [`McpStdioTransport`](../src/types.ts#L109)

| Field | Type | Required | Description |
| ------ | --------- | -------- | ----------- |
| `type` | `'stdio'` | yes | Literal |

### [`McpStreamableHttpTransport`](../src/types.ts#L119)

| Field | Type | Required | Description |
| --------- | ------------------------------------------- | -------- | --------------------- |
| `type` | `'streamable-http'` | yes | Literal |
| `url` | `string` | yes | URL template |
| `headers` | [`McpKeyValueInput`](../src/types.ts#L62)[] | no | Optional HTTP headers |

### [`McpSseTransport`](../src/types.ts#L133)

| Field | Type | Required | Description |
| --------- | ------------------------------------------- | -------- | ------------------------- |
| `type` | `'sse'` | yes | Literal |
| `url` | `string` | yes | SSE endpoint URL template |
| `headers` | [`McpKeyValueInput`](../src/types.ts#L62)[] | no | Optional HTTP headers |

## [`McpInput`](../src/types.ts#L26)

Shared input leaf (`Input`) used by env vars, headers, variables, and arguments.

| Field | Type | Required | Description |
| ------------- | ------------------------------------------------- | -------- | -------------------------------------------- |
| `choices` | `string[]` | no | Allowed values the user must select from |
| `default` | `string` | no | Default value |
| `description` | `string` | no | Human-readable description for clients |
| `format` | `'string' \| 'number' \| 'boolean' \| 'filepath'` | no | Input format hint |
| `isRequired` | `boolean` | no | Whether the input is required |
| `isSecret` | `boolean` | no | Whether the input is a secret value |
| `placeholder` | `string` | no | Placeholder shown during configuration |
| `value` | `string` | no | Fixed value (end users should not configure) |

## [`McpInputWithVariables`](../src/types.ts#L51)

Extends [`McpInput`](../src/types.ts#L26) with nested `{curly_brace}` variables
(`InputWithVariables`).

| Field | Type | Required | Description |
| ----------- | -------------------------------------------------------------- | -------- | --------------------------------- |
| `variables` | `Record<string, McpInput>` ([`McpInput`](../src/types.ts#L26)) | no | Nested variable input definitions |

## [`McpKeyValueInput`](../src/types.ts#L62)

Named key/value input for env vars or headers (`KeyValueInput`). Extends
[`McpInputWithVariables`](../src/types.ts#L51).

| Field | Type | Required | Description |
| ------ | -------- | -------- | ----------------------------------- |
| `name` | `string` | yes | Header or environment variable name |

## [`McpArgument`](../src/types.ts#L101)

Package or runtime argument union (`Argument`).

| Variant | `type` | Description |
| ---------------------------------------------- | -------------- | -------------------------------- |
| [`McpPositionalArgument`](../src/types.ts#L73) | `'positional'` | Positional command-line argument |
| [`McpNamedArgument`](../src/types.ts#L87) | `'named'` | Named flag (`--flag={value}`) |

### [`McpPositionalArgument`](../src/types.ts#L73)

Extends [`McpInputWithVariables`](../src/types.ts#L51).

| Field | Type | Required | Description |
| ------------ | -------------- | -------- | ------------------------------------ |
| `type` | `'positional'` | yes | Literal |
| `isRepeated` | `boolean` | no | Whether the argument may be repeated |
| `valueHint` | `string` | no | Identifier / label for the argument |

### [`McpNamedArgument`](../src/types.ts#L87)

Extends [`McpInputWithVariables`](../src/types.ts#L51).

| Field | Type | Required | Description |
| ------------ | --------- | -------- | ------------------------------------ |
| `type` | `'named'` | yes | Literal |
| `name` | `string` | yes | Flag name, including leading dashes |
| `isRepeated` | `boolean` | no | Whether the argument may be repeated |
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Example `server.json` fixtures

Adapted from the MCP Registry **v1.8.1**
[generic server.json examples](https://github.com/modelcontextprotocol/registry/blob/v1.8.1/docs/reference/server-json/generic-server-json.md),
which conform to the
[`server.schema.json`](https://github.com/modelcontextprotocol/registry/blob/v1.8.1/docs/reference/server-json/draft/server.schema.json)
draft at that tag.

Structure matches the upstream examples; names, versions, URLs, and metadata
values are rewritten for local testing of the mapping library.

| File | Upstream example |
| ------------------------------ | ----------------------------------------- |
| `npm.server.json` | Basic server with NPM package |
| `npm-oci.server.json` | Filesystem server with npm + OCI packages |
| `nuget-positional.server.json` | Constant (fixed) package arguments |
| `remote.server.json` | Remote server |
Loading
Loading