Skip to content

Add a USB host control MCP module - #34

Merged
Miyamura80 merged 2 commits into
mainfrom
claude/mobile-usb-control
Aug 28, 2026
Merged

Add a USB host control MCP module#34
Miyamura80 merged 2 commits into
mainfrom
claude/mobile-usb-control

Conversation

@Miyamura80

@Miyamura80 Miyamura80 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Adds an in-process usb MCP module so an agent, through the SealGate gateway, can enumerate and drive devices plugged into the phone over the Android USB Host API (USB-OTG) — the USB analogue of the existing bluetooth/battery/wifi modules.

Tools

Tool Args Returns
usb_list_devices none [{device_name, vendor_id, product_id, manufacturer?, product?, serial?, device_class, interface_count, has_permission}]
usb_request_permission device_name {requested:true} (async grant) or {granted:true} if already permitted
usb_open device_name, interface_index? claimed interface's endpoints[{address, direction, type, max_packet_size}]
usb_bulk_transfer device_name, endpoint_address, value_hex?, length?, timeout_ms? OUT: {bytes_transferred}; IN: {value_hex, bytes_transferred}
usb_control_transfer device_name, request_type, request, value, index, value_hex?, length?, timeout_ms? OUT: {bytes_transferred}; IN: {value_hex, bytes_transferred}
usb_close device_name {closed:true} (idempotent)

Transfers are protocol-agnostic raw bulk/control with hex payloads; direction is inferred from the endpoint address / request_type top bit (0x80 = IN). CDC-ACM/FTDI/HID/vendor protocols layer on top — no serial specifics baked in.

Design

Mirrors BluetoothModule: all hardware access sits behind a UsbSource interface (production impl AndroidUsbSource over android.hardware.usb.UsbManager), so the module is JVM-testable with a fake. Open UsbDeviceConnections plus the claimed UsbInterface are held by device_name in a ConcurrentHashMap across calls, so usb_open → transfers → usb_close reference one session. Every precondition (no host support, no permission, unknown device/endpoint, bad hex, not-open, transfer failure/timeout) is returned as an in-band isError tool result — never a JSON-RPC error, never a crash.

Permission model

Different from a manifest permission: USB host access needs no <uses-permission>, but requires per-device runtime permission granted via a system dialog (UsbManager.requestPermission + a RECEIVER_NOT_EXPORTED broadcast receiver). usb_request_permission triggers the dialog and returns immediately; every I/O tool reports an in-band error (permission not granted for device <id>: call usb_request_permission and approve the on-device dialog) when hasPermission is false. The manifest declares <uses-feature android:name="android.hardware.usb.host" android:required="false" /> so the app still installs on phones without OTG (devices simply never enumerate). A USB-OTG adapter is required to attach devices.

Registered in TunnelService.connect() so the gateway binds a usb stdio server by name.

Verification

  • JVM-verified (throwaway Gradle project, 10/10 UsbModuleTest cases pass): tools/list advertises the full ordered tool set; usb_list_devices JSON shaping incl. has_permission; I/O with no open connection → in-band error; bulk OUT bad hex → in-band error; bulk IN returns value_hex; permission-missing on open → in-band error; usb_control_transfer round-trips its args to the source and returns the payload; unknown tool → in-band error.
  • CI-only (no Android SDK in the dev sandbox): AndroidUsbSource.kt and the TunnelService wiring reference android.* and compile only under the Android toolchain; assembleDebug + lintDebug + testDebugUnitTest are the authoritative check. SecurityException/exceptions are handled in the same function as each UsbManager/connection call per the lint rule.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NgN7YdfLT41kWnWqe1MPZa


Generated by Claude Code


Summary by cubic

Adds a usb MCP module so an agent can enumerate and drive USB devices plugged into the phone over the Android USB Host API (USB-OTG), the USB analogue of the existing bluetooth/wifi/battery modules. Hardware access sits behind a UsbSource interface, so module logic is JVM-testable with a fake; AndroidUsbSource is the production implementation over UsbManager.

New Features

  • Six tools: usb_list_devices, usb_request_permission, usb_open, usb_bulk_transfer, usb_control_transfer, usb_close.
  • Transfers are protocol-agnostic raw bulk/control with hex payloads; direction is inferred from the endpoint address / request_type top bit.
  • Open connections and claimed interfaces are held by device name across calls, so usb_open → transfers → usb_close reference one session.
  • Every precondition failure returns an in-band isError tool result — never a JSON-RPC error or crash.

Migration

  • USB host needs no manifest permission, but per-device runtime permission is granted via a system dialog; usb_request_permission fires it, and every I/O tool reports an in-band error until granted.
  • The manifest declares <uses-feature android:name="android.hardware.usb.host" android:required="false" /> so the app still installs on phones without OTG; a USB-OTG adapter is required to attach devices.

Written for commit b794586. Summary will update on new commits.

Review in cubic

Add an in-process `usb` MCP module so an agent, through the SealGate gateway,
can enumerate and drive devices plugged into the phone over the Android USB
Host API (USB-OTG) - the USB analogue of the bluetooth/battery/wifi modules.

Tools: usb_list_devices, usb_request_permission, usb_open, usb_bulk_transfer,
usb_control_transfer, usb_close. Transfers are protocol-agnostic raw bulk/
control with hex payloads (direction inferred from the endpoint address /
request_type top bit), so CDC-ACM/FTDI/HID/vendor protocols layer on top.

Design mirrors BluetoothModule: all hardware access sits behind a `UsbSource`
interface (production impl `AndroidUsbSource` over `UsbManager`) so the module
is JVM-testable with a fake; every precondition (no host support, no
permission, unknown device/endpoint, bad hex, not-open, transfer failure/
timeout) is returned as an in-band `isError` tool result, never a JSON-RPC
error or crash. Open `UsbDeviceConnection`s + claimed interfaces are held by
device_name across calls.

Permission model differs from a manifest permission: USB host needs no
`<uses-permission>` but per-device runtime permission granted via a system
dialog (`UsbManager.requestPermission`), so usb_request_permission triggers it
and every I/O tool reports an in-band error when permission is missing. The
manifest declares `<uses-feature android:name="android.hardware.usb.host"
android:required="false" />` so the app still installs without OTG. A USB-OTG
adapter is required to attach devices.

Registered in TunnelService.connect() so the gateway can bind a `usb` server by
name. JVM test (UsbModuleTest, 10 cases) covers the tool set, device JSON
shaping, permission-missing, not-open, bad-hex, bulk IN, and control round-trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgN7YdfLT41kWnWqe1MPZa
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@Miyamura80
Miyamura80 merged commit 8987416 into main Aug 28, 2026
3 checks passed
@github-actions
github-actions Bot deleted the claude/mobile-usb-control branch August 28, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants