Files
Michał Pierzchała 658f822c40 fix: encode the mcp subcommand in server.json package arguments (#2275)
* fix: encode the mcp subcommand in server.json package arguments

A registry-format launcher (e.g. one consuming /.well-known/mcp.json or the MCP registry entry) starts the server from the package descriptor only; without the positional "mcp" argument it runs the bare CLI instead of the stdio MCP server (bin.ts only starts the MCP server for the mcp subcommand).

Enforce the argument in scripts/sync-mcp-metadata.mjs so sync and the CI/prepack checks (check:mcp-metadata) keep server.json correct, and regenerate server.json.

* test: own the registry launch-argument invariant; add changelog entry

- scripts/__tests__/mcp-metadata.test.ts asserts the checked-in server.json's agent-device npm package entry declares the exact fixed positional mcp argument (and stays stdio-only), so a missing or wrong argument fails the unit lane in both directions. Wired into the unit-core project include list.
- Changelog: user-visible release fix under Unreleased.
2026-09-04 10:53:04 +02:00

63 lines
1.7 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
const root = process.cwd();
const checkOnly = process.argv.includes('--check');
const packagePath = path.join(root, 'package.json');
const serverPath = path.join(root, 'server.json');
const pkg = readJson(packagePath);
const server = readJson(serverPath);
const expectedName = pkg.mcpName;
const expectedVersion = pkg.version;
const registryDescriptionMaxLength = 100;
const mcpPackageArguments = [{ type: 'positional', value: 'mcp' }];
if (typeof expectedName !== 'string' || expectedName.length === 0) {
fail('package.json must define mcpName.');
}
if (typeof expectedVersion !== 'string' || expectedVersion.length === 0) {
fail('package.json must define version.');
}
if (
typeof server.description === 'string' &&
server.description.length > registryDescriptionMaxLength
) {
fail(`server.json description must be ${registryDescriptionMaxLength} characters or fewer.`);
}
server.name = expectedName;
server.version = expectedVersion;
if (Array.isArray(server.packages)) {
for (const packageEntry of server.packages) {
if (packageEntry?.identifier === pkg.name) {
packageEntry.version = expectedVersion;
packageEntry.packageArguments = mcpPackageArguments;
}
}
}
const next = `${JSON.stringify(server, null, 2)}\n`;
const current = fs.readFileSync(serverPath, 'utf8');
if (checkOnly) {
if (next !== current) {
fail('server.json is out of sync. Run `pnpm sync:mcp-metadata`.');
}
process.exit(0);
}
if (next !== current) {
fs.writeFileSync(serverPath, next);
}
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
function fail(message) {
process.stderr.write(`${message}\n`);
process.exit(1);
}