diff --git a/.github/workflows/mcp-registry-publish.yml b/.github/workflows/mcp-registry-publish.yml index dfac3e62..17d041f6 100644 --- a/.github/workflows/mcp-registry-publish.yml +++ b/.github/workflows/mcp-registry-publish.yml @@ -47,7 +47,7 @@ jobs: - name: Install mcp-publisher run: | ARCHIVE="mcp-publisher_linux_amd64.tar.gz" - curl -fsSL -o "${ARCHIVE}" \ + curl -fsSL --retry 3 --retry-delay 2 -o "${ARCHIVE}" \ "https://github.com/modelcontextprotocol/registry/releases/download/v${MCP_PUBLISHER_VERSION}/${ARCHIVE}" echo "${MCP_PUBLISHER_SHA256} ${ARCHIVE}" | sha256sum --check tar xzf "${ARCHIVE}" mcp-publisher @@ -73,9 +73,21 @@ jobs: PACKAGE_VERSION=$(node -p "require('./package.json').version") SERVER_NAME=$(node -p "require('./server.json').name") # `npm view ` exits 0 with empty output when the field is - # absent and non-zero when the version itself is missing, so npm's own - # error is left to surface rather than being collapsed into "empty". - PUBLISHED_MCP_NAME=$(npm view "repomix@${PACKAGE_VERSION}" mcpName) + # absent and non-zero when the version itself is missing. This workflow + # runs right after npm-publish.yml, so a miss here is more likely to be + # registry propagation lag than a genuinely absent version: retry on the + # non-zero exit, and let npm's own stderr stay in the log either way. + for attempt in 1 2 3 4 5; do + if PUBLISHED_MCP_NAME=$(npm view "repomix@${PACKAGE_VERSION}" mcpName); then + break + fi + if [ "${attempt}" -eq 5 ]; then + echo "::error::repomix@${PACKAGE_VERSION} is not visible on npm" + exit 1 + fi + echo "npm metadata not visible yet, retrying in 10s (${attempt}/5)" + sleep 10 + done if [ -z "${PUBLISHED_MCP_NAME}" ]; then echo "::error::repomix@${PACKAGE_VERSION} is published but has no mcpName field" exit 1 diff --git a/server.json b/server.json index 93520043..ec27cfd2 100644 --- a/server.json +++ b/server.json @@ -20,10 +20,19 @@ "transport": { "type": "stdio" }, + "runtimeArguments": [ + { + "type": "positional", + "value": "-y", + "isRequired": true, + "description": "Skip the npx install prompt, which would otherwise stall the stdio handshake on a cold cache" + } + ], "packageArguments": [ { "type": "positional", "value": "--mcp", + "isRequired": true, "description": "Run Repomix as an MCP server over stdio" } ] diff --git a/tests/mcp/serverJson.test.ts b/tests/mcp/serverJson.test.ts index 732efb1c..bc36be93 100644 --- a/tests/mcp/serverJson.test.ts +++ b/tests/mcp/serverJson.test.ts @@ -35,6 +35,10 @@ const getDeclaredCliFlags = (): Set => { // published package.json and comparing it to the `name` in server.json. That // check only runs at publish time, after the npm version is already published // and therefore immutable, so a mismatch is caught here instead. +// +// Note that the `version` fields committed in server.json are not asserted here: +// mcp-registry-publish.yml overwrites them from package.json before publishing, +// so the committed values are placeholders and drift from package.json by design. describe('server.json (MCP Registry metadata)', () => { test('mcpName in package.json matches the server.json name', () => { expect(packageJson.mcpName).toBe(serverJson.name); @@ -68,4 +72,25 @@ describe('server.json (MCP Registry metadata)', () => { expect(declaredFlags).toContain(flag); } }); + + // Every packaged argument here is a constant needed to start the server, so a + // client that renders only the required arguments must still emit all of them. + test('every packaged argument is marked required', () => { + const npmPackage = serverJson.packages.find((pkg: { registryType: string }) => pkg.registryType === 'npm'); + + for (const arg of npmPackage.packageArguments) { + expect(arg.isRequired).toBe(true); + } + }); + + // runtimeArguments go to npx rather than to repomix, so they are checked + // against the documented launch command instead of against the CLI flags. + // Without `-y`, npx can stall on its install prompt on a cold cache, which + // over stdio is indistinguishable from a server that never starts. + test('the npx runtime arguments match the documented launch command', () => { + const npmPackage = serverJson.packages.find((pkg: { registryType: string }) => pkg.registryType === 'npm'); + + expect(npmPackage.runtimeHint).toBe('npx'); + expect(npmPackage.runtimeArguments.map((arg: { value: string }) => arg.value)).toContain('-y'); + }); });