Skip to content

Commit d1e9b4c

Browse files
committed
✨ Add droidmind-client CLI and upgrade MCP SDK
Add droidmind-client CLI for server introspection - Introduce `droidmind-client list-tools` command to query available tools from a running DroidMind SSE server - Support sse://, sses://, http://, and https:// URL schemes Upgrade dependencies and improve compatibility - Bump mcp[cli] from 1.8.1 to 1.25.0 - Constrain Python to >=3.13,<3.14 for transitive dependency support - Fix FastMCP initialization to use `instructions` parameter - Update ruff target version to py313 Improve code quality and documentation - Rename unused variables to underscore prefix convention - Move stdio_server import to module level - Add project plan/roadmap documentation page
1 parent bdf54d1 commit d1e9b4c

File tree

15 files changed

+721
-372
lines changed

15 files changed

+721
-372
lines changed

Dockerfile

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,28 @@ FROM python:3.13-slim-bookworm AS builder
22

33
# Install build dependencies
44
RUN apt-get update && \
5-
apt-get install -y --no-install-recommends curl git ca-certificates && \
5+
apt-get install -y --no-install-recommends git ca-certificates && \
66
rm -rf /var/lib/apt/lists/*
77

88
# Set up the application directory
99
WORKDIR /app
1010

11-
# Copy essential files for dependency installation
12-
COPY pyproject.toml ./
13-
COPY LICENSE ./
14-
COPY README.md ./
11+
# Create virtual environment and install uv
12+
RUN python -m venv /opt/venv
13+
ENV VIRTUAL_ENV=/opt/venv
14+
ENV PATH="/opt/venv/bin:${PATH}"
15+
RUN pip install --no-cache-dir --upgrade pip && \
16+
pip install --no-cache-dir uv
17+
18+
# Copy dependency metadata and sync from lockfile
19+
COPY pyproject.toml uv.lock ./
20+
RUN uv sync --frozen --no-dev --active --no-install-project
1521

16-
# Create virtual environment and install dependencies
17-
RUN python -m venv /opt/venv && \
18-
. /opt/venv/bin/activate && \
19-
pip install --no-cache-dir --upgrade pip && \
20-
pip install --no-cache-dir -e .[sse]
22+
# Copy the application code and install the project (deps already installed)
23+
COPY droidmind ./droidmind
24+
COPY README.md ./
25+
COPY LICENSE ./
26+
RUN pip install --no-cache-dir --no-deps .
2127

2228
# Final stage
2329
FROM python:3.13-slim-bookworm
@@ -40,8 +46,8 @@ COPY entrypoint.sh ./
4046
# Make entrypoint script executable
4147
RUN chmod +x entrypoint.sh
4248

43-
# Ensure the DroidMind CLI is accessible and scripts are executable
44-
ENV PATH="/opt/venv/bin:/app/.venv/bin:${PATH}"
49+
# Ensure the DroidMind CLI is accessible
50+
ENV PATH="/opt/venv/bin:${PATH}"
4551

4652
# Default port (still useful if user switches to SSE)
4753
EXPOSE 4256
@@ -51,4 +57,4 @@ ENTRYPOINT ["./entrypoint.sh"]
5157

5258
# Default command to run the server (will be processed by entrypoint.sh)
5359
# Now defaults to stdio via the entrypoint script logic
54-
CMD ["droidmind"]
60+
CMD ["droidmind"]

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<img src="docs/assets/images/logo_neon_glow_icon.png" alt="DroidMind Logo" width="180" />
66

7-
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-9D00FF.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
7+
[![Python 3.13](https://img.shields.io/badge/python-3.13-9D00FF.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
88
[![License](https://img.shields.io/badge/license-Apache_2.0-FF00FF.svg?style=for-the-badge&logo=apache&logoColor=white)](LICENSE)
99
[![Status](https://img.shields.io/badge/status-active_development-39FF14.svg?style=for-the-badge&logo=githubactions&logoColor=white)](docs/plan.md)
1010
[![Code Style](https://img.shields.io/badge/code_style-ruff-00FFFF.svg?style=for-the-badge&logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff)
@@ -63,7 +63,7 @@ Your IDE will be configured to launch DroidMind on demand. Full instructions for
6363

6464
### Prerequisites
6565

66-
- Python 3.13 or higher
66+
- Python 3.13 (3.14 not yet supported)
6767
- `uv` (Python package manager)
6868
- Android device with USB debugging enabled
6969
- ADB (Android Debug Bridge) installed and in your system's PATH
@@ -125,17 +125,17 @@ High-risk operations are flagged, and critical ones are blocked by default. Lear
125125
DroidMind uses `uv` for dependency management and development workflows.
126126

127127
```bash
128-
# Install/update dependencies (after cloning and activating .venv)
129-
uv pip install -e .[dev,sse]
128+
# Install/update dependencies (creates/updates `.venv`)
129+
uv sync --all-groups
130130
131131
# Run tests
132-
pytest
132+
uv run pytest
133133
134134
# Run linting
135-
ruff check .
135+
uv run ruff check .
136136
137137
# Run type checking
138-
pyright # Ensure pyright is installed or use ruff's type checking capabilities
138+
uv run pyright
139139
```
140140

141141
## 🤝 Contributing

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<img src="assets/images/logo_neon_glow_icon.png" alt="DroidMind Logo" width="180" />
55
</p>
66

7-
[![Python 3.13+](https://img.shields.io/badge/python-3.13+-9D00FF.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
7+
[![Python 3.13](https://img.shields.io/badge/python-3.13-9D00FF.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
88
[![License](https://img.shields.io/badge/license-Apache_2.0-FF00FF.svg?style=for-the-badge&logo=apache&logoColor=white)](license.md)
99
[![Code Style](https://img.shields.io/badge/code_style-ruff-00FFFF.svg?style=for-the-badge&logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff)
1010
[![Type Check](https://img.shields.io/badge/type_check-pyright-FFBF00.svg?style=for-the-badge&logo=typescript&logoColor=white)](https://github.com/microsoft/pyright)

docs/installation.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Get DroidMind up and running on your system. This guide covers the primary ways
66

77
Before you begin, ensure you meet the following requirements:
88

9-
- **Python**: DroidMind requires Python 3.13 or higher. You can [download Python](https://www.python.org/downloads/) from the official website.
9+
- **Python**: DroidMind requires Python 3.13 (Python 3.14 is not yet supported). You can [download Python](https://www.python.org/downloads/) from the official website.
1010
- **UV**: We strongly recommend using `uv` for project and package management. It's a fast, modern Python package installer and resolver. Follow the [official uv installation guide](https://github.com/astral-sh/uv#installation).
1111
- **Android Device**: An Android device (physical or emulator) with USB debugging enabled.
1212
- **ADB (Android Debug Bridge)**: ADB must be installed and accessible in your system's PATH. ADB is part of the [Android SDK Platform Tools](https://developer.android.com/studio/releases/platform-tools).
@@ -62,21 +62,16 @@ This method gives you a local copy of the DroidMind codebase, allowing for devel
6262
```
6363

6464
3. **Install Dependencies**:
65-
With the virtual environment activated, install DroidMind and its dependencies. Choose the extras based on your needs:
65+
With the virtual environment activated, install DroidMind and its dependencies using `uv`:
6666

67-
- **For SSE Transport (Recommended for AI Assistant web UIs, Claude Desktop, etc.):**
67+
- **For running DroidMind:**
6868
```bash
69-
uv pip install -e .[sse]
69+
uv sync --no-dev
7070
```
71-
- **For Stdio Transport (Direct terminal interaction):**
71+
- **For development (tests, linting, docs tooling):**
7272
```bash
73-
uv pip install -e .[stdio]
73+
uv sync --all-groups
7474
```
75-
- **For Development (includes all dependencies, plus dev tools):**
76-
```bash
77-
uv pip install -e .[dev,sse]
78-
```
79-
(Note: `dev` typically includes `stdio` and `sse` specific dependencies if structured that way in `pyproject.toml`)
8075

8176
## 🏃‍♀️ Running DroidMind
8277

docs/plan.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: Project Plan
3+
---
4+
5+
# 🗺️ Project Plan / Roadmap
6+
7+
This document tracks near-term maintenance work and longer-term improvements for DroidMind.
8+
9+
## ✅ Maintenance Baseline
10+
11+
- Keep dependencies current via `uv lock --upgrade` + CI green.
12+
- Support **Python 3.13** (Python 3.14 support depends on transitive ecosystem readiness).
13+
- Ensure Docker builds are reproducible using `uv.lock`.
14+
15+
## 🔜 Next Up
16+
17+
- Replace usage of private MCP internals (e.g. `mcp._mcp_server`) with public APIs when available.
18+
- Add a lightweight “smoke test” CI job that boots `droidmind --transport sse` and performs a basic MCP handshake.
19+
- Clarify packaging and installation paths (source/dev vs `uvx` usage) and keep docs consistent.
20+
21+
## 💡 Nice-to-Haves
22+
23+
- Add optional dependency sets (e.g. minimal `stdio` vs `sse`) once imports are fully modular.
24+
- Add integration tests against a real Android emulator in CI (opt-in / nightly).
25+

docs/quickstart.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Welcome to DroidMind! This guide will help you quickly connect DroidMind to your
44

55
## Prerequisites
66

7-
- **Python & UV**: Ensure Python 3.13+ and `uv` are installed. DroidMind uses `uvx` for zero-install IDE integration.
7+
- **Python & UV**: Ensure Python 3.13 and `uv` are installed (Python 3.14 is not yet supported). DroidMind uses `uvx` for zero-install IDE integration.
88
- **AI Assistant with MCP Support**: You'll need an AI assistant that supports the Model Context Protocol (MCP). Examples include Claude Desktop, Cursor, or others listed [here](https://modelcontextprotocol.io/clients).
99
- **Android Device/Emulator**: Have an Android device connected via USB (with USB debugging enabled) or an emulator running. For network connections, ensure ADB over TCP/IP is set up.
1010
- **ADB**: ADB must be installed and in your system PATH.

droidmind/adb.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ async def push_file(self, serial: str, local_path: str, device_path: str) -> str
505505
try:
506506
# Execute push command
507507
logger.info("Pushing %s to %s on %s", local_path, device_path, serial)
508-
stdout, _ = await self._run_adb_device_command(
508+
_stdout, _stderr = await self._run_adb_device_command(
509509
serial,
510510
["push", local_path, device_path],
511511
timeout_seconds=60, # Longer timeout for file transfer
@@ -546,7 +546,7 @@ async def pull_file(self, serial: str, device_path: str, local_path: str) -> str
546546

547547
# Execute pull command
548548
logger.info("Pulling %s from %s to %s", device_path, serial, local_path)
549-
stdout, _ = await self._run_adb_device_command(
549+
_stdout, _stderr = await self._run_adb_device_command(
550550
serial,
551551
["pull", device_path, local_path],
552552
timeout_seconds=60, # Longer timeout for file transfer

droidmind/client.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
DroidMind client utilities.
3+
4+
This module backs the `droidmind-client` console script. It provides simple
5+
connectivity checks and introspection helpers for a running DroidMind server.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from contextlib import AsyncExitStack
11+
import json
12+
13+
import anyio
14+
import click
15+
from mcp.client.session import ClientSession
16+
from mcp.client.sse import sse_client
17+
18+
19+
def _normalize_sse_url(url: str) -> str:
20+
# Many MCP clients use `sse://` as a scheme alias for HTTP.
21+
if url.startswith("sse://"):
22+
return "http://" + url.removeprefix("sse://")
23+
if url.startswith("sses://"):
24+
return "https://" + url.removeprefix("sses://")
25+
return url
26+
27+
28+
async def _list_tools(url: str, connect_timeout: float) -> int:
29+
try:
30+
async with AsyncExitStack() as stack:
31+
read_stream, write_stream = await stack.enter_async_context(
32+
sse_client(_normalize_sse_url(url), timeout=connect_timeout)
33+
)
34+
session = await stack.enter_async_context(ClientSession(read_stream, write_stream))
35+
await session.initialize()
36+
result = await session.list_tools()
37+
except Exception as exc: # noqa: BLE001 - CLI boundary
38+
click.echo(f"Failed to connect/list tools: {exc}", err=True)
39+
return 1
40+
41+
click.echo(json.dumps([t.model_dump(mode="json") for t in result.tools], indent=2))
42+
return 0
43+
44+
45+
@click.group(context_settings={"help_option_names": ["-h", "--help"]}, no_args_is_help=True)
46+
def cli() -> None:
47+
"""Utilities for connecting to a running DroidMind MCP server."""
48+
49+
50+
@cli.command("list-tools")
51+
@click.option(
52+
"--url",
53+
default="sse://127.0.0.1:4256/sse",
54+
show_default=True,
55+
help="SSE endpoint URL (accepts sse://, sses://, http://, https://).",
56+
)
57+
@click.option("--timeout", default=5.0, show_default=True, type=float, help="HTTP timeout for connection setup.")
58+
def list_tools(url: str, timeout: float) -> None:
59+
"""List available tools from a running DroidMind server (JSON)."""
60+
raise SystemExit(anyio.run(_list_tools, url, timeout))
61+
62+
63+
def main() -> None:
64+
cli()
65+
66+
67+
if __name__ == "__main__": # pragma: no cover
68+
main()

droidmind/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
# Create the MCP server with lifespan
1111
mcp = FastMCP(
1212
"DroidMind",
13-
description="Control Android devices with MCP",
13+
instructions="Control Android devices with MCP",
1414
dependencies=["rich>=13.9.4"],
1515
)

droidmind/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import anyio
1616
import click
1717
from mcp.server.sse import SseServerTransport
18+
from mcp.server.stdio import stdio_server
1819
from rich.logging import RichHandler
1920
from starlette.applications import Starlette
2021
from starlette.middleware import Middleware
@@ -181,7 +182,6 @@ def run_stdio_server(config: dict[str, Any]) -> None:
181182
"""
182183
# Use stdio transport for terminal use
183184
logger.info("Using stdio transport for terminal interaction")
184-
from mcp.server.stdio import stdio_server
185185

186186
# Determine if we should show startup message (only if log_file is specified)
187187
bool(config.get("log_file") and config["log_file"] != "console")

0 commit comments

Comments
 (0)