# Directory Structure
```
├── .gitignore
├── .python-version
├── LICENSE
├── pyproject.toml
├── README.md
├── src
│ └── mcpandroidbuild
│ ├── __init__.py
│ ├── __main__.py
│ ├── build.sh
│ ├── instrumentedTest.sh
│ ├── server.py
│ └── test.sh
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
```
3.13
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
.DS_Store
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Android Project MCP Server
A Model Context Protocol server that builds Android project that enables seamless workflow working with Android projects in Visual Studio Code using extensions like Cline or Roo Code.
<a href="https://glama.ai/mcp/servers/@ShenghaiWang/androidbuild">
<img width="380" height="200" src="https://glama.ai/mcp/servers/@ShenghaiWang/androidbuild/badge" alt="Android Project Server MCP server" />
</a>
### Available Tools
- `build` - Build Android project
- `folder` (string, required): The full path of the current folder that the Android project sits
- `test` - Run unit test
- `folder` (string, required): The full path of the current folder that the Android project sits
- `instrumentedTest` - Run Instrumented test
- `folder` (string, required): The full path of the current folder that the Android project sits
## Installation
### Using uv (recommended)
When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will
use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcpandroidbuild*.
### Using PIP
Alternatively you can install `mcpandroidbuild` via pip:
```
pip install mcpandroidbuild
```
After installation, you can run it as a script using:
```
python -m mcpandroidbuild
```
## Configuration
### Configure for Claude.app
Add to your Claude settings:
<details>
<summary>Using uvx</summary>
```json
"mcpServers": {
"mcpandroidbuild": {
"command": "uvx",
"args": ["mcpandroidbuild"]
}
}
```
</details>
<details>
<summary>Using pip installation</summary>
```json
"mcpServers": {
"mcpandroidbuild": {
"command": "python",
"args": ["-m", "mcpandroidbuild"]
}
}
```
</details>
## License
mcpandroidbuild MCP tool is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
```
--------------------------------------------------------------------------------
/src/mcpandroidbuild/test.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
cd $1
pwd
./gradlew test
```
--------------------------------------------------------------------------------
/src/mcpandroidbuild/build.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
cd $1
pwd
./gradlew build
```
--------------------------------------------------------------------------------
/src/mcpandroidbuild/__main__.py:
--------------------------------------------------------------------------------
```python
from mcpandroidbuild import main
if __name__ == "__main__":
main()
```
--------------------------------------------------------------------------------
/src/mcpandroidbuild/__init__.py:
--------------------------------------------------------------------------------
```python
from .server import run
def main():
"""Android Project MCP Server - Building Android project"""
import asyncio
asyncio.run(run())
if __name__ == "__main__":
main()
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
requires-python = ">=3.13"
name = "mcpandroidbuild"
version = "0.10.0"
description = "A MCP tool that builds Android project and send error back. It also helps to run test and feed the error back to llm."
authors = [{ name = "Tim Wang" }]
maintainers = [{ name = "Tim Wang", email = "[email protected]" }]
keywords = ["http", "mcp", "llm", "automation", "Android"]
license = { text = "MIT" }
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
]
readme = "README.md"
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.3.0",
]
[project.scripts]
mcpandroidbuild = "mcpandroidbuild:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3"]
```
--------------------------------------------------------------------------------
/src/mcpandroidbuild/instrumentedTest.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
cd $1
pwd
echo "Checking for connected devices..."
DEVICE=$(adb devices | awk 'NR>1 {print $1}' | head -n 1)
if [ -z "$DEVICE" ]; then
echo "No device found. Searching for available emulators..."
# List available emulators and select the first one
EMULATOR_NAME=$($ANDROID_HOME/emulator/emulator -list-avds | head -n 1)
if [ -z "$EMULATOR_NAME" ]; then
echo "No emulators found! Please create one using AVD Manager."
exit 1
fi
echo "Starting emulator: $EMULATOR_NAME..."
# Start the selected emulator in the background
$ANDROID_HOME/emulator/emulator -avd "$EMULATOR_NAME" -no-window -no-audio -no-snapshot-load &
echo "Waiting for emulator to boot..."
adb wait-for-device
# Wait until the emulator is fully booted
while [[ "$(adb shell getprop sys.boot_completed | tr -d '\r')" != "1" ]]; do
echo "Still booting..."
sleep 5
done
DEVICE=$(adb devices | awk 'NR>1 {print $1}' | head -n 1)
fi
if [ -n "$DEVICE" ]; then
MODEL=$(adb -s "$DEVICE" shell getprop ro.product.model)
ANDROID_VERSION=$(adb -s "$DEVICE" shell getprop ro.build.version.release)
echo "Using device: $MODEL (Android $ANDROID_VERSION) - $DEVICE"
else
echo "Failed to detect or launch a device!"
exit 1
fi
# Run Android tests
echo "Running Android tests on device: $DEVICE..."
./gradlew connectedAndroidTest
```
--------------------------------------------------------------------------------
/src/mcpandroidbuild/server.py:
--------------------------------------------------------------------------------
```python
from mcp.server.lowlevel import Server
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
ErrorData,
TextContent,
Tool,
Annotations,
Field,
Annotated,
INVALID_PARAMS,
)
from pydantic import BaseModel
import subprocess
import os, json
from mcp.shared.exceptions import McpError
class Folder(BaseModel):
"""Parameters"""
folder: Annotated[str, Field(description="The full path of the current folder that the Android project sits")]
server = Server("build")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name = "build",
description = "Build the Android project in the folder",
inputSchema = Folder.model_json_schema(),
),
Tool(
name="test",
description="Run test for the Android project in the folder",
inputSchema=Folder.model_json_schema(),
),
Tool(
name="instrumentedTest",
description="Run instrumented test for the Android project in the folder",
inputSchema=Folder.model_json_schema(),
)
]
@server.call_tool()
async def call_tool(name, arguments: dict) -> list[TextContent]:
try:
args = Folder(**arguments)
except ValueError as e:
raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e)))
# os.chdir(args.folder)
script_dir = os.path.dirname(os.path.abspath(__file__))
command = [""]
if name == "build":
command = [os.path.join(script_dir, "build.sh"), args.folder]
elif name == "test":
command = [os.path.join(script_dir, "test.sh"), args.folder]
else:
command = [os.path.join(script_dir, "instrumentedTest.sh"), args.folder]
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
stdout_lines = result.stdout.decode("utf-8").splitlines()
stderr_lines = result.stderr.decode("utf-8").splitlines()
all_lines = stdout_lines + stderr_lines
error_lines = [line for line in all_lines if "failure: " in line.lower() or "e: " in line.lower() or " failed" in line.lower()]
error_message = "\n".join(error_lines)
if not error_message:
error_message = "Successful"
return [
TextContent(type="text", text=f"{error_message}")
]
async def run():
options = server.create_initialization_options()
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
options,
raise_exceptions=True,
)
if __name__ == "__main__":
import asyncio
asyncio.run(run())
```