DeviceMirror / documentation

A connection you can build on.

DeviceMirror connects a local device bridge to a secure cloud workspace. Watch a live WebRTC stream, send an action from your browser, call the HTTP API, or give a compatible MCP client access to the same devices.

The local bridge is responsible for the screen source and action execution. A simulated adapter is included for development and demonstrations. It renders changing frames, encodes real video, and executes requests against its state. The console always labels these devices as simulated.

The phone source and phone actions may be simulated. Authentication, credentials, device ownership, quotas, signaling, API requests, and billing use the real services. A simulated source does not demonstrate physical phone performance.

Your first connected device

  1. Sign in to the console with Google. DeviceMirror creates a workspace owned by your authenticated account.
  2. Select Add device, give it a name, and choose Simulated device.
  3. Copy the connector token shown once. Store it securely on the machine that runs the device bridge.
  4. Run the simulated bridge as described below. Keep the process running while watching or controlling the device.
  5. Open the device in the console and select Start live video. Tap or swipe on the stream, send text, or take a screenshot.

Register each device separately. Each connector token belongs to exactly one device. Deleting a device revokes its connection.

Run without a phone

The simulator runs on your computer and connects outward to the cloud. It uses the same device connector boundary that a physical device adapter implements. Your cloud account must own the device and have available capacity.

cd apps/mock-device
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp config.example.json config.json
chmod 600 config.json
# Set the endpoint and each device's ID and token in config.json.
.venv/bin/python connector.py --config config.json

The repository's apps/mock-device/README.md contains current runtime prerequisites and multi-device options. Keep your connector token out of shell history and shared logs.

To test multiple devices, register another device and start another adapter with its own device ID and connector token. Disconnecting a local process makes that device unavailable; it must reconnect before screenshots and controls can complete.

HTTP API

API requests use HTTPS and a Bearer credential created in Console → API credentials. A browser session cannot be replaced by a connector token; connector tokens are only for the local bridge.

Device discovery
export DM_URL='https://device-mirror.jiulong.hu'
export DM_API_KEY='your-scoped-api-credential'
curl "$DM_URL/v1/devices" \
  -H "Authorization: Bearer $DM_API_KEY"
Method Path Purpose
GET /v1/devices List devices in your credential's scope
GET /v1/devices/{id} Read device details and status
GET /v1/devices/{id}/screenshot Capture a fresh image
POST /v1/devices/{id}/actions Tap, swipe, type, or send a key
POST /v1/devices/{id}/video-sessions Create a bounded live viewing session
GET /v1/me Read workspace entitlement and usage

Download OpenAPI specification ↗

Take a screenshot

curl "$DM_URL/v1/devices/$DM_DEVICE_ID/screenshot" \
  -H "Authorization: Bearer $DM_API_KEY"

# Response includes mimeType, base64 data, width, height.
# This is an on-demand capture, not the live video transport.

Send an action

curl -X POST "$DM_URL/v1/devices/$DM_DEVICE_ID/actions" \
  -H "Authorization: Bearer $DM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"tap","x":195,"y":422}'

# Swipe: pixel coordinates in the device's reported dimensions
# {"type":"swipe","x":195,"y":650,"endX":195,"endY":250,"durationMs":350}
# Type: {"type":"type","text":"Hello from DeviceMirror"}
# Key:  {"type":"key","key":"HOME"}

Use width and height returned by the device. The included simulated phone defaults to 390 × 844. Invalid coordinates and unsupported actions return an error. A request is only successful when the connected adapter confirms it.

Errors

Failures return a structured error.code and error.message. Keep the request ID when contacting your operator. Handle 401 by checking credentials, 403 by checking permissions, 404 by checking device ownership, 409/503 by checking the bridge, and 429 by backing off or checking quota. Do not immediately retry an action if its execution status is unknown.

Connect an MCP client

DeviceMirror exposes an HTTPS Streamable HTTP MCP endpoint. Create an API credential with the scopes and device access your client needs, then configure your MCP client with the endpoint and Bearer header.

Example client configuration
{
  "mcpServers": {
    "device-mirror": {
      "type": "http",
      "url": "https://device-mirror.jiulong.hu/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_SCOPED_API_CREDENTIAL"
      }
    }
  }
}

Configuration keys vary by client. Choose HTTP or Streamable HTTP transport; a local stdio command is not required. The standard tool flow is list devices → capture screenshot → inspect the image → execute an action → capture again.

Keep credentials in your client's secret store or environment substitution mechanism. Never commit the example with a real credential. Revoking a credential in the console removes its ability to make new requests.

Live video

