fix(mcp): Mark the launch arguments required and add -y for npx

intent(mcp-registry): address PR review before the first publish, since server.json values are immutable once a version lands on the registry

decision(server-json): mark `--mcp` `isRequired: true` — a client rendering only the required arguments would otherwise emit bare `npx repomix`, which runs the packer and writes repomix-output.xml instead of speaking MCP over stdio

decision(server-json): add `runtimeArguments` with `-y`, matching the `npx -y repomix --mcp` in every mcp-server.md; without it npx can stall on its install prompt on a cold cache, which over stdio is indistinguishable from a server that never starts

learned(mcp-registry): confirmed against the live registry that npm-based entries express this as a positional `-y` in `runtimeArguments`, and that `mcp-publisher validate` accepts the new shape

learned(mcp-registry): verified the hand-written verify URL by hand against a real entry — `/v0.1/servers/{encodedName}/versions/{version}` returns the nested `.server.version` the workflow asserts — so the first real publish is no longer the first exercise of that path

decision(mcp-registry): retry the `npm view mcpName` check 5x10s like the registry verify step, since it runs right after npm-publish.yml and a miss there is more likely propagation lag than a genuinely absent version

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kazuki Yamada
2026-08-29 11:19:37 +09:00
parent 6cf5d8d37a
commit 67179c3c28
3 changed files with 50 additions and 4 deletions
+16 -4
View File
@@ -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 <pkg> <field>` 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
+9
View File
@@ -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"
}
]
+25
View File
@@ -35,6 +35,10 @@ const getDeclaredCliFlags = (): Set<string> => {
// 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');
});
});