Repository navigation
feat(mcp): add dcd mcp, one stdio server for cloud tests and cloud devices - #237
Conversation
…ch and models
The first stages of the Agent Devices CLI (dcd plan 3.1 and 3.2).
- --json prints one envelope, {ok, session, data, error}, on success
and on failure.
- Exit codes: 0 ok, 1 usage, 2 failed, 3 limit, 4 session ended or
not found.
- The session id may be left out when exactly one session is open.
- start --app uploads a local build first; an https URL goes to the
API's install-from-URL.
- Progress goes to stderr, with no spinner unless at a terminal.
Hidden from dcd --help until the API reaches production.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
Stage 3.3 of the Agent Devices CLI. - inspect prints the compact screen; screenshot saves the frame to a file and reports its path. - run sends one flow (a file or --yaml) with optional --env KEY=VALUE, and polls a flow that outlasts the API's wait. A failed flow exits 2. - logs prints the app's recent log lines; --follow keeps printing new ones until the session ends. - open opens the monitor at a terminal, and prints its URL otherwise. Running a directory of flows needs an API change first and is refused for now. openBrowser moves out of login.ts so device can share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
Stage 5.3 of Agent Devices. Downloads what a session left behind into ./<session>-artifacts (or -o dir): recording.mp4, device.log, and per action actions/NNN-screenshot.jpg and NNN-screen.json, numbered oldest first. Follows every page. --links lists the signed links instead. Files that cannot be fetched are reported, and the command exits 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
logs --follow, a long run and start --wait reused the token resolved at the start. A dcd login access token lasts about an hour, so a poll past that failed with an auth error. Each poll now calls refreshAuth first; an API key is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
dcd device run <dir> zips the directory (without dot-folders and node_modules), uploads it to both copies through POST /device/bundles/upload-url, registers it with its SHA-256, and runs it by id. --entry picks one flow inside it; without it every flow runs. The zip is encrypted when the org's default or DCD_ENCRYPT says so, as dcd cloud's flow zips are. Needs dcd#1718. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
…vices Agent Devices 4.4. dcd mcp serves the dcd_* tools (the same in-process server dcd-mcp runs) and the device tools, proxied from the hosted <api>/mcp so they are defined once. It adds what only a local process can do: install_app takes app_path (uploaded first) and run takes path (a flow file sent as yaml, or a directory uploaded as a bundle). Uses the CLI's credentials; the hosted /mcp accepts a dcd login session with dcd#1719. Without the hosted endpoint it serves the dcd_* tools only. Read-only mode keeps only read-only device tools. Hidden until the device API is live; dcd-mcp is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
Read-only mode hid them from the tool list, but a call made anyway was still forwarded to the hosted /mcp, which knows nothing of the mode. The bridge now refuses any device tool not annotated readOnlyHint, and the server instructions in read-only mode name only the reading tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
…/device-inspect-run # Conflicts: # src/commands/device.ts # src/gateways/device-gateway.ts # src/types/domain/device-session.types.ts # test/unit/device-command.test.ts
| const { name } = request.params; | ||
| const args = (request.params.arguments ?? {}) as Record<string, unknown>; | ||
| if (localNames.has(name)) { | ||
| return (await local.callTool({ name, arguments: args })) as CallToolResult; |
There was a problem hiding this comment.
Bug: dcd_* calls over 60s time out under dcd mcp. The PR says these tools "behave exactly as before", but that doesn't hold for long calls.
- Cause:
local.callTool(...)is called without request options, so the MCP SDK client applies its default 60s request timeout (DEFAULT_REQUEST_TIMEOUT_MSEC). - Example:
dcd_run_cloud_testwithwait: truepolls every 10s for up towaitTimeoutSeconds, which defaults to 600 and accepts up to 3600 (run-cloud-test.ts). Nothing sends progress notifications while it waits. - What the agent sees: after 60s,
McpError -32001 Request timed outas a raw JSON-RPC error, because this path isn't wrapped inrunTool. By then the test may already be submitted and billing, and the agent never gets the upload id. The in-process handler ignores the cancel and keeps polling. - Under
dcd-mcp: the same call works. - Also affected: a large binary upload without
wait, a slowdcd_download_artifacts, and the remoteclient.callToolon line 216 (a long devicerun).
Suggested fix:
- Take the handler's
extraargument. - Pass request options to both
callToolcalls, e.g.local.callTool({ name, arguments: args }, undefined, { signal: extra.signal, timeout: <≥ 3600s + upload time>, resetTimeoutOnProgress: true }).
Alternatively, call the dcd_* handlers directly rather than through a Client.
There was a problem hiding this comment.
Fixed in 9958924. Both forwarded calls (the in-process dcd_* tools and the hosted device tools) now take the handler's extra and pass { signal: extra.signal, timeout: 2h, resetTimeoutOnProgress: true }. So the calling client's own timeout is what limits a call, and its cancel is passed through. 2h covers dcd_run_cloud_test's 3600s maximum wait plus an upload. I didn't add a test, because it would need a call slower than 60s.
There was a problem hiding this comment.
Added a test in e677477: createBridgeServer takes callTimeoutMs (default 2h), and the test shows a forwarded call that outlasts it times out while a faster one doesn't, even when the agent's own client allows longer.
…/device-artifacts # Conflicts: # src/commands/device.ts # src/gateways/device-gateway.ts # src/types/domain/device-session.types.ts
dcd mcp forwarded every call through an MCP Client with no request options, so the SDK's 60s default applied: dcd_run_cloud_test with wait (up to an hour), a large upload, or a long device run failed with 'Request timed out' while the work carried on. Both forwarded calls now pass the caller's cancel signal and a 2h timeout that resets on progress, so the calling client's own timeout is what limits them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
…/device-run-bundles # Conflicts: # src/commands/device.ts # src/gateways/device-gateway.ts # src/types/domain/device-session.types.ts
createBridgeServer takes callTimeoutMs (default 2h), so a test can show a forwarded call outlasting it times out and a faster one doesn't. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA
…/device-mcp-bridge
Stacked on #235. Agent Devices stage 4.4. Needs devicecloud-dev/dcd#1719 for
dcd loginusers, and dcd#1718 for directories.What
dcd mcpruns one MCP server on stdio with two sets of tools:dcd_*cloud-test tools. These come from the same in-process serverdcd-mcpruns, reached over an in-memory transport, so they behave exactly as before.start_device,inspect_screen,run, …). These are proxied from the hosted<api>/mcp, so they are defined and contract-tested once, in the API. They are fetched when the client lists tools.The bridge adds the inputs a remote server can't serve:
install_appapp_pathdcd device install), then calls the tool withapp_binary_idrunpathyaml. A directory is uploaded as a flow bundle and sent asflow_upload_idThe two tool descriptions get one sentence about these inputs.
Details
getContext):DEVICE_CLOUD_API_KEYor adcd loginsession. The hosted/mcpaccepts the session with dcd#1719. When a session is renewed, the remote connection is remade./mcp): it serves thedcd_*tools only and says why on stderr. A device tool call then returns a tool error.--read-only/DCD_MCP_READONLY=1): only device tools annotatedreadOnlyHintare listed, anddcd_run_cloud_testis left out as before.dcd --helpuntil the device API is live, likedcd devicein feat(device): add dcd device start, list, status, stop, install, launch and models #231. The launch PR (feat(device): list dcd device and dcd mcp in help, and deprecate dcd live #233) should unhide it too.dcd-mcpand itsdcd_*tools are unchanged.Tests
test/unit/mcp-bridge.test.tsruns the bridge against a local stateless Streamable HTTP server that stands in for the hosted/mcp. It covers:yamldcd_*only when the endpoint is unreachablepnpm lint,pnpm typecheck,pnpm test:unit(400 passing, exits cleanly) andpnpm buildpass.🤖 Generated with Claude Code
https://claude.ai/code/session_018tQfgpTsA9UhcopKoDezUA