Video uses WebRTC with Cloudflare Realtime SFU. Control requests travel through authenticated API requests and the device's outbound connection. Screenshots are individual captures and are not used as a video substitute.

  1. Send POST /v1/devices/{id}/video-sessions with {}. Save the returned lease ID, ICE servers and expiry; negotiation:"server-offer" identifies the receiving flow.
  2. Create an RTCPeerConnection with those ICE servers and install its track handler. Do not create an initial local offer or pre-add a transceiver for this fresh receiving session.
  3. Send POST /v1/devices/{id}/video-sessions/{leaseId}/tracks with the empty JSON object {}. The service selects the authorized publication and returns an SFU-generated SDP offer.
  4. Apply the returned offer with setRemoteDescription, then call createAnswer and setLocalDescription. Wait for ICE gathering to complete.
  5. Send PUT /v1/devices/{id}/video-sessions/{leaseId}/renegotiate with {sessionDescription:{type:"answer",sdp}}. Continue only after the service accepts that answer. Attach the received track to a video element and verify that frames are playing.
  6. Delete the lease when finished. Each lease permits one track negotiation; start a new lease after an uncertain negotiation failure.

This public pilot is additionally capped at 20 registered simulated devices across the deployment, 2,000 reserved video minutes per UTC month, and 20 model requests per UTC day. These deployment limits apply alongside each account's allowance. Capacity exhaustion returns HTTP 429. Viewing sessions are time limited. The console shows their status and asks you to reconnect after expiry. Starting a session reserves its bounded duration against the monthly video allowance. A device must have a connected publisher before media can flow.

Built-in device agent

The console's agent sends a fresh device screenshot to the operator's configured vision model. A model can request a device action, inspect the result, and respond with its findings. Tool execution uses the same ownership checks and operation limits as the HTTP API.

Model credentials remain on the server. If the operator has not configured a model provider, the console reports that setup is required. It does not substitute a generated demo reply. You remain responsible for the tasks you authorize on your devices.

Access and credentials

Google sign-in establishes your account. Device ownership is enforced by the server. API credentials are stored as hashes and shown only at creation. Choose a name, the minimum required scopes, and optionally individual devices.

Scope Allows
devices:read Discover devices, read status, capture screenshots
devices:control Execute tap, swipe, text, and key actions
devices:video Create and negotiate live video sessions

A connector token authenticates a specific local bridge, not a user or an MCP client. Keep it separate from API credentials. Use a dedicated API credential for each integration so that it can be revoked independently.

Subscriptions and resource limits

A free workspace includes 2 registered devices, 3,000 operations, and 60 video minutes each month. The Pro test plan includes 10 registered devices, 100,000 operations, and 1,000 video minutes per month.

Stripe is currently configured for sandbox subscription testing. The console labels test mode explicitly. A successful sandbox payment is not a live charge.

Checkout and the customer portal are hosted by Stripe. Workspace access changes after the service verifies and synchronizes the subscription. Visiting a success URL alone does not grant Pro access. Refresh the billing page after a change; webhook delivery can take a moment.

Requests also have rate and concurrency limits. Public registration and simulated devices do not bypass these protections. Model provider costs are separate from workspace video and operation allowances.

When a connection needs attention

The device is offline

Check that the local bridge is running, that its URL is correct, and that it uses the connector token for that exact device. Wait for its reconnect attempt or restart the bridge. Deleting a device permanently revokes that registration.

Video does not start

Confirm the device is online and that Realtime configuration is ready. Check your remaining video allowance. Browser or network policies can block WebRTC or autoplay; use the video play control when needed. DeviceMirror reports negotiation failures instead of replacing the video with an animation.

Sign-in returns to an error

Use the same hostname throughout sign-in. The deployment's Google callback is https://device-mirror.jiulong.hu/api/auth/callback/google. An operator should check the OAuth client's approved origins and callback URL, server secrets, and authentication logs.

An integration receives access denied

Check that the credential is active, has the correct scope, and includes the requested device. Devices belonging to another account are not accessible. Create a new scoped credential rather than sharing a connector token.

Pro is not reflected after checkout

Refresh billing status and check Stripe webhook delivery. Only an active, unexpired subscription to the configured price grants Pro. Operators should inspect webhook signature configuration and event processing before changing account data.

Privacy and operational access

DeviceMirror processes account details, connection metadata, usage, and operational logs to provide the service. Screenshots and control inputs pass through the service when requested. A model task sends its screenshot and instructions to the operator's configured model provider.

Only connect devices you own or have permission to operate. Grant integrations the minimum access they need, revoke unused credentials, and avoid entering sensitive personal data into a simulated demonstration. Service administrators can inspect basic user, device, subscription, usage, and runtime records through a server-authorized admin interface.

For repository setup, environment variables, deployment resources, and physical adapter integration, consult the checked-in docs/cloud/ documentation. This page describes the cloud interfaces; physical-device compatibility must be verified with the relevant hardware and local bridge.