Skip to content

Commit 44f422b

Browse files
debugmcpdevcynarlabclaude
authored
feat(release): list mcp-debugger in the official MCP Registry (#836)
The official MCP Registry (registry.modelcontextprotocol.io) had no entry for this server (#835). - server.json at the repo root names io.github.debugmcp/mcp-debugger and lists the npm package (npx, stdio) and the Docker image (stdio, with the project bind-mounted at /workspace). The registry's own validator accepts it. - The registry proves ownership at the exact listed versions, so the CLI package declares mcpName and the image carries the io.modelcontextprotocol.server.name label. - release.yml gains mcp-registry-publish. It runs after npm and Docker publish, checks that server.json names the tag's version, and waits for npm to show mcpName (fresh versions took about three minutes to appear at v0.25.0). It then logs in with GitHub Actions OIDC, so no secret is stored, and publishes with a checksum-pinned mcp-publisher v1.8.1. Prerelease tags are skipped. - sync-versions.cjs moves server.json with each release cut, and scripts/check-mcp-registry-metadata.mjs keeps the three copies of the name and the versions consistent (release dry-run, release job, unit tests). No released artifact carries the markers yet, so the first listing publishes with the next release. Claude-Session: https://claude.ai/code/session_01Ng3BuLvrQYMyMp3nqaU9US Co-authored-by: JF <john.franklin@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5e58321 commit 44f422b

12 files changed

Lines changed: 391 additions & 1 deletion

File tree

‎.github/workflows/release.yml‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,70 @@ jobs:
567567
multiple.sigstore.json
568568
if-no-files-found: error
569569

570+
# Official MCP Registry listing (issue #835). The registry proves ownership by
571+
# reading `mcpName` from the npm package and the io.modelcontextprotocol.server.name
572+
# label from the Docker image, at exactly the versions server.json names, so this
573+
# runs after both have published. GitHub Actions OIDC proves the io.github.debugmcp
574+
# namespace, so no secret is stored. Prerelease tags stay off the registry.
575+
mcp-registry-publish:
576+
name: Publish to the MCP Registry
577+
needs: [npm-publish, docker-publish]
578+
if: ${{ !contains(github.event.inputs.ref || github.ref, '-') }}
579+
runs-on: ubuntu-latest
580+
permissions:
581+
contents: read
582+
id-token: write # GitHub OIDC login for the io.github.debugmcp namespace
583+
584+
steps:
585+
- name: Checkout code
586+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
587+
with:
588+
ref: ${{ github.event.inputs.ref || github.ref }}
589+
persist-credentials: false
590+
591+
- name: Setup Node.js
592+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
593+
with:
594+
node-version: '22.x'
595+
596+
- name: Check server.json names this release
597+
env:
598+
RELEASE_REF: ${{ github.event.inputs.ref || github.ref }}
599+
run: node scripts/check-mcp-registry-metadata.mjs --version "${RELEASE_REF#refs/tags/v}"
600+
601+
# A fresh version takes a few minutes to reach npm's read path (about three at
602+
# v0.25.0), and the registry verifies mcpName by reading it, so wait for it.
603+
- name: Wait for the npm version to show mcpName
604+
env:
605+
RELEASE_REF: ${{ github.event.inputs.ref || github.ref }}
606+
run: |
607+
VERSION="${RELEASE_REF#refs/tags/v}"
608+
for attempt in $(seq 1 30); do
609+
NAME=$(npm view "@debugmcp/mcp-debugger@${VERSION}" mcpName 2>/dev/null || true)
610+
if [ "$NAME" = "io.github.debugmcp/mcp-debugger" ]; then
611+
echo "npm shows @debugmcp/mcp-debugger@${VERSION} mcpName=${NAME}"
612+
exit 0
613+
fi
614+
sleep 20
615+
done
616+
echo "::error::@debugmcp/mcp-debugger@${VERSION} did not show mcpName within 10 minutes"
617+
exit 1
618+
619+
- name: Install mcp-publisher (pinned, checksum-verified)
620+
env:
621+
MCP_PUBLISHER_VERSION: v1.8.1
622+
MCP_PUBLISHER_SHA256: a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc
623+
run: |
624+
curl -fsSL -o mcp-publisher.tar.gz \
625+
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_linux_amd64.tar.gz"
626+
echo "${MCP_PUBLISHER_SHA256} mcp-publisher.tar.gz" | sha256sum -c -
627+
tar xzf mcp-publisher.tar.gz mcp-publisher
628+
629+
- name: Publish server.json
630+
run: |
631+
./mcp-publisher login github-oidc
632+
./mcp-publisher publish server.json
633+
570634
create-release:
571635
name: Create GitHub Release
572636
needs: [docker-publish, pypi-publish, npm-publish, provenance]

‎CLAUDE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ A dual-state overlay (`SessionLifecycleState` + `ExecutionState`) is derived fro
323323

324324
### Distribution
325325
- `packages/mcp-debugger/` - Self-contained CLI bundle for npm/npx distribution
326+
- `server.json` - Official MCP Registry listing (`io.github.debugmcp/mcp-debugger`): versions move with `scripts/sync-versions.cjs`; the name must match the CLI's `mcpName` and the Dockerfile's `io.modelcontextprotocol.server.name` label (`scripts/check-mcp-registry-metadata.mjs`); `release.yml`'s `mcp-registry-publish` job publishes it after npm and Docker (OIDC, no secret; skipped for prereleases)
326327

327328
### Supporting Infrastructure
328329
- `src/container/dependencies.ts` - Dependency injection container

‎Dockerfile‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ ENV DEBUG_MCP_DISABLE_LANGUAGES=go,dotnet
187187
# Set application directory
188188
WORKDIR /app
189189

190+
# Ownership proof for the official MCP Registry (issue #835): the registry reads this
191+
# label from the image named in server.json before it accepts a listing.
192+
LABEL io.modelcontextprotocol.server.name="io.github.debugmcp/mcp-debugger"
193+
190194
# Set container marker for runtime
191195
ENV MCP_CONTAINER=true
192196
# The network transports bind loopback by default (issue #680). A container's

‎changelog.d/835.added.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**Listed in the official MCP Registry as `io.github.debugmcp/mcp-debugger`** — the repository carries a `server.json` naming the npm package (run with `npx`, stdio) and the Docker image (stdio, with the project mounted at `/workspace`). The CLI package declares `mcpName` and the image carries the `io.modelcontextprotocol.server.name` label, which the registry checks before it accepts a version. Each release publishes the listing after npm and Docker, logging in with GitHub Actions OIDC, so no secret is stored; prerelease tags are skipped (#835)

‎docs/release-checklist.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ Pre-release validation for mcp-debugger. Run `npm run release:dry-run` to automa
4646

4747
## After Tagging
4848

49-
- [ ] Monitor GitHub Actions → Release workflow (all **6** jobs: build-and-test, docker-publish, npm-publish, pypi-publish, **provenance**, create-release)
49+
- [ ] Monitor GitHub Actions → Release workflow (all **7** jobs: build-and-test, docker-publish, npm-publish, pypi-publish, **provenance**, **mcp-registry-publish** (skipped for prerelease tags), create-release)
5050
- [ ] GitHub Release has all expected assets: one `.tgz` per published package, `multiple.intoto.jsonl`, `multiple.sigstore.json`, `sbom.spdx.json`, `sbom.cyclonedx.json`
5151
- [ ] GitHub Release body has the correct changelog content
5252
- [ ] Verify provenance: `gh attestation verify debugmcp-mcp-debugger-x.y.z.tgz --repo debugmcp/mcp-debugger` (download the asset first)
5353
- [ ] Verify npm: each published package shows the new version with `latest` dist-tag and a provenance badge on npmjs.com; `npm audit signatures` passes in a scratch project that installs them
5454
- [ ] **Run the canary as the release gate**: `gh workflow run canary.yml -f version=x.y.z` and confirm every leg is green (Actions → "Canary (published artifacts)"). It installs the published npm package (default and `--omit=optional` + `CODELLDB_PATH` legs), runs npx, pulls the Docker image, and drives a breakpoint→variables→continue cycle in mock/python/rust on x64/arm64 Linux, arm64 macOS, and Windows — superseding the manual npx/docker spot checks below
5555
- [ ] Fallback manual checks (when the canary can't run): `npx @debugmcp/mcp-debugger@x.y.z stdio` works (if the npx cache misbehaves, `npm install --prefix <tmp-dir>` is the reliable smoke path); `docker pull debugmcp/mcp-debugger:x.y.z` works
5656
- [ ] Verify: PyPI has `debug-mcp-server-launcher==x.y.z`
57+
- [ ] Verify the official MCP Registry lists the version: `curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.debugmcp/mcp-debugger"` shows `x.y.z`. The listing comes from `server.json` (moved by `scripts/sync-versions.cjs`, checked by `scripts/check-mcp-registry-metadata.mjs` in the dry-run); the registry verifies the npm package's `mcpName` and the Docker image's `io.modelcontextprotocol.server.name` label at exactly that version, and the job logs in with GitHub Actions OIDC (no secret) (#835)
5758
- [ ] Verify the pi package surface: on a clean machine with pi installed, `pi install npm:@debugmcp/mcp-debugger@x.y.z` then `pi list` shows the package with the `mcp-debugger` skill and (with `pi-mcp-adapter` present) its MCP server entry
5859
- [ ] **Website content review** — audit https://debugmcp.io against what this release shipped (language matrix, tool count, feature claims, comparison table) and update `debugmcp/website`
5960
- [ ] Update `SECURITY.md` supported-versions table if a new minor line started (should have happened pre-tag)

‎packages/mcp-debugger/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "@debugmcp/mcp-debugger",
3+
"mcpName": "io.github.debugmcp/mcp-debugger",
34
"version": "0.25.0",
45
"description": "Step-through debugging MCP server for LLMs",
56
"homepage": "https://github.com/debugmcp/mcp-debugger",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Types for `check-mcp-registry-metadata.mjs`, so its TypeScript tests see a real API.
3+
*/
4+
5+
/** The registry schema's cap on `description`. */
6+
export const DESCRIPTION_MAX: number;
7+
8+
/** The registry schema's cap on `title`. */
9+
export const TITLE_MAX: number;
10+
11+
/** The server name the Dockerfile's `io.modelcontextprotocol.server.name` label declares, or `null`. */
12+
export function dockerfileServerName(dockerfile: string): string | null;
13+
14+
/** The three sources the check compares, as parsed from a checkout. */
15+
export interface McpRegistrySources {
16+
serverJson: Record<string, any>;
17+
cliPackage: Record<string, any>;
18+
dockerfile: string;
19+
rootVersion: string;
20+
}
21+
22+
/** Every inconsistency between the sources and `version`; empty when consistent. */
23+
export function checkMcpRegistryMetadata(input: {
24+
serverJson: Record<string, any>;
25+
cliPackage: Record<string, any>;
26+
dockerfile: string;
27+
version: string;
28+
}): string[];
29+
30+
/** Read server.json, the CLI package.json, the Dockerfile and the root version from a checkout. */
31+
export function readMcpRegistrySources(root: string): McpRegistrySources;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Consistency check for the official MCP Registry listing (issue #835).
3+
*
4+
* The registry accepts `server.json` only if every package it lists proves ownership at
5+
* exactly the listed version: the npm package's `mcpName` and the Docker image's
6+
* `io.modelcontextprotocol.server.name` label must both equal the server name. Those three
7+
* copies of the name live in three files, and the versions move with every release cut, so
8+
* this check runs in the release dry-run, in the release job before publishing, and in the
9+
* unit tests.
10+
*/
11+
import fs from 'fs';
12+
import path from 'path';
13+
import { fileURLToPath } from 'url';
14+
import { isMain } from './lib/is-main.mjs';
15+
16+
/** The registry's schema caps these fields (server.schema.json 2025-12-11). */
17+
export const DESCRIPTION_MAX = 100;
18+
export const TITLE_MAX = 100;
19+
20+
const LABEL = /^LABEL\s+io\.modelcontextprotocol\.server\.name="([^"]+)"\s*$/m;
21+
22+
/**
23+
* The server name the Dockerfile's label declares, or `null` when there is no label.
24+
*
25+
* @param {string} dockerfile
26+
* @returns {string | null}
27+
*/
28+
export function dockerfileServerName(dockerfile) {
29+
const match = LABEL.exec(dockerfile.replace(/\r\n/g, '\n'));
30+
return match ? match[1] : null;
31+
}
32+
33+
/**
34+
* Every way the three sources disagree with each other or with `version`.
35+
*
36+
* @param {{ serverJson: any, cliPackage: any, dockerfile: string, version: string }} input
37+
* @returns {string[]} problems; empty when the listing is consistent
38+
*/
39+
export function checkMcpRegistryMetadata({ serverJson, cliPackage, dockerfile, version }) {
40+
const problems = [];
41+
const name = serverJson.name;
42+
43+
if (cliPackage.mcpName !== name) {
44+
problems.push(`packages/mcp-debugger/package.json mcpName is ${JSON.stringify(cliPackage.mcpName)}, server.json name is ${JSON.stringify(name)}`);
45+
}
46+
const label = dockerfileServerName(dockerfile);
47+
if (label !== name) {
48+
problems.push(`Dockerfile label io.modelcontextprotocol.server.name is ${JSON.stringify(label)}, server.json name is ${JSON.stringify(name)}`);
49+
}
50+
if (typeof serverJson.description !== 'string' || serverJson.description.length > DESCRIPTION_MAX) {
51+
problems.push(`server.json description must be at most ${DESCRIPTION_MAX} characters`);
52+
}
53+
if (serverJson.title !== undefined && serverJson.title.length > TITLE_MAX) {
54+
problems.push(`server.json title must be at most ${TITLE_MAX} characters`);
55+
}
56+
if (serverJson.version !== version) {
57+
problems.push(`server.json version is ${serverJson.version}, expected ${version}`);
58+
}
59+
60+
const packages = serverJson.packages ?? [];
61+
const npm = packages.find(entry => entry.registryType === 'npm');
62+
const oci = packages.find(entry => entry.registryType === 'oci');
63+
if (!npm) {
64+
problems.push('server.json lists no npm package');
65+
} else {
66+
if (npm.identifier !== cliPackage.name) {
67+
problems.push(`server.json npm identifier is ${npm.identifier}, the CLI package is ${cliPackage.name}`);
68+
}
69+
if (npm.version !== version) problems.push(`server.json npm version is ${npm.version}, expected ${version}`);
70+
}
71+
if (!oci) {
72+
problems.push('server.json lists no oci package');
73+
} else {
74+
const tag = oci.identifier.slice(oci.identifier.lastIndexOf(':') + 1);
75+
if (!oci.identifier.startsWith('docker.io/debugmcp/mcp-debugger:')) {
76+
problems.push(`server.json oci identifier ${oci.identifier} is not docker.io/debugmcp/mcp-debugger:<version>`);
77+
} else if (tag !== version) {
78+
problems.push(`server.json oci tag is ${tag}, expected ${version}`);
79+
}
80+
}
81+
return problems;
82+
}
83+
84+
/** Read the three sources from a checkout. */
85+
export function readMcpRegistrySources(root) {
86+
return {
87+
serverJson: JSON.parse(fs.readFileSync(path.join(root, 'server.json'), 'utf8')),
88+
cliPackage: JSON.parse(fs.readFileSync(path.join(root, 'packages', 'mcp-debugger', 'package.json'), 'utf8')),
89+
dockerfile: fs.readFileSync(path.join(root, 'Dockerfile'), 'utf8'),
90+
rootVersion: JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version
91+
};
92+
}
93+
94+
if (isMain(import.meta.url)) {
95+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
96+
const versionFlag = process.argv.indexOf('--version');
97+
try {
98+
const sources = readMcpRegistrySources(root);
99+
const version = versionFlag !== -1 ? process.argv[versionFlag + 1] : sources.rootVersion;
100+
if (!version) throw new Error('Usage: node scripts/check-mcp-registry-metadata.mjs [--version <x.y.z>]');
101+
const problems = checkMcpRegistryMetadata({ ...sources, version });
102+
if (problems.length > 0) {
103+
console.error(`MCP Registry metadata is inconsistent for ${version}:`);
104+
for (const problem of problems) console.error(` - ${problem}`);
105+
console.error('Run scripts/sync-versions.cjs to move server.json with the release.');
106+
process.exit(1);
107+
}
108+
console.log(`MCP Registry metadata consistent: ${sources.serverJson.name} at ${version}`);
109+
} catch (error) {
110+
console.error(error instanceof Error ? error.message : String(error));
111+
process.exit(1);
112+
}
113+
}

‎scripts/release-dry-run.sh‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ for pkg_dir in "${CODELLDB_PLATFORM_PKGS[@]}"; do
7878
fi
7979
done
8080

81+
# MCP Registry listing (issue #835): server.json, the CLI's mcpName and the Docker
82+
# label must agree, and server.json must name this version (sync-versions moves it).
83+
if REGISTRY_CHECK=$(node scripts/check-mcp-registry-metadata.mjs --version "$ROOT_VER" 2>&1); then
84+
pass "$REGISTRY_CHECK"
85+
else
86+
fail "MCP Registry metadata: $REGISTRY_CHECK"
87+
fi
88+
8189
# --- 2. CHANGELOG has this version with a date ---
8290
echo ""
8391
echo "── CHANGELOG ──"

‎scripts/sync-versions.cjs‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,34 @@ async function syncVersions() {
169169
}
170170
}
171171

172+
// The MCP Registry listing (issue #835) names the published npm version and
173+
// the Docker tag, so server.json moves with every release cut. The registry
174+
// verifies both artifacts at exactly these versions when the release publishes.
175+
const serverJsonPath = path.join(__dirname, '..', 'server.json');
176+
if (fs.existsSync(serverJsonPath)) {
177+
try {
178+
const server = JSON.parse(fs.readFileSync(serverJsonPath, 'utf8'));
179+
const before = JSON.stringify(server);
180+
server.version = targetVersion;
181+
for (const entry of server.packages || []) {
182+
if (entry.registryType === 'oci') {
183+
entry.identifier = entry.identifier.replace(/:[^:/]+$/, `:${targetVersion}`);
184+
} else {
185+
entry.version = targetVersion;
186+
}
187+
}
188+
if (JSON.stringify(server) !== before) {
189+
fs.writeFileSync(serverJsonPath, JSON.stringify(server, null, 2) + '\n');
190+
versionChanges.push(` ✅ server.json (MCP Registry): → ${targetVersion}`);
191+
updatedCount++;
192+
} else {
193+
versionChanges.push(` ⏩ server.json (MCP Registry): already at ${targetVersion}`);
194+
}
195+
} catch (err) {
196+
console.error(` ❌ Error updating server.json: ${err.message}`);
197+
}
198+
}
199+
172200
console.log('📋 Version changes:');
173201
versionChanges.forEach(change => console.log(change));
174202

0 commit comments

Comments
 (0)