From 87fadfd58f308ec3fc014c261cdc05f5dd7d66ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 3 Sep 2026 20:36:27 +0200 Subject: [PATCH 1/2] 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. --- scripts/sync-mcp-metadata.mjs | 2 ++ server.json | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/sync-mcp-metadata.mjs b/scripts/sync-mcp-metadata.mjs index 856b51372a..9ce6aff391 100644 --- a/scripts/sync-mcp-metadata.mjs +++ b/scripts/sync-mcp-metadata.mjs @@ -12,6 +12,7 @@ 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.'); @@ -32,6 +33,7 @@ if (Array.isArray(server.packages)) { for (const packageEntry of server.packages) { if (packageEntry?.identifier === pkg.name) { packageEntry.version = expectedVersion; + packageEntry.packageArguments = mcpPackageArguments; } } } diff --git a/server.json b/server.json index 20c5e32a6a..4382bb449a 100644 --- a/server.json +++ b/server.json @@ -15,7 +15,13 @@ "version": "0.20.11-dev", "transport": { "type": "stdio" - } + }, + "packageArguments": [ + { + "type": "positional", + "value": "mcp" + } + ] } ] } From 21083ca3416de24e89607f19b12d0ccd825258a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 4 Sep 2026 09:47:19 +0200 Subject: [PATCH 2/2] 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. --- CHANGELOG.md | 8 +++-- scripts/__tests__/mcp-metadata.test.ts | 48 ++++++++++++++++++++++++++ vitest.config.ts | 3 ++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 scripts/__tests__/mcp-metadata.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1191468c..b545aafb59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,12 @@ writing `airplane_mode_on` and broadcasting `ACTION_AIRPLANE_MODE_CHANGED` — a broadcast Android refuses for non-system callers, so the old path failed *after* writing the setting and left the device reporting airplane mode with the network still up (#2223). The response now reports the - `airplaneMode` the connectivity service holds after the change, and an Android build that does not - expose that command is refused with `UNSUPPORTED_OPERATION` before anything is written. + `airplaneMode` the connectivity service holds after the change, and an Android build that does not + expose that command is refused with `UNSUPPORTED_OPERATION` before anything is written. +- Fixed: the MCP registry entry (`server.json`) now declares the fixed `mcp` subcommand via + `packageArguments`, so a registry-format launcher — the MCP Registry or the website's + `/.well-known/mcp.json` discovery manifest — starts the stdio MCP server. Previously it ran + `agent-device` with no subcommand, i.e. the bare CLI (#2275). - Breaking (0.21): iOS Appium/WebDriver snapshots now expose engine-owned acquisition facts and typed fidelity warnings. The SDK snapshot `truncated` field is optional when Appium cannot report hierarchy completeness; regular snapshots fail closed without valid viewport evidence, while diff --git a/scripts/__tests__/mcp-metadata.test.ts b/scripts/__tests__/mcp-metadata.test.ts new file mode 100644 index 0000000000..32c72a450e --- /dev/null +++ b/scripts/__tests__/mcp-metadata.test.ts @@ -0,0 +1,48 @@ +/** + * Guard for the registry-format launch argument. + * + * The MCP registry entry (and the website's `/.well-known/mcp.json` discovery manifest) tell a + * launcher how to start the server: the npm package, the stdio transport, and + * `packageArguments`. Without the fixed `mcp` positional argument, a client that honors the + * manifest runs `agent-device` with no subcommand — the bare CLI — instead of the stdio MCP + * server (`src/bin.ts` only starts the server for the `mcp` subcommand). + * + * `check:mcp-metadata` compares the file against a regeneration, which proves the corrected file + * is self-consistent; this test owns the invariant directly against the checked-in file and fails + * in both directions — a missing argument and a wrong one. + */ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { test } from 'vitest'; + +const ROOT = join(import.meta.dirname, '..', '..'); + +const EXPECTED_MCP_PACKAGE_ARGUMENTS = [{ type: 'positional', value: 'mcp' }]; + +type ServerPackage = { + registryType?: string; + identifier?: string; + transport?: { type?: string }; + packageArguments?: unknown; +}; + +test('the published registry entry starts the MCP server, not the bare CLI', async () => { + const pkg = JSON.parse(await readFile(join(ROOT, 'package.json'), 'utf8')) as { + name: string; + }; + const server = JSON.parse(await readFile(join(ROOT, 'server.json'), 'utf8')) as { + packages?: ServerPackage[]; + }; + + const entry = (server.packages ?? []).find((candidate) => candidate.identifier === pkg.name); + assert.ok(entry, `server.json must describe the ${pkg.name} npm package`); + assert.equal(entry.registryType, 'npm'); + assert.deepEqual(entry.transport, { type: 'stdio' }); + + assert.deepEqual( + entry.packageArguments, + EXPECTED_MCP_PACKAGE_ARGUMENTS, + 'registry-format launchers must start the stdio MCP server; without the fixed mcp subcommand they run the bare CLI', + ); +}); diff --git a/vitest.config.ts b/vitest.config.ts index e9908d2674..c43702cd7d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -147,6 +147,9 @@ export default defineConfig({ // Publish preparation spawns only fixture-owned scripts and proves both Android // helper families are rebuilt through the shared release/size-report owner. 'scripts/__tests__/prepare-publish-assets.test.ts', + // Parse-only guard on the checked-in registry entry: the npm package must declare + // the fixed mcp subcommand, or registry-format launchers run the bare CLI. + 'scripts/__tests__/mcp-metadata.test.ts', 'scripts/ios-snapshot-benchmark/*.test.ts', // Parses CI configuration only, so this action guard needs no device or subprocess lane. 'test/ci/upload-agent-device-artifacts.test.ts',