- droidmind
- docs
# Android MCP Server
<div align="center">

**Connect AI assistants to Android devices for enhanced development workflows**
[](https://modelcontextprotocol.io/)
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
</div>
## 📱 Overview
Android MCP Server bridges the gap between AI assistants and Android devices, enabling seamless integration of Android development workflows with AI-powered IDEs like Cursor. By leveraging the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), the server exposes Android Debug Bridge (ADB) capabilities, device diagnostics, file systems, and development tools through standardized interfaces.
**Key Benefits:**
- 🔍 **Enhanced Debugging** - Analyze logs, capture screenshots, and diagnose issues with AI assistance
- 🛠️ **Streamlined Development** - Access device features, install apps, and manage files without leaving your IDE
- 🔄 **ROM Development** - Flash images, manage partitions, and customize system components
- 📊 **System Analysis** - Monitor performance, analyze battery usage, and collect diagnostics
---
## 🎯 Real-World Use Cases
### App Developer Scenarios
#### 1. Crash Debugging
> **Scenario**: A developer is working on an Android app that crashes intermittently on certain devices.
**Without Android MCP Server:**
1. Developer manually connects to device
2. Runs the app and reproduces the crash
3. Opens terminal to run ADB commands
4. Captures logcat output
5. Copies logs back to IDE
6. Analyzes logs manually
**With Android MCP Server:**
1. Developer asks AI: "My app just crashed on the test device. What happened?"
2. Android MCP Server automatically:
- Captures relevant logcat entries
- Extracts stack trace
- Provides AI with device properties and app state
3. AI analyzes the combined context and suggests fixes
```
User: My app just crashed when testing the checkout flow. Can you help diagnose the issue?
AI: I'll help you diagnose the crash. Let me check the logs and device information.
[AI uses Android MCP Server to retrieve logs]
I found the issue in your crash logs. The app crashed with a NullPointerException in PaymentProcessor.java:218.
The device is running Android 12 (SDK 31) on a Samsung Galaxy S21.
The crash occurred because you're trying to access the PaymentIntent object before it's initialized in the
checkout flow on devices running Android 12 or higher.
Here's the fix:
```
#### 2. UI Optimization
> **Scenario**: A developer needs to optimize their app's UI for various screen sizes and densities.
**Now possible with Android MCP Server:**
1. Developer asks: "How does my UI look on these different devices?"
2. Android MCP Server:
- Captures screenshots from connected devices
- Provides screen dimensions and density information
- Collects layout hierarchy data
3. AI analyzes UI across devices and suggests improvements
### System Developer Scenarios
#### 1. Custom ROM Troubleshooting
> **Scenario**: A ROM developer is debugging a custom build that fails to boot properly.
**With Android MCP Server:**
1. Developer asks: "My custom ROM isn't booting. What's going wrong?"
2. Android MCP Server:
- Pulls boot logs from the device in recovery mode
- Retrieves last_kmsg or kernel logs
- Collects partition information
3. AI analyzes the boot failure and suggests solutions
#### 2. Performance Optimization
> **Scenario**: A system developer needs to optimize battery life for a custom Android build.
**With Android MCP Server:**
1. Developer requests: "Analyze battery consumption on my device"
2. Android MCP Server:
- Collects battery stats and wakelocks
- Monitors system processes and resource usage
- Identifies power-hungry services
3. AI provides actionable recommendations for optimization
---
## 🏗️ Architecture
The Android MCP Server follows a modular architecture designed for reliability, security, and extensibility.
```
┌───────────────────┐ ┌─────────────────────┐ ┌───────────────────┐
│ IDE & AI Tools │ │ Android MCP Server │ │ Android Devices │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌───────────────┐ │ │ ┌─────────────┐ │
│ │ Cursor IDE │ │ │ │ MCP Protocol │ │ │ │ Real Device │ │
│ └─────────────┘ │ │ └───────┬───────┘ │ │ └─────────────┘ │
│ ┌─────────────┐ │ │ ┌───────┴───────┐ │ │ ┌─────────────┐ │
│ │ Claude AI │◄─┼─────┼─►│ ADB Wrapper │◄─┼─────┼─►│ Emulator │ │
│ └─────────────┘ │ │ └───────┬───────┘ │ │ └─────────────┘ │
│ ┌─────────────┐ │ │ ┌───────┴───────┐ │ │ │
│ │ Other Tools │ │ │ │ Security Layer│ │ │ │
│ └─────────────┘ │ │ └───────────────┘ │ │ │
└───────────────────┘ └─────────────────────┘ └───────────────────┘
```
### Components
1. **MCP Protocol Layer**
- Implements Model Context Protocol specification
- Handles resource, tool, and prompt requests
- Manages client communication
2. **ADB Wrapper**
- Provides a secure interface to ADB commands
- Handles device discovery and management
- Processes command outputs into structured data
3. **Security Layer**
- Command sanitization and validation
- Permission management
- Secure storage of sensitive information
---
## 🧰 Feature Set
### Device Management
#### Resources
| Resource URI | Description | Example Usage |
| ------------------------------ | ------------------------- | ----------------------------------------------- |
| `device://list` | List of connected devices | "Show me all connected Android devices" |
| `device://{serial}/properties` | Device properties | "What's the Android version on my test device?" |
| `device://{serial}/features` | Hardware capabilities | "Does this device have NFC support?" |
| `device://{serial}/storage` | Storage statistics | "How much free space is on the device?" |
#### Tools
| Tool Name | Description | Parameters |
| ------------------- | -------------------------- | --------------------------------------------- |
| `connect_device` | Connect to wireless device | `ip_address`, `port` (optional) |
| `disconnect_device` | Disconnect device | `serial` |
| `reboot_device` | Reboot device | `serial`, `mode` (normal/recovery/bootloader) |
| `set_property` | Set system property | `serial`, `property`, `value` |
### Application Development
#### Resources
| Resource URI | Description | Example Usage |
| ------------------------------------------ | -------------------------- | ---------------------------------------------- |
| `fs://{serial}/app/{package}/manifest` | App manifest | "Show me the permissions my app is requesting" |
| `fs://{serial}/app/{package}/data` | App data directory listing | "What files has my app created?" |
| `fs://{serial}/app/{package}/shared_prefs` | App preferences | "Show me the saved preferences in my app" |
| `logs://{serial}/app/{package}` | App-specific logs | "What errors is my app generating?" |
#### Tools
| Tool Name | Description | Parameters |
| ------------------- | ----------------- | ------------------------------------------- |
| `install_app` | Install APK | `serial`, `apk_path`, `options` |
| `uninstall_app` | Remove app | `serial`, `package`, `keep_data` (optional) |
| `start_app` | Launch app | `serial`, `package`, `activity` (optional) |
| `stop_app` | Force stop app | `serial`, `package` |
| `clear_app_data` | Clear app data | `serial`, `package` |
| `grant_permission` | Grant permission | `serial`, `package`, `permission` |
| `revoke_permission` | Revoke permission | `serial`, `package`, `permission` |
### File System Access
#### Resources
| Resource URI | Description | Example Usage |
| --------------------------- | -------------------- | ---------------------------------------- |
| `fs://{serial}/list/{path}` | Directory listing | "Show me what's in the Downloads folder" |
| `fs://{serial}/read/{path}` | File contents | "Let me see the contents of config.xml" |
| `fs://{serial}/stat/{path}` | File/directory stats | "When was this file last modified?" |
| `fs://{serial}/size/{path}` | Directory size | "How big is the DCIM folder?" |
#### Tools
| Tool Name | Description | Parameters |
| ------------------ | ------------------------- | ------------------------------------- |
| `push_file` | Upload file to device | `serial`, `local_path`, `device_path` |
| `pull_file` | Download file from device | `serial`, `device_path`, `local_path` |
| `delete_file` | Remove file/directory | `serial`, `path` |
| `create_directory` | Create directory | `serial`, `path` |
| `move_file` | Move/rename file | `serial`, `source_path`, `dest_path` |
| `copy_file` | Copy file | `serial`, `source_path`, `dest_path` |
### Debugging & Diagnostics
#### Resources
| Resource URI | Description | Example Usage |
| ------------------------- | ------------------------- | --------------------------------------- |
| `logs://{serial}/logcat` | System logs | "Show me the recent system logs" |
| `logs://{serial}/anr` | App Not Responding traces | "Why did my app freeze?" |
| `logs://{serial}/crashes` | Recent crash reports | "Show crash reports from the last hour" |
| `logs://{serial}/battery` | Battery statistics | "What's draining the battery?" |
| `logs://{serial}/memory` | Memory usage | "What's using the most memory?" |
| `logs://{serial}/network` | Network statistics | "Show network usage by app" |
#### Tools
| Tool Name | Description | Parameters |
| -------------------- | ---------------------- | --------------------------------------------- |
| `capture_screenshot` | Take screenshot | `serial` |
| `record_screen` | Record screen video | `serial`, `duration`, `bitrate` (optional) |
| `capture_bugreport` | Generate bug report | `serial`, `path` (output location) |
| `dump_heap` | Capture heap dump | `serial`, `package`, `path` (output location) |
| `start_profiling` | Start method profiling | `serial`, `package`, `output` |
| `stop_profiling` | Stop method profiling | `serial` |
### UI Testing & Automation
#### Resources
| Resource URI | Description | Example Usage |
| --------------------------------- | -------------------- | -------------------------------------------- |
| `screen://{serial}/hierarchy` | UI element hierarchy | "Show me the current screen layout" |
| `screen://{serial}/accessibility` | Accessibility info | "Check this screen for accessibility issues" |
| `screen://{serial}/focusable` | Focusable elements | "What elements can receive focus?" |
#### Tools
| Tool Name | Description | Parameters |
| -------------- | -------------------------- | -------------------------------------------- |
| `tap` | Tap at coordinates | `serial`, `x`, `y` |
| `swipe` | Swipe gesture | `serial`, `x1`, `y1`, `x2`, `y2`, `duration` |
| `input_text` | Type text | `serial`, `text` |
| `press_key` | Press hardware/system key | `serial`, `keycode` |
| `start_intent` | Launch activity via intent | `serial`, `intent` |
### System & ROM Development
#### Resources
| Resource URI | Description | Example Usage |
| ------------------------------ | --------------------- | --------------------------------- |
| `system://{serial}/partitions` | Partition information | "Show me the partition layout" |
| `system://{serial}/bootloader` | Bootloader state | "Is the bootloader unlocked?" |
| `system://{serial}/kernel` | Kernel information | "What kernel version is running?" |
| `system://{serial}/selinux` | SELinux policy | "Show me the SELinux policy" |
#### Tools
| Tool Name | Description | Parameters |
| ------------------- | --------------------- | ------------------------------------ |
| `flash_image` | Flash partition image | `serial`, `partition`, `image_path` |
| `sideload_package` | Install OTA package | `serial`, `package_path` |
| `wipe_partition` | Erase partition | `serial`, `partition` |
| `unlock_bootloader` | Unlock bootloader | `serial` (with safety confirmations) |
| `lock_bootloader` | Lock bootloader | `serial` |
| `enable_root` | Enable root access | `serial` |
| `disable_root` | Disable root access | `serial` |
### Testing & Performance
#### Resources
| Resource URI | Description | Example Usage |
| ------------------------- | --------------- | -------------------------------- |
| `perf://{serial}/cpu` | CPU usage | "What's the CPU utilization?" |
| `perf://{serial}/gpu` | GPU usage | "What's the GPU load?" |
| `perf://{serial}/memory` | Memory usage | "What's the memory consumption?" |
| `perf://{serial}/battery` | Battery usage | "What's the power consumption?" |
| `perf://{serial}/network` | Network traffic | "What's the network activity?" |
#### Tools
| Tool Name | Description | Parameters |
| ----------------------- | ------------------- | ------------------------------------------------ |
| `run_instrumented_test` | Run test | `serial`, `package`, `test_class`, `test_method` |
| `run_monkey` | UI stress test | `serial`, `package`, `event_count` |
| `analyze_startup` | Measure app startup | `serial`, `package` |
| `analyze_frames` | Measure frame rate | `serial`, `package`, `duration` |
| `capture_systrace` | System trace | `serial`, `categories`, `duration` |
---
## 💻 Implementation Details
### Core Technologies
- **Python 3.10+**: Base programming language
- **MCP Python SDK**: Model Context Protocol implementation
- **ADB Python Wrappers**: For interfacing with Android Debug Bridge
- **AsyncIO**: For non-blocking operations
- **Image Processing Libraries**: For screenshot manipulation
### Sample Resource Implementation
```python
@mcp.resource("device://{serial}/properties")
def device_properties(serial: str, ctx: Context) -> str:
"""
Get detailed properties of a specific device.
Provides system properties including Android version, build details, and hardware info.
Args:
serial: Device serial number
Returns:
Formatted device properties as text
"""
adb_ctx = ctx.lifespan_context
# Validate device exists
if not device_exists(adb_ctx.adb_path, serial):
return "Error: Device not found"
# Get device properties
result = subprocess.run(
[adb_ctx.adb_path, "-s", serial, "shell", "getprop"],
capture_output=True, text=True, check=True
)
# Parse and format properties for better readability
props = {}
for line in result.stdout.splitlines():
match = re.match(r'\[(.+)\]: \[(.+)\]', line)
if match:
key, value = match.groups()
props[key] = value
# Extract key information
android_version = props.get("ro.build.version.release", "Unknown")
sdk_version = props.get("ro.build.version.sdk", "Unknown")
device_model = props.get("ro.product.model", "Unknown")
device_brand = props.get("ro.product.brand", "Unknown")
device_name = props.get("ro.product.name", "Unknown")
# Format output in a structured way
formatted_output = f"""
# Device Properties for {serial}
## Basic Information
- Model: {device_model}
- Brand: {device_brand}
- Name: {device_name}
- Android Version: {android_version}
- SDK Level: {sdk_version}
## Build Details
- Build Number: {props.get("ro.build.display.id", "Unknown")}
- Build Type: {props.get("ro.build.type", "Unknown")}
- Build Time: {props.get("ro.build.date", "Unknown")}
## Hardware
- Chipset: {props.get("ro.hardware", "Unknown")}
- Architecture: {props.get("ro.product.cpu.abi", "Unknown")}
- Screen Density: {props.get("ro.sf.lcd_density", "Unknown")}
"""
# Include full property dump at the end
formatted_output += "\n## All Properties\n"
for key, value in sorted(props.items()):
formatted_output += f"- {key}: {value}\n"
return formatted_output
```
### Sample Tool Implementation
```python
@mcp.tool()
async def install_app(
serial: str,
apk_path: str,
reinstall: bool = False,
grant_permissions: bool = True,
ctx: Context
) -> str:
"""
Install an APK on the target device
Args:
serial: Device serial number
apk_path: Path to the APK file (local to the server)
reinstall: Whether to reinstall if app exists
grant_permissions: Whether to grant all requested permissions
Returns:
Installation result message
"""
adb_ctx = ctx.lifespan_context
# Validate device exists
if not device_exists(adb_ctx.adb_path, serial):
return "Error: Device not found"
# Validate APK exists
if not os.path.exists(apk_path):
return f"Error: APK file not found at {apk_path}"
# Build command with options
cmd = [adb_ctx.adb_path, "-s", serial, "install"]
if reinstall:
cmd.append("-r")
if grant_permissions:
cmd.append("-g")
cmd.append(apk_path)
# Log command execution
ctx.info(f"Installing APK: {os.path.basename(apk_path)}")
# Execute install with progress reporting
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
# Process output and report progress
total_size = os.path.getsize(apk_path)
reported_progress = 0
while True:
line = await process.stdout.readline()
if not line:
break
line_text = line.decode('utf-8').strip()
# Parse progress information
if "Performing Streamed Install" in line_text:
ctx.info("Starting streamed install...")
elif "%" in line_text:
# Extract progress percentage
match = re.search(r'(\d+)%', line_text)
if match:
progress = int(match.group(1))
if progress > reported_progress:
reported_progress = progress
await ctx.report_progress(progress, 100)
# Get final result
await process.wait()
stderr = (await process.stderr.read()).decode('utf-8')
if process.returncode != 0:
return f"Error installing APK: {stderr}"
# Get the package name from the APK
package_name = await get_apk_package_name(apk_path)
return f"Successfully installed {package_name}"
```
### Handling Screenshots
```python
@mcp.tool()
async def capture_screenshot(serial: str, ctx: Context) -> Image:
"""
Capture a screenshot from the device
Args:
serial: Device serial number
Returns:
Device screenshot as an image
"""
adb_ctx = ctx.lifespan_context
# Validate device exists
if not device_exists(adb_ctx.adb_path, serial):
raise ValueError(f"Device {serial} not found")
# Generate temp file path
screenshot_path = os.path.join(adb_ctx.temp_dir, f"{serial}_screen.png")
ctx.info(f"Capturing screenshot from {serial}...")
try:
# Take screenshot and pull to local temp directory
await ctx.report_progress(0, 3)
# Step 1: Capture screenshot on device
process = await asyncio.create_subprocess_exec(
adb_ctx.adb_path, "-s", serial, "shell", "screencap", "-p", "/sdcard/screen.png",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
_, stderr = await process.communicate()
if process.returncode != 0:
raise RuntimeError(f"Failed to capture screenshot: {stderr.decode('utf-8')}")
await ctx.report_progress(1, 3)
# Step 2: Pull file to local temp directory
process = await asyncio.create_subprocess_exec(
adb_ctx.adb_path, "-s", serial, "pull", "/sdcard/screen.png", screenshot_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
_, stderr = await process.communicate()
if process.returncode != 0:
raise RuntimeError(f"Failed to pull screenshot: {stderr.decode('utf-8')}")
await ctx.report_progress(2, 3)
# Step 3: Clean up on device
process = await asyncio.create_subprocess_exec(
adb_ctx.adb_path, "-s", serial, "shell", "rm", "/sdcard/screen.png",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await process.communicate()
await ctx.report_progress(3, 3)
# Read the image and return it
with open(screenshot_path, "rb") as f:
image_data = f.read()
ctx.info(f"Screenshot captured successfully ({len(image_data)} bytes)")
return Image(data=image_data, format="png")
except Exception as e:
ctx.error(f"Error capturing screenshot: {str(e)}")
raise
finally:
# Clean up local temp file
if os.path.exists(screenshot_path):
os.unlink(screenshot_path)
```
### Sample Prompt Implementation
```python
@mcp.prompt()
def analyze_anr(anr_trace: str) -> str:
"""
Generate a prompt to analyze an Application Not Responding (ANR) trace
Args:
anr_trace: The ANR trace content to analyze
"""
return f"""I need help analyzing this Android Application Not Responding (ANR) trace.
Can you identify the root cause and suggest potential fixes?
```
{anr_trace}
```
Please include in your analysis:
1. Which thread is causing the ANR
2. What that thread was doing when it got stuck
3. Any locks or resources being waited on
4. Potential code fixes to resolve the issue
"""
```
## 🔐 Security Considerations
Security is a critical component of the Android MCP Server, especially when dealing with development devices that may contain sensitive information or have elevated privileges.
### Command Sanitization
All inputs to ADB commands are carefully sanitized to prevent shell injection:
```python
def sanitize_path(path: str) -> str:
"""Sanitize a path to prevent command injection"""
# Remove shell special characters and escape sequences
sanitized = re.sub(r'[;&|<>$`\\"\']', '', path)
# Prevent path traversal
sanitized = os.path.normpath(sanitized)
if sanitized.startswith('..'):
raise ValueError("Path traversal detected")
return sanitized
```
### Permission Model
Operations are categorized by risk level:
1. **Safe Operations**
- Read-only operations (retrieving logs, properties)
- Screen captures and UI inspection
- Running safe diagnostic commands
2. **Moderate Risk Operations**
- Installing/uninstalling applications
- Modifying application data
- Changing system settings
- File transfers
3. **High Risk Operations**
- Flashing system images
- Bootloader operations
- Partition wiping
- Root access operations
High-risk operations require explicit confirmation and display detailed warnings about potential consequences.
### Device Authentication
For wireless devices, proper authentication is enforced:
```python
@mcp.tool()
async def connect_wireless_device(
ip_address: str,
port: int = 5555,
pairing_code: str = None,
ctx: Context
) -> str:
"""Connect to a wireless device with proper authentication"""
# For Android 11+ devices, require a pairing code for secure connection
device_os_version = await get_device_os_version(ip_address, port)
if device_os_version >= 30: # Android 11+
if not pairing_code:
return "Error: Devices running Android 11+ require a pairing code for wireless debugging"
# Use the secure pairing method
result = await pair_with_device(ip_address, port, pairing_code)
if not result.success:
return f"Pairing failed: {result.message}"
# Connect to the device
result = await connect_to_device(ip_address, port)
return result.message
```
## 🏁 Getting Started
### Prerequisites
- Python 3.10 or higher
- Android SDK Platform Tools (ADB)
- Connected Android device or emulator
- MCP-compatible IDE (e.g., Cursor)
### Installation
```bash
# Install using pip
pip install android-mcp-server
# Or install with extra dependencies
pip install "android-mcp-server[gui,monitoring]"
```
### Configuration
Create a configuration file at `~/.config/android-mcp/config.yaml`:
```yaml
# Android MCP Server Configuration
# ADB Configuration
adb:
# Path to ADB executable (leave empty to use PATH)
path: ""
# Default server port
server_port: 5037
# Additional ADB server options
server_options: {}
# Security settings
security:
# Require confirmation for high-risk operations (recommended)
confirm_high_risk: true
# Restrict access to certain device paths
restricted_paths:
- "/data/system"
- "/data/misc/keystore"
- "/data/misc/user"
# Device permissions (serial -> allowed operations)
device_permissions:
"emulator-5554":
allow_all: true
"ABCD1234":
allow_install: true
allow_file_access: true
allow_flash: false
# Server settings
server:
# Logging verbosity (debug, info, warning, error)
log_level: "info"
# Temporary file directory (leave empty for system default)
temp_dir: ""
# Maximum file size for transfers (in bytes)
max_file_size: 104857600 # 100 MB
```
### Running the Server
```bash
# Run with default settings
android-mcp-server
# Specify configuration file
android-mcp-server --config /path/to/config.yaml
# Connect to specific devices only
android-mcp-server --devices emulator-5554,ABCD1234
# Run with debugging enabled
android-mcp-server --debug
# Start in development mode with MCP Inspector
android-mcp-server --dev
```
### Connecting to Cursor
1. Configure Cursor to use the Android MCP Server:
- Open Cursor settings
- Navigate to AI Integrations -> MCP
- Add a new MCP server with the command `android-mcp-server`
2. Start using the server in your workflow:
- Ask Claude about connected devices
- Request logs or screenshots
- Analyze app performance
- Debug crashes with AI assistance
---
## 🚀 Roadmap
### Version 1.0
- [x] Basic device management
- [x] Application installation and management
- [x] Log access and analysis
- [x] Screenshot capture
- [x] File system operations
### Version 1.1
- [ ] Wireless device connection
- [ ] Enhanced file transfer capabilities
- [ ] UI testing framework integration
- [ ] Performance monitoring dashboard
### Version 1.2
- [ ] ROM flashing and management
- [ ] System trace collection and analysis
- [ ] Network traffic monitoring
- [ ] Battery usage optimization
### Version 2.0
- [ ] Multi-device parallel operations
- [ ] Integrated CI/CD workflows
- [ ] Custom plugin architecture
- [ ] Remote device farm integration
---
## 🤝 Contributing
Contributions to the Android MCP Server are welcome! Here's how you can help:
1. **Report Issues**: Submit bugs or feature requests via GitHub issues
2. **Contribute Code**: Fork the repository and submit pull requests
3. **Improve Documentation**: Help enhance the documentation and examples
4. **Share Use Cases**: Share your Android development workflows
Please review our [contributing guidelines](CONTRIBUTING.md) before making contributions.
---
## 📄 License
This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
---
## 🙏 Acknowledgments
- [Model Context Protocol](https://modelcontextprotocol.io/) team for the protocol specification
- [Android Open Source Project](https://source.android.com/) for Android tools and documentation
- [Cursor IDE](https://cursor.sh/) for AI-powered development environment
- All contributors who help improve this project
---
<div align="center">
Made with ❤️ for Android developers everywhere
</div>