How @escrcpy/madb Functions as an MCP Server for Android Automation

@escrcpy/madb implements a Multi-Client Protocol (MCP) server that exposes Android Debug Bridge (ADB) operations via JSON-RPC, enabling AI agents and UI components to control Android devices concurrently through a stateless TCP/WebSocket interface.

In the viarotel-org/escrcpy monorepo, @escrcpy/madb acts as the middleware layer between the Electron main process and Android hardware. It transforms local ADB capabilities into a network-accessible API defined in packages/madb/AGENTS.md, allowing multiple simultaneous clients—including built-in AI copilots, external automation scripts, and renderer processes—to execute commands without blocking the UI thread.

Multi-Client Protocol (MCP) Architecture

The MCP server implements a stateless JSON-RPC interface over TCP sockets. Unlike direct ADB connections that lock to a single client, madb multiplexes commands from multiple agents while maintaining a shared device-state cache.

This architecture relies on four core components located in the shared utilities package:

  • Device Abstraction (packages/shared/src/device.ts): Wraps the 32-tool yadb suite to provide high-level methods for install, push, pull, shell execution, and screen capture.
  • ADBKit Integration (packages/shared/src/adbkit.ts): Low-level wrapper around the adbkit library that manages ADB server connections and binary streams.
  • Network Layer (packages/shared/src/network.ts): Handles TCP socket creation, WebSocket upgrades, and client connection pooling for concurrent session management.
  • Security Module (packages/shared/src/security.ts): Validates all incoming JSON-RPC payloads and sanitizes shell arguments before passing them to the device layer.

Integration with the Electron Main Process

The server runs as a separate Node.js process to prevent ADB I/O from blocking the Electron UI. During the build phase, desktop/electron-builder.config.js copies the compiled bundle from packages/madb/dist into the Electron resources directory at extra/common/madb.

When the application launches, the main process spawns madb as a child process:

import { spawn } from 'node:child_process';
import path from 'node:path';

const madbBin = path.resolve(__dirname, 'extra/common/madb', 'madb.js');
const madb = spawn('node', [madbBin], {
  stdio: ['ignore', 'inherit', 'inherit'],
  env: { ...process.env, NODE_ENV: 'production' },
});

The main process then retrieves the listening port from the child process and exposes it to renderer windows through electron-ipcx:

madb.once('message', ({ port }) => {
  ipcMain.handle('madb-port', () => port);
});

MCP Communication Flow

Client interactions follow a strict request-response pattern with asynchronous state broadcasting:

  1. Connection: A renderer or external agent connects to the TCP/WebSocket endpoint published via electron-ipcx.
  2. Command Execution: The client sends a JSON-RPC request such as {method:"device.install", params:["/path/app.apk"]}.
  3. Translation: madb translates the call to the appropriate adbkit or yadb command in packages/shared/src/device.ts, executes it on the target device, and captures stdout/stderr using the Promise helper (packages/shared/src/promise.ts).
  4. Response: Results are marshalled back over the JSON-RPC channel.
  5. Broadcasting: Side-effects (e.g., new apps appearing, battery changes) trigger broadcasts to all connected clients via the network layer.

Consuming the MCP Server from Client Code

Renderer processes connect via WebSocket after obtaining the port from the main process:

import { ipcxRenderer } from '@escrcpy/electron-ipcx';

const port = await ipcxRenderer.invoke('madb-port');
const socket = new WebSocket(`ws://localhost:${port}`);

const call = (method: string, params?: any[]) =>
  new Promise((resolve, reject) => {
    const id = Math.random().toString(36).slice(2);
    socket.send(JSON.stringify({ jsonrpc: '2.0', id, method, params }));
    const listener = (msg: MessageEvent) => {
      const data = JSON.parse(msg.data);
      if (data.id === id) {
        socket.removeEventListener('message', listener);
        data.error ? reject(data.error) : resolve(data.result);
      }
    };
    socket.addEventListener('message', listener);
  });

// Install an APK
await call('device.install', ['/tmp/app-release.apk']);

Clients also listen for broadcast state updates:

socket.addEventListener('message', e => {
  const data = JSON.parse(e.data);
  if (data.method === 'device.state') {
    console.log('Device state changed:', data.params);
  }
});

Summary

  • @escrcpy/madb implements a stateless MCP server using JSON-RPC over TCP/WebSocket to enable concurrent Android device control.
  • It wraps the adbkit library and yadb tool suite (32 tools) through abstractions defined in packages/shared/src/device.ts and packages/shared/src/adbkit.ts.
  • The server runs as a child process spawned by the Electron main process, with build-time bundling configured in desktop/electron-builder.config.js.
  • electron-ipcx bridges the gap between the main process and renderer windows, securely transmitting the server port.
  • Input validation occurs in packages/shared/src/security.ts, while network multiplexing is handled by packages/shared/src/network.ts.
  • All connected clients receive broadcast updates when device state changes, ensuring UI synchronization across multiple agents.

Frequently Asked Questions

What does MCP stand for in @escrcpy/madb?

MCP stands for Multi-Client Protocol. It defines a network interface that allows multiple autonomous agents—such as AI copilots, automation scripts, and UI components—to issue ADB commands simultaneously without interfering with each other's sessions.

How does madb communicate with Android devices?

The server communicates through the adbkit wrapper defined in packages/shared/src/adbkit.ts, which manages ADB server connections. For extended functionality, it leverages the yadb suite—a collection of 32 specialized tools—to perform operations like high-speed file transfer, screen capture, and deep system queries.

Can multiple AI agents control the same device concurrently?

Yes. Because madb is stateless and uses JSON-RPC multiplexing via packages/shared/src/network.ts, any number of agents can maintain parallel connections. The server isolates errors per-client and broadcasts state changes globally, ensuring that all agents see consistent device status.

Where is the madb server binary located after installation?

According to desktop/electron-builder.config.js, the compiled bundle from packages/madb/dist is copied to extra/common/madb within the Electron resources directory. The main process spawns madb.js from this location at runtime, making it available to both internal renderers and external clients.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →