From 1443f1dfeb36e6b16451dbcd92e32410f0049a15 Mon Sep 17 00:00:00 2001 From: Feng Qian Date: Tue, 29 Sep 2026 08:20:40 -0700 Subject: [PATCH] fix(mcp): link the public repository from the registry manifest server.json omitted `repository` because the server's source was private. 0.2.0 shipped one pointing at a repo that 404'd for everyone outside the org, and the field was dropped rather than publish a link nobody could open. ShiplightAI/shiplight-cli is public now and holds this source, so the premise is gone and registry readers have no way to reach the code. Both the url and `subfolder: apps/mcp-server` come from the instructions the existing tests left for this moment; the url returns HTTP 200 to an anonymous request, and `mcp-publisher validate` accepts the manifest with subfolder included. Two tests pinned the field absent and each documented how to invert it. Both now assert the link, and both fail against the pre-change manifest. The well-formedness guard in server-json-registry-manifest.test.ts was written to be vacuous while the field was missing, so it activates on its own. The publish preflight already fetches the url anonymously and blocks on a 4xx, so a regression to a private or wrong repo fails the release rather than shipping another broken link. This reaches the registry with 0.2.4; the published 0.2.3 entry keeps the manifest it was published with. --- .../scripts/publish-mcp-registry.ts | 7 +++-- .../mcp-server/scripts/repositoryLink.test.ts | 16 +++++----- apps/mcp-server/scripts/repositoryLink.ts | 15 ++++++---- apps/mcp-server/server.json | 5 ++++ .../server-json-registry-manifest.test.ts | 30 +++++++++---------- 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/apps/mcp-server/scripts/publish-mcp-registry.ts b/apps/mcp-server/scripts/publish-mcp-registry.ts index 69d2dd4..aa77e71 100644 --- a/apps/mcp-server/scripts/publish-mcp-registry.ts +++ b/apps/mcp-server/scripts/publish-mcp-registry.ts @@ -205,9 +205,10 @@ if (npmVersion) { // Repository link — optional, but if present it must be publicly readable // --------------------------------------------------------------------------- // -// The field is deliberately absent from server.json; see repositoryLink.ts for -// why, and for the verdict rules this check applies. It exists because the -// private monorepo URL shipped once already, in 0.2.0. +// server.json points at ShiplightAI/shiplight-cli, which is public; see +// repositoryLink.ts for the verdict rules this check applies. The check exists +// because a private monorepo URL shipped once already, in 0.2.0, and the field +// then stayed absent through 0.2.3 rather than ship a link nobody could open. step('Repository link'); const repoUrl = manifest.repository?.url; let repoStatus = ''; diff --git a/apps/mcp-server/scripts/repositoryLink.test.ts b/apps/mcp-server/scripts/repositoryLink.test.ts index 2ef16d6..f42d2f2 100644 --- a/apps/mcp-server/scripts/repositoryLink.test.ts +++ b/apps/mcp-server/scripts/repositoryLink.test.ts @@ -96,17 +96,15 @@ describe('server.json', () => { readFileSync(join(dirname(dirname(fileURLToPath(import.meta.url))), 'server.json'), 'utf8'), ) as { repository?: { url?: string } }; - it('does not link a repository readers cannot open', () => { + it('links the public repository that holds this server', () => { // Checked without a network call so it holds in any environment: the URL - // itself is the defect, whatever GitHub happens to answer today. The field - // is optional in the registry schema and stays omitted while this - // repository is not public — see the note in - // scripts/__tests__/server-json-registry-manifest.test.ts for how to add it - // back when that changes. + // itself is what matters here, whatever GitHub happens to answer today. The + // publish preflight does fetch it anonymously and blocks on a 4xx, which is + // what catches a regression to a private or wrong repo. assert.equal( - manifest.repository, - undefined, - 'server.json declares a repository, but this repo is not public — the link would 404 for registry readers.', + manifest.repository?.url, + 'https://github.com/ShiplightAI/shiplight-cli', + 'server.json must link the public repo that holds this server, so registry readers can reach its source.', ); }); }); diff --git a/apps/mcp-server/scripts/repositoryLink.ts b/apps/mcp-server/scripts/repositoryLink.ts index 9da089c..1b159dd 100644 --- a/apps/mcp-server/scripts/repositoryLink.ts +++ b/apps/mcp-server/scripts/repositoryLink.ts @@ -21,12 +21,15 @@ const TRANSIENT_4XX = new Set(['408', '425', '429']); /** * Decide what an anonymous fetch of `repository.url` means for a release. * - * `repository` is optional in the registry schema, and ours is deliberately - * absent: the server's source lives in a private monorepo, and a link nobody - * can open is worse than no link at all. Pointing it at some other public - * Shiplight repo is not a fix either — the field exists so reviewers can read - * *this server's* code, so a wrong repo misleads exactly the people it is meant - * to serve. + * `repository` is optional in the registry schema. Ours points at + * ShiplightAI/shiplight-cli, which holds this server's source and is public. + * + * It was absent for 0.2.1 through 0.2.3, when that source sat in a private + * monorepo and a link nobody could open was worse than no link at all. That + * reasoning still governs the verdicts below: a link is only worth publishing + * if an anonymous reader can follow it to *this server's* code, so a private + * repo and a wrong-but-public one are equally useless to the reviewers the + * field exists to serve. * * Only a 4xx blocks, and only a 4xx that means "you cannot see this". A 5xx or * an unreachable host is trouble at GitHub's end or on the runner, and must not diff --git a/apps/mcp-server/server.json b/apps/mcp-server/server.json index 51a8420..6096de2 100644 --- a/apps/mcp-server/server.json +++ b/apps/mcp-server/server.json @@ -3,6 +3,11 @@ "name": "ai.shiplight/shiplight", "title": "Shiplight", "description": "An MCP server that provides browser automation", + "repository": { + "url": "https://github.com/ShiplightAI/shiplight-cli", + "source": "github", + "subfolder": "apps/mcp-server" + }, "version": "0.2.3", "websiteUrl": "https://www.shiplight.ai", "packages": [ diff --git a/scripts/__tests__/server-json-registry-manifest.test.ts b/scripts/__tests__/server-json-registry-manifest.test.ts index b8e0e59..7e84d69 100644 --- a/scripts/__tests__/server-json-registry-manifest.test.ts +++ b/scripts/__tests__/server-json-registry-manifest.test.ts @@ -17,9 +17,10 @@ * the schema was the whole contract cost the 0.2.1 registry publish, after npm * had already been written and could not be taken back. * - * `repository`, by contrast, really is optional and is omitted on purpose: the - * source is private, and a link nobody outside the org can open is worse than - * no link. See the repository tests below. + * `repository`, by contrast, really is optional. It was omitted through 0.2.3, + * when the source was private and a link nobody outside the org could open was + * worse than no link; it now points at the public repo. See the repository + * tests below. */ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; @@ -104,26 +105,25 @@ describe('server.json tracks the published MCP package', () => { assert.match(manifest.websiteUrl ?? '', /^https:\/\//); }); - test('does not advertise a repository readers cannot open', () => { + test('advertises the public repository that holds this server', () => { // The field is optional in the schema — only name, description and version // are required — and 0.2.0 shipped one pointing at a repository that 404'd // for everyone outside the org, so every reader of the registry entry got a - // broken link. It stays omitted while this repository is not public. + // broken link. It stayed omitted through 0.2.3 for that reason. // - // When ShiplightAI/shiplight-cli goes public, add it back: - // "repository": { "url": "https://github.com/ShiplightAI/shiplight-cli", - // "source": "github", "subfolder": "apps/mcp-server" } - // and invert this assertion. The publish workflow's manifest validator - // checks the URL is anonymously reachable, so it will confirm the change. + // ShiplightAI/shiplight-cli is public now and holds this server's source, + // so the link is worth publishing. subfolder points at it inside the + // monorepo. The publish preflight fetches the URL anonymously and blocks on + // a 4xx, so a regression to a private or wrong repo fails the release. // // apps/mcp-server/scripts/repositoryLink.test.ts asserts the same thing in // the mcp-server lane, so a developer running that package's tests sees it // without waiting for this one. - assert.equal( - manifest.repository, - undefined, - 'server.json declares a repository, but this repo is not public — the link would 404 for registry readers', - ); + assert.deepEqual(manifest.repository, { + url: 'https://github.com/ShiplightAI/shiplight-cli', + source: 'github', + subfolder: 'apps/mcp-server', + }); }); test('if a repository is declared at all, it is a well-formed public GitHub link', () => {