diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..db8c18b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +mcadb - A FastMCP server exposing Android Debug Bridge (ADB) functionality through the Model Context Protocol. Enables AI assistants to automate Android devices via screenshots, input simulation, app control, UI inspection, file transfer, and shell commands. + +## Commands + +```bash +# Install dependencies +uv sync + +# Run the server locally +uv run mcadb + +# Run directly with uvx (no install needed) +uvx mcadb + +# Development dependencies +uv sync --group dev + +# Linting and formatting +uv run ruff check src/ +uv run ruff format src/ + +# Type checking +uv run mypy src/ + +# Docker (requires privileged mode for USB access) +docker compose up --build +``` + +## Architecture + +Modular MCPMixin architecture with 50 tools across 6 domain mixins: + +- **`src/server.py`**: FastMCP app and `ADBServer` class (thin orchestrator inheriting all mixins) +- **`src/config.py`**: Persistent singleton config (`~/.config/adb-mcp/config.json`) +- **`src/models.py`**: Pydantic models (`DeviceInfo`, `CommandResult`, `ScreenshotResult`) +- **`src/mixins/base.py`**: Core ADB execution with `run_adb()`, `run_shell()`, and injection-safe `run_shell_args()` using `shlex.quote()` +- **`src/mixins/devices.py`**: Device discovery, info, logcat, reboot +- **`src/mixins/input.py`**: Tap, swipe, scroll, keys, text, clipboard, shell command +- **`src/mixins/apps.py`**: Launch, close, install, intents, broadcasts +- **`src/mixins/screenshot.py`**: Screen capture, recording, display settings +- **`src/mixins/ui.py`**: Accessibility tree dump, element search, text polling +- **`src/mixins/files.py`**: Push, pull, list, delete, exists + +### Key Patterns + +- All tools use `run_shell_args()` (injection-safe) except `shell_command` which intentionally uses `run_shell()` for raw commands +- Developer mode tools are gated by `is_developer_mode()` checks and tagged with `tags={"developer"}` +- Destructive operations use `ctx.elicit()` for user confirmation +- `@mcp_tool()` and `@mcp_resource()` decorators from `fastmcp.contrib.mcp_mixin` + +## MCP Client Configuration + +```json +{ + "mcpServers": { + "mcadb": { + "command": "uvx", + "args": ["mcadb"] + } + } +} +``` + +## Device Requirements + +- ADB installed and accessible in PATH +- USB debugging enabled on Android device +- For Docker: `--privileged` flag and `/dev/bus/usb` volume mount required diff --git a/Dockerfile b/Dockerfile index cfe3074..9e157bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,4 +25,4 @@ COPY src/ ./src/ # Expose ADB server port (optional, mainly for debugging) EXPOSE 5037 -CMD ["uv", "run", "python", "-m", "src.server"] \ No newline at end of file +CMD ["uv", "run", "mcadb"] \ No newline at end of file diff --git a/README.md b/README.md index 9cfc864..da3e38b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Android ADB MCP Server +# mcadb A [Model Context Protocol](https://modelcontextprotocol.io/) server that gives AI assistants direct control over Android devices through ADB. Point any MCP-compatible client at a phone plugged into USB, and it can take screenshots, tap buttons, launch apps, inspect UI elements, transfer files, and run shell commands — all through structured, type-safe tool calls. @@ -8,11 +8,11 @@ Built on [FastMCP](https://gofastmcp.com/) with a modular mixin architecture. 50 ```bash # Run directly (no install) -uvx android-mcp-server +uvx mcadb # Or install and run -uv add android-mcp-server -android-mcp-server +uv add mcadb +mcadb ``` ### MCP Client Configuration @@ -22,9 +22,9 @@ Add to your MCP client's config (Claude Desktop, Claude Code, etc.): ```json { "mcpServers": { - "android-adb": { + "mcadb": { "command": "uvx", - "args": ["android-mcp-server"] + "args": ["mcadb"] } } } @@ -32,12 +32,12 @@ Add to your MCP client's config (Claude Desktop, Claude Code, etc.): For Claude Code: ```bash -claude mcp add android-adb -- uvx android-mcp-server +claude mcp add mcadb -- uvx mcadb ``` For local development: ```bash -claude mcp add android-adb -- uv run --directory /path/to/mcp-adb android-mcp-server +claude mcp add mcadb -- uv run --directory /path/to/mcp-adb mcadb ``` ## Prerequisites @@ -189,8 +189,8 @@ All tools that accept user-provided values use **injection-safe command executio ## Docker ```bash -docker build -t android-mcp-server . -docker run --privileged -v /dev/bus/usb:/dev/bus/usb android-mcp-server +docker build -t mcadb . +docker run --privileged -v /dev/bus/usb:/dev/bus/usb mcadb ``` The `--privileged` flag and USB volume mount are required for ADB to detect physical devices. @@ -199,9 +199,9 @@ MCP client config for Docker: ```json { "mcpServers": { - "android-adb": { + "mcadb": { "command": "docker", - "args": ["run", "-i", "--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "android-mcp-server"] + "args": ["run", "-i", "--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "mcadb"] } } } @@ -216,7 +216,7 @@ cd mcp-adb uv sync --group dev # Run locally -uv run android-mcp-server +uv run mcadb # Lint uv run ruff check src/ diff --git a/docker-compose.yml b/docker-compose.yml index b04ec23..ff16b9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,5 @@ -version: '3.8' - services: - android-mcp-server: + mcadb: build: . volumes: - /dev/bus/usb:/dev/bus/usb @@ -14,6 +12,6 @@ services: environment: - PYTHONPATH=/app working_dir: /app - command: ["python", "-m", "src.server"] + command: ["uv", "run", "mcadb"] devices: - - /dev/bus/usb \ No newline at end of file + - /dev/bus/usb diff --git a/pyproject.toml b/pyproject.toml index 1bf850e..6a2708c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "android-mcp-server" +name = "mcadb" version = "0.3.1" description = "Android ADB MCP Server for device automation via Model Context Protocol" authors = [ @@ -26,12 +26,12 @@ dependencies = [ requires-python = ">=3.11" [project.urls] -Homepage = "https://github.com/supported-systems/android-mcp-server" -Documentation = "https://github.com/supported-systems/android-mcp-server#readme" -Repository = "https://github.com/supported-systems/android-mcp-server" +Homepage = "https://git.supported.systems/MCP/mcp-adb" +Documentation = "https://git.supported.systems/MCP/mcp-adb#readme" +Repository = "https://git.supported.systems/MCP/mcp-adb" [project.scripts] -android-mcp-server = "src.server:main" +mcadb = "src.server:main" [build-system] requires = ["hatchling"] diff --git a/src/server.py b/src/server.py index 6ea2311..211d479 100644 --- a/src/server.py +++ b/src/server.py @@ -196,7 +196,7 @@ class ADBServer( # Initialize FastMCP server mcp = FastMCP( - "android-adb", + "mcadb", instructions="""Android ADB MCP Server for device automation. Use devices_list() first to see connected devices. @@ -224,11 +224,11 @@ def main(): try: from importlib.metadata import version - package_version = version("android-mcp-server") + package_version = version("mcadb") except Exception: package_version = "0.3.1" - print(f"📱 Android ADB MCP Server v{package_version}", flush=True) + print(f"📱 mcadb v{package_version}", flush=True) mcp.run() diff --git a/uv.lock b/uv.lock index 6aa83bd..72a2b6b 100644 --- a/uv.lock +++ b/uv.lock @@ -2,37 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.11" -[[package]] -name = "android-mcp-server" -version = "0.3.1" -source = { editable = "." } -dependencies = [ - { name = "fastmcp" }, - { name = "pydantic" }, -] - -[package.dev-dependencies] -dev = [ - { name = "mypy" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "fastmcp", specifier = ">=2.14.0,<3.0.0" }, - { name = "pydantic", specifier = ">=2.12.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "mypy", specifier = ">=1.19.0" }, - { name = "pytest", specifier = ">=9.0.0" }, - { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "ruff", specifier = ">=0.15.0" }, -] - [[package]] name = "annotated-doc" version = "0.0.4" @@ -764,6 +733,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "mcadb" +version = "0.3.1" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, + { name = "pydantic" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = ">=2.14.0,<3.0.0" }, + { name = "pydantic", specifier = ">=2.12.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.19.0" }, + { name = "pytest", specifier = ">=9.0.0" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.15.0" }, +] + [[package]] name = "mcp" version = "1.26.0"