`mobile_list_elements_on_screen` called `response.data.elements.flatMap(...)`
directly. When the device screen is off or locked, `mobilecli dump ui` answers
with `{"status": "ok", "data": {}}`, so `.elements` is `undefined` and the call
died with a raw "Cannot read properties of undefined (reading 'flatMap')"
TypeError and a stack trace.
An agent driving the device got an unactionable internal error instead of
something it could recover from, and the correct next step (wake the device)
was not discoverable from the message, so it typically retried the same failing
call.
Guard both levels of the payload and raise an `ActionableError`, which the tool
wrapper already renders as "<message>. Please fix the issue and try again."
without a stack trace. `DumpUIResponse` now models `data` and `data.elements`
as optional so the guard is type-honest rather than a cast.
An empty `elements` array still resolves to an empty list, so a genuinely empty
screen keeps its previous behaviour.
Closes#455
Co-authored-by: Yi-111-a <42726078+Yi-111-a@users.noreply.github.com>
- mount /mcp on POST only so the GET/DELETE 405 routes are reachable again
- pass onerror to serveStdio so a failing factory is logged, not swallowed
- run the legacy initialize and tools/list tests through createMcpHandler
* fix: serve server/discover for the 2026-07-28 protocol revision
The server advertises support for protocol revision 2026-07-28 in its
handshake, but answers server/discover with 'Method not found' because
the hand-wired transports never mark the connection as serving the
modern era — the SDK only installs the server/discover handler behind
that era gate (via createMcpHandler / serveStdio).
Route both transports through the SDK serving entries:
- stdio: serveStdio() owns the transport and the era decision; a
server/discover probe pins the connection modern, an initialize
handshake keeps serving the 2025-era protocol as before.
- HTTP: createMcpHandler() + toNodeHandler() classify each request;
envelope-carrying requests get a fresh era-marked instance and
legacy traffic is served stateless from the same factory (the same
per-request model as the previous hand-rolled handler).
Fixes#409
* fix: log request-handling failures via onerror callbacks
The previous hand-rolled route logged transport setup and
request-handling failures; the SDK serving entries answer HTTP 500
silently unless configured. Wire both createMcpHandler and
toNodeHandler to the application logger through their onerror
options.
Goose moved off block.github.io/goose, and the stub left behind only
redirects the site root, so both URLs in the install badge now 404 and the
button renders as a broken image. The same paths resolve on goose-docs.ai
and still hand the parameters off to the goose:// extension handler.
Applied to the English, Japanese and Chinese READMEs alike.
Replace the single @modelcontextprotocol/sdk 1.30.0 dependency with the v2
package split:
- @modelcontextprotocol/server -> McpServer, StdioServerTransport (/stdio)
- @modelcontextprotocol/node -> NodeStreamableHTTPServerTransport
- @modelcontextprotocol/express -> createMcpExpressApp
- @modelcontextprotocol/client -> Client, InMemoryTransport (tests only)
Tool registration wraps the raw zod shape in z.object() as v2 requires; the
batch tool already did this at call time. zod bumped to ^4.2.0. Wire
behaviour on stdio and on POST /mcp is unchanged; the server does not opt
into the 2026-07-28 protocol revision.
- add the missing mobile_batch_commands tool to the tool list
- Streamable HTTP section: add the localhost DNS rebinding protection
note, the "old pure-SSE flow is no longer available" sentence, and the
Smithery / horizontal hosts clause
- zh-CN: wrap the privacy policy URL in a markdown link; GitHub was
pulling the full-width 。 into the URL, producing a broken link
- README (en/ja/zh-CN): /mcp only accepts POST; GET and DELETE return 405
- register response close cleanup before connecting and handling, so a
response that closes early still tears down the transport and server
- verify script: wait for the child to exit before deciding on SIGKILL;
child.killed only means a signal was sent
- validate-response: extract the last balanced, parseable JSON object,
ignoring braces inside strings, instead of slicing first { to last }
The prompt asks the agent for bare json, but it sometimes prefixes a
sentence ("All assertions passed.") and the run failed on a passing
test. Extract the outermost {...} instead of requiring the response to
start with it, and match code fences anywhere rather than anchored.
Android package names are case-sensitive and the emulator installs
com.mobilenext.playground, but the prompt asserted the capitalized
com.mobilenext.Playground. Present since the file was added in 349c3c1;
earlier e2e runs passed only because the agent grading the prompt
silently normalized the casing.
Clients still pointing at the old SSE transport got a bare 404 (or a 401
when MOBILEMCP_AUTH was set). Serve 410 Gone with a pointer to the README
instead, registered ahead of the auth middleware so the reason reaches
unauthenticated clients.
Also adds an npm start script.
Replace deprecated SSE transport with stateless MCP Streamable HTTP
using StreamableHTTPServerTransport (sessionIdGenerator undefined) and
createMcpExpressApp for localhost Host/DNS-rebinding protection. Keep
MOBILEMCP_AUTH Bearer middleware and stdio as the default. Closes#98.
- skip 0xff fill bytes that may pad a marker (JPEG B.1.1.2), and reject an
SOF segment shorter than 8 bytes instead of reading garbage dimensions
- declare "coordinates match" only when the raw dimensions are equal, not
when the rounded ratios happen to print as 1
- tool description says the mapping text depends on the device reporting
its screen size
mobile_take_screenshot downscales by default since #418 but returned only
the image. A model reads a position off the smaller image and taps it in
full-size screen coordinates, so every screenshot-derived tap lands short.
The tool result now carries a text block ahead of the image that states the
screenshot size, the screen size, and the multiplier to apply before tapping.
When both agree it says so. When either size is unknown the text is omitted
rather than risk a wrong instruction.
Screenshot dimensions are read from the image bytes: png through the
existing PNG class, jpeg through a small SOF-marker parser (baseline and
progressive). Screen dimensions come from getScreenSize, which is already the
space taps use, so iOS points vs pixels needs no special casing.
Closes#29, closes#163.
Publishing to npm and the MCP registry ran inside the build job, so it
could complete while e2e_test was still running or after it had failed.
Move publishing into a dedicated publish_npm job and make both publish
jobs depend on e2e_test.