Backend of fast_bridge using uiautomator2
SaferSkills independently audited fast_bridge_backend (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Backend da aplicação Fast Bridge — uma API REST, WebSocket e MCP para controle remoto de dispositivos Android via ADB e uiautomator2, com streaming de vídeo em tempo real via scrcpy.
fast-bridge para AgentesO Fast Bridge Backend expõe dispositivos Android conectados via USB (ADB) como recursos HTTP, WebSocket e MCP. Com ele é possível:
fast_bridge_backend/
├── main.py # Ponto de entrada — FastAPI + Uvicorn
├── mcp_entry.py # Entrypoint MCP (FastMCP) em STDIO
├── pyproject.toml # Dependências gerenciadas via UV
├── app/
│ ├── dependencies.py # DeviceManager singleton + get_device_manager()
│ ├── binaries/ # scrcpy-server-v*.jar
│ ├── controller/
│ │ ├── scrcpy.py # ScrcpyServer: streaming de vídeo + controle WebSocket
│ │ ├── touch_controller.py # Protocolo binário de toque para scrcpy
│ │ ├── android_input.py # Constantes de input Android (KeyeventAction, MetaState)
│ │ ├── keycode.py # Enum de KeyCodes Android
│ │ └── file_manager.py # list_files_by_path() — wrapper de ls -la
│ ├── core/
│ │ ├── constants.py # Constantes globais (PORT)
│ │ └── logger_config.py # Configuração do Loguru
│ ├── model/
│ │ ├── adboutput.py # Modelo Pydantic AdbResponse
│ │ └── file_entry.py # Modelos FileEntry e FileManagerResponse + parse_ls_output()
│ ├── routes/
│ │ ├── device.py # Endpoints REST e WebSocket (usa DeviceService via Depends)
│ │ └── health.py # GET /health
│ └── services/
│ └── device_service.py # DeviceService: toda a lógica de negócio + get_device_service()
├── logs/ # Logs rotativos gerados pelo Loguru
└── tests/
└── test_device_api.py # Testes unitários com mocksAs rotas não gerenciam conexões diretamente. O padrão é:
DeviceManager (singleton em app/dependencies.py)
└── DeviceService (instanciado por request via Depends)
└── Rotas (recebem DeviceService via Depends(get_device_service))# Padrão nas rotas
@router.get("/device/{serial}/screenshot")
def screenshot(serial: str, svc: DeviceService = Depends(get_device_service)):
return svc.screenshot(serial)Os testes sobrescrevem a dependência via app.dependency_overrides:
app.dependency_overrides[get_device_service] = lambda: DeviceService(_make_mock_manager(mock_device))Cliente (navegador)
│
▼ ws://localhost:8000/ws/device/{serial}/control
FastAPI WebSocket
│
├── ScrcpyServer._stream_video_to_websocket() ─► frames JPEG para o cliente
└── ScrcpyServer._handle_control_websocket() ◄─ eventos JSON do cliente
│
└── ScrcpyTouchController ─► protocolo binário scrcpy ─► dispositivoPATHapp/binaries/scrcpy-server-v2.7.jarPrincipais dependências Python:
| Pacote | Versão |
|---|---|
| fastapi | 0.135.1 |
| uvicorn | 0.42.0 |
| uiautomator2 | 3.5.0 |
| adbutils | 2.12.0 |
| pydantic | 2.12.5 |
| pillow | 12.1.1 |
| loguru | 0.7.3 |
| mcp | ≥1.27.1 |
| av | 17.0.0 |
O projeto usa UV para gerenciamento de dependências.
# Clone o repositório
git clone <url-do-repositorio>
cd fast_bridge_backend
# Instale as dependências com UV (recomendado)
uv sync
# Ou com pip tradicional
python -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate # Windows
pip install -r requirements.txtpython main.pyO servidor sobe em http://localhost:8000.
http://localhost:8000/docspython mcp_entry.py#### GET /health
Verifica se o servidor está em execução.
Resposta `200`
{ "status": "ok" }#### GET /devices
Lista todos os dispositivos Android conectados via ADB.
Resposta `200`
[
{
"serialno": "RQCTA0823SP",
"devpath": "usb:1-2",
"state": "device"
}
]#### GET /device/{device_serial}/screenshot
Retorna um screenshot do dispositivo como imagem JPEG.
| Parâmetro | Tipo | Descrição |
|---|---|---|
device_serial | path | Serial do dispositivo |
display_id | query | ID do display (padrão: 0) |
Resposta `200` — image/jpeg
#### GET /device/{device_serial}/screen_info
Retorna as dimensões da tela do dispositivo.
Resposta `200`
{
"width": 1080,
"height": 2400
}#### GET /device/{device_serial}/prop/{shell_property}
Consulta uma propriedade do sistema Android via getprop.
Exemplo: GET /device/emulator-5554/prop/ro.product.model
Resposta `200` — AdbResponse
{
"device_serial": "emulator-5554",
"stdout": "Pixel 6",
"exit_code": 0
}Resposta `505` — Erro de ADB.
#### GET /device/{device_serial}/window_dump
Retorna o dump da hierarquia de UI do dispositivo em XML (uiautomator2).
| Parâmetro | Tipo | Valores | Descrição |
|---|---|---|---|
format | query | xml | Formato de saída (padrão: xml). Outros valores retornam 400. |
Resposta `200` — text/xml
#### GET /device/{device_serial}/file_manager
Lista arquivos e diretórios em um caminho do dispositivo via ls -la.
| Parâmetro | Tipo | Descrição |
|---|---|---|
device_serial | path | Serial do dispositivo |
path | query | Caminho no dispositivo (padrão: .) |
Resposta `200` — FileManagerResponse
{
"path": "/sdcard",
"entries": [
{
"name": "Download",
"permissions": "drwxrwx--x",
"is_dir": true,
"is_symlink": false,
"owner": "root",
"group": "sdcard_rw",
"size": 4096,
"modified_at": "2024-01-15 10:30",
"symlink_target": null
}
]
}#### POST /device/{device_serial}/input/keyevent
Envia um evento de tecla Android.
| Parâmetro | Tipo | Descrição |
|---|---|---|
keycode | query | Código da tecla Android (ex.: 4 = BACK, 66 = ENTER) |
repeat | query | Número de repetições com longpress (padrão: 0) |
metastate | query | Flags de meta state (padrão: 0) |
Resposta `200`
{ "detail": "Key event 4 sent to device emulator-5554" }#### PUT /device/{device_serial}/input/text
Envia uma string de texto para o dispositivo.
| Parâmetro | Tipo | Descrição |
|---|---|---|
text | query | Texto a ser digitado |
Resposta `200`
{ "detail": "Text sent to device emulator-5554" }#### PUT /device/{device_serial}/input/touch
Envia um evento de toque (tap) em coordenadas absolutas.
| Parâmetro | Tipo | Descrição |
|---|---|---|
x | query | Coordenada X em pixels |
y | query | Coordenada Y em pixels |
Resposta `200`
{ "detail": "Touch event sent to device emulator-5554 at (540, 960)" }#### POST /device/{device_serial}
Executa um comando shell no dispositivo via ADB. O body deve ser uma lista de strings (tokens).
Body application/json
["pm", "list", "packages"]Resposta `200` — AdbResponse
{
"device_serial": "emulator-5554",
"stdout": "package:com.example.app\n...",
"exit_code": 0
}Resposta `505` — Erro de ADB.
#### WS /ws/device/{device_serial}/control
Canal bidirecional que combina streaming de vídeo (scrcpy) com controle do dispositivo.
Mensagens do cliente → servidor (JSON):
type | Campos adicionais | Descrição |
|---|---|---|
touchDown | xP, yP (0.0–1.0) | Toque iniciado (coordenadas percentuais) |
touchMove | xP, yP (0.0–1.0) | Arrastar |
touchUp | xP, yP (0.0–1.0) | Toque liberado |
keyEvent | data.eventNumber | Evento de tecla Android |
text | detail | Envio de texto via broadcast (am broadcast) |
ping | — | Keepalive |
CoordenadasxP/yPsão percentuais (0.0–1.0). O servidor converte para pixels absolutos usando a resolução obtida no handshake do scrcpy.
Mensagens do servidor → cliente:
{"type": "pong"} em resposta ao pingO servidor MCP usa somente transporte STDIO.
Execução direta:
python mcp_entry.pyConfiguração no Claude Desktop:
{
"mcpServers": {
"fast-bridge": {
"command": "python",
"args": ["mcp_entry.py"]
}
}
}fast-bridge para AgentesAlém da integração MCP padrão, o projeto inclui um fluxo operacional para agentes (ex.: Copilot CLI) controlarem Android de forma confiável.
Configuração recomendada do servidor MCP para a skill:
.venv/bin/python (ou python com o ambiente virtual ativo)mcp_entry.py (a partir da raiz do projeto)#### Regras operacionais (loop obrigatório)
list_connected_devices.get_ui_hierarchy(serial) e localize o alvo no XML.execute_adb_command(serial, command).get_ui_hierarchy (ou take_screenshot) após a ação.Esse ciclo evita automações “cegas” e garante confirmação de estado em cada etapa.
#### Coordenadas de toque (tap)
Para tocar em um elemento da UI, use bounds do XML:
bounds="[left,top][right,bottom]"
center_x = (left + right) / 2
center_y = (top + bottom) / 2Depois envie:
["input", "tap", "<center_x>", "<center_y>"]#### Ferramentas usadas no fluxo
As ferramentas MCP expostas pelo backend são:
list_connected_devicesget_ui_hierarchy(serial)take_screenshot(serial)execute_adb_command(serial, command)Em alguns clientes/agentes, elas podem aparecer com prefixo de namespace (por exemplo, fb-list_connected_devices, fb-get_ui_hierarchy, etc.). A funcionalidade é a mesma.
#### Segurança do execute_adb_command
input, am, pm, dumpsys, getprop, settings, service, wm, cmd.;, |, &, $, ` `, >, <`, quebras de linha).list[str]), sem concatenação shell.#### Troubleshooting rápido
command/args acima;fast-bridge nesta sessão.am start retorna sucesso, mas a UI não mudou:-n package/.Activity ou deep link).take_screenshot(serial) para confirmação visual.#### list_connected_devices
Retorna os seriais de todos os dispositivos ADB conectados.
Retorno: list[str]
#### get_ui_hierarchy(serial)
Retorna o dump XML da hierarquia de UI da tela atual do dispositivo.
| Parâmetro | Tipo | Descrição |
|---|---|---|
serial | str | Serial ADB do dispositivo |
Retorno: str — XML UTF-8 da hierarquia de views.
#### take_screenshot(serial)
Captura um screenshot e retorna como string base64 (JPEG).
| Parâmetro | Tipo | Descrição |
|---|---|---|
serial | str | Serial ADB do dispositivo |
Retorno: str — JPEG codificado em base64.
#### execute_adb_command(serial, command)
Executa um comando ADB shell autorizado no dispositivo.
| Parâmetro | Tipo | Descrição |
|---|---|---|
serial | str | Serial ADB do dispositivo |
command | list[str] | Comando tokenizado, ex.: ["input", "tap", "540", "960"] |
Comandos permitidos: input, am, pm, dumpsys, getprop, settings, service, wm, cmd.
Metacaracteres de shell (;, |, &, $, ` `, >, <`) são rejeitados.
Retorno:
{ "stdout": "...", "exit_code": 0 }AdbResponseclass AdbResponse(BaseModel):
device_serial: str # Serial do dispositivo
stdout: str # Saída do comando
exit_code: int # 0 = sucesso, 1 = erroFileEntryclass FileEntry(BaseModel):
name: str
permissions: str # ex.: "drwxr-xr-x"
is_dir: bool
is_symlink: bool
owner: str
group: str
size: int
modified_at: str # ex.: "2024-01-15 10:30"
symlink_target: str | NoneFileManagerResponseclass FileManagerResponse(BaseModel):
path: str
entries: list[FileEntry]| Módulo | Responsabilidade |
|---|---|
main.py | Configuração do app FastAPI, CORS e Uvicorn |
mcp_entry.py | Entrypoint do servidor MCP em transporte STDIO |
app/dependencies.py | DeviceManager singleton; provedor get_device_manager() |
app/services/device_service.py | DeviceService: toda a lógica de negócio; provedor get_device_service() |
app/routes/device.py | Endpoints REST e WebSocket; injetam DeviceService via Depends |
app/routes/health.py | GET /health |
app/controller/scrcpy.py | Gerencia o servidor scrcpy no dispositivo, streaming de vídeo e controle via WebSocket |
app/controller/touch_controller.py | Serializa eventos de toque no protocolo binário do scrcpy |
app/controller/android_input.py | Enums KeyeventAction e MetaState |
app/controller/keycode.py | Enum KeyCode com todos os keycodes Android |
app/controller/file_manager.py | list_files_by_path() — executa ls -la e retorna FileManagerResponse |
app/model/adboutput.py | Schema Pydantic AdbResponse |
app/model/file_entry.py | Schemas FileEntry e FileManagerResponse + parse_ls_output() |
app/core/constants.py | Constantes globais |
app/core/logger_config.py | Loguru configurado com rotação e compressão |
Os testes usam pytest com unittest.mock para isolar dependências ADB. As dependências são injetadas via app.dependency_overrides.
pytest tests/
# Ou um teste específico:
pytest tests/test_device_api.py::test_send_adb_shell_successCasos de teste cobertos em tests/test_device_api.py:
GET /devices — listagem de dispositivos mockadosPOST /device/{serial} — execução de shell com sucesso e com erro (505)GET /device/{serial}/prop/{property} — consulta de propriedadeConfigurado via Loguru. Logs são emitidos para:
INFOINFO, rotação a cada 5 MB, retenção de 7 dias, compressão ZIPAtenção: Usefrom app.core import logem todo código dentro deapp/. Nunca useprint()oulogging.getLogger().
O frontend da aplicação está disponível em fast-bridge-nine.vercel.app e se comunica com este backend via HTTP e WebSocket.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.