Repository navigation
v4.3.0 — Broadcast: one call, many clients
Minor on top of v4.2.1. One feature, no new tool — clientId on every public tool now accepts an array; pass several IDs to fan a single invocation out to multiple connected clients in parallel.
Why
When an iOS sim and an Android emulator are both connected, parity checks ("does this screen render the same on both?", "did the same tap land on the same route?") used to need two round-trips per assertion. Adding a third meta-tool for this would have bloated the surface area; folding it into the existing clientId field keeps the public footprint at six static tools.
What changed
call, wait_until and assert accept clientId as string | string[]. A bare string keeps the single-client behaviour (image content passes through; auto-resolution still applies when omitted). An array — even with one entry — switches the caller into broadcast mode: every listed client is dispatched in parallel and results are aggregated.
// two screenshots in one response
call({ clientId: ["ios-1", "android-1"], tool: "host__screenshot" })
// wait until both clients land on CART
wait_until({
clientId: ["ios-1", "android-1"],
tool: "navigation__get_current_route",
predicate: { op: "equals", path: "name", value: "CART" },
})
// assert the same fiber exists on both platforms
assert({
clientId: ["ios-1", "android-1"],
tool: "fiber_tree__query",
args: { mcpId: "checkout:button:submit" },
predicate: { op: "exists" },
})Response shapes
call— when every per-client result is text, you get a single envelope{ results: [{ clientId, ok, result | error }, ...] }. When any result carries image content (e.g.host__screenshot), per-client## <clientId>headers are interleaved with each client's blocks so each image stays paired with its source.wait_until—{ ok, perClient: [{ clientId, ok, attempts, elapsedMs, matched? | lastResult, lastError?, reason? }, ...] }. Each client polls independently inside the sharedtimeoutMs; overallokis true only when every client matched.assert—{ pass, perClient: [{ clientId, pass, actual?, expected?, op?, path?, message?, result?, error? }, ...] }. Overallpassis true only when every client passed.
Broadcast is not fail-fast: an error or timeout on one client does not abort the others — each gets a complete entry in perClient, so you can see which device misbehaved instead of guessing.
Filter, not broadcast
list_tools and describe_tool also accept the array form, but there it narrows the considered client set (filter for list_tools, canonicalisation pool for describe_tool) rather than triggering a broadcast — they are read-only listings, so "fan out" has no meaning.
Compatibility
Pure addition. Every existing clientId: "ios-1" call keeps the exact same single-client shape, including image-content passthrough. No protocol bump, no in-app changes required.