Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions scripts/__tests__/mcp-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -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',
);
});
2 changes: 2 additions & 0 deletions scripts/sync-mcp-metadata.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand All @@ -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;
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion server.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
"version": "0.20.11-dev",
"transport": {
"type": "stdio"
}
},
"packageArguments": [
{
"type": "positional",
"value": "mcp"
}
]
}
]
}
3 changes: 3 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading