quash-mcp 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of quash-mcp might be problematic. Click here for more details.
- quash_mcp/__init__.py +1 -0
- quash_mcp/__main__.py +10 -0
- quash_mcp/backend_client.py +203 -0
- quash_mcp/server.py +399 -0
- quash_mcp/state.py +137 -0
- quash_mcp/tools/__init__.py +9 -0
- quash_mcp/tools/build.py +739 -0
- quash_mcp/tools/build_old.py +185 -0
- quash_mcp/tools/configure.py +140 -0
- quash_mcp/tools/connect.py +153 -0
- quash_mcp/tools/execute.py +177 -0
- quash_mcp/tools/runsuite.py +209 -0
- quash_mcp/tools/usage.py +31 -0
- quash_mcp-0.2.0.dist-info/METADATA +271 -0
- quash_mcp-0.2.0.dist-info/RECORD +17 -0
- quash_mcp-0.2.0.dist-info/WHEEL +4 -0
- quash_mcp-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Run Suite tool - Execute multiple tasks in sequence like test suites.
|
|
3
|
+
Mimics the Electron app's suite execution functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import time
|
|
8
|
+
from typing import Dict, Any, List, Optional, Callable
|
|
9
|
+
from ..state import get_state
|
|
10
|
+
from .execute import execute
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def runsuite(
|
|
14
|
+
suite_name: str,
|
|
15
|
+
tasks: List[Dict[str, Any]],
|
|
16
|
+
progress_callback: Optional[Callable[[str], None]] = None
|
|
17
|
+
) -> Dict[str, Any]:
|
|
18
|
+
"""
|
|
19
|
+
Execute a suite of tasks in sequence.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
suite_name: Name of the suite being executed
|
|
23
|
+
tasks: List of task definitions with:
|
|
24
|
+
- prompt (str): Task instruction
|
|
25
|
+
- type (str): 'setup', 'test', or 'teardown' (optional, default 'test')
|
|
26
|
+
- retries (int): Number of retry attempts (optional, default 0)
|
|
27
|
+
- continueOnFailure (bool): Continue suite if task fails (optional, default False)
|
|
28
|
+
- waitBefore (int): Seconds to wait before executing (optional, default 0)
|
|
29
|
+
progress_callback: Optional callback for progress updates
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dict with suite execution results
|
|
33
|
+
"""
|
|
34
|
+
state = get_state()
|
|
35
|
+
|
|
36
|
+
# Validate prerequisites
|
|
37
|
+
if not state.is_ready():
|
|
38
|
+
return {
|
|
39
|
+
"status": "error",
|
|
40
|
+
"message": "❌ Not ready to execute suite. Please connect device and configure first.",
|
|
41
|
+
"suite_name": suite_name,
|
|
42
|
+
"completed_tasks": 0,
|
|
43
|
+
"total_tasks": len(tasks)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Validate tasks
|
|
47
|
+
if not tasks or len(tasks) == 0:
|
|
48
|
+
return {
|
|
49
|
+
"status": "error",
|
|
50
|
+
"message": "❌ Suite must have at least one task",
|
|
51
|
+
"suite_name": suite_name,
|
|
52
|
+
"completed_tasks": 0,
|
|
53
|
+
"total_tasks": 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Execution tracking
|
|
57
|
+
suite_start_time = time.time()
|
|
58
|
+
results = []
|
|
59
|
+
completed_tasks = 0
|
|
60
|
+
failed_tasks = 0
|
|
61
|
+
skipped_tasks = 0
|
|
62
|
+
|
|
63
|
+
def log_progress(message: str):
|
|
64
|
+
"""Helper to log progress"""
|
|
65
|
+
if progress_callback:
|
|
66
|
+
progress_callback(message)
|
|
67
|
+
|
|
68
|
+
log_progress(f"🚀 Starting suite: {suite_name}")
|
|
69
|
+
log_progress(f"📋 Total tasks: {len(tasks)}")
|
|
70
|
+
log_progress("")
|
|
71
|
+
|
|
72
|
+
# Execute each task
|
|
73
|
+
for idx, task_def in enumerate(tasks, 1):
|
|
74
|
+
task_prompt = task_def.get('prompt')
|
|
75
|
+
task_type = task_def.get('type', 'test')
|
|
76
|
+
retries = task_def.get('retries', 0)
|
|
77
|
+
continue_on_failure = task_def.get('continueOnFailure', False)
|
|
78
|
+
wait_before = task_def.get('waitBefore', 0)
|
|
79
|
+
|
|
80
|
+
# Validate task has prompt
|
|
81
|
+
if not task_prompt:
|
|
82
|
+
log_progress(f"⚠️ Task {idx}: Skipped (no prompt)")
|
|
83
|
+
skipped_tasks += 1
|
|
84
|
+
results.append({
|
|
85
|
+
"task_number": idx,
|
|
86
|
+
"type": task_type,
|
|
87
|
+
"status": "skipped",
|
|
88
|
+
"message": "No prompt provided"
|
|
89
|
+
})
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Wait before executing if specified
|
|
93
|
+
if wait_before > 0:
|
|
94
|
+
log_progress(f"⏳ Task {idx}: Waiting {wait_before}s before execution...")
|
|
95
|
+
await asyncio.sleep(wait_before)
|
|
96
|
+
|
|
97
|
+
# Task execution with retries
|
|
98
|
+
task_success = False
|
|
99
|
+
task_attempts = 0
|
|
100
|
+
max_attempts = retries + 1
|
|
101
|
+
task_result = None
|
|
102
|
+
|
|
103
|
+
log_progress(f"▶️ Task {idx}/{len(tasks)} [{task_type.upper()}]: {task_prompt[:60]}{'...' if len(task_prompt) > 60 else ''}")
|
|
104
|
+
|
|
105
|
+
while task_attempts < max_attempts and not task_success:
|
|
106
|
+
task_attempts += 1
|
|
107
|
+
|
|
108
|
+
if task_attempts > 1:
|
|
109
|
+
log_progress(f" 🔄 Retry {task_attempts - 1}/{retries}...")
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Execute the task
|
|
113
|
+
task_result = await execute(
|
|
114
|
+
task=task_prompt,
|
|
115
|
+
progress_callback=lambda msg: log_progress(f" {msg}")
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if task_result.get('status') == 'success':
|
|
119
|
+
task_success = True
|
|
120
|
+
log_progress(f" ✅ Task {idx} completed")
|
|
121
|
+
else:
|
|
122
|
+
if task_attempts < max_attempts:
|
|
123
|
+
log_progress(f" ⚠️ Task {idx} failed, retrying...")
|
|
124
|
+
else:
|
|
125
|
+
log_progress(f" ❌ Task {idx} failed after {task_attempts} attempt(s)")
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
log_progress(f" ❌ Task {idx} error: {str(e)}")
|
|
129
|
+
task_result = {
|
|
130
|
+
"status": "failed",
|
|
131
|
+
"message": str(e)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Record task result
|
|
135
|
+
if task_success:
|
|
136
|
+
completed_tasks += 1
|
|
137
|
+
results.append({
|
|
138
|
+
"task_number": idx,
|
|
139
|
+
"type": task_type,
|
|
140
|
+
"prompt": task_prompt[:100],
|
|
141
|
+
"status": "completed",
|
|
142
|
+
"attempts": task_attempts,
|
|
143
|
+
"message": task_result.get('message', 'Success')
|
|
144
|
+
})
|
|
145
|
+
else:
|
|
146
|
+
failed_tasks += 1
|
|
147
|
+
results.append({
|
|
148
|
+
"task_number": idx,
|
|
149
|
+
"type": task_type,
|
|
150
|
+
"prompt": task_prompt[:100],
|
|
151
|
+
"status": "failed",
|
|
152
|
+
"attempts": task_attempts,
|
|
153
|
+
"message": task_result.get('message', 'Failed') if task_result else 'Unknown error'
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
# Check if we should continue or stop
|
|
157
|
+
if not continue_on_failure:
|
|
158
|
+
log_progress("")
|
|
159
|
+
log_progress(f"⛔ Stopping suite execution (task {idx} failed and continueOnFailure=False)")
|
|
160
|
+
log_progress(f"📊 Remaining tasks: {len(tasks) - idx} (skipped)")
|
|
161
|
+
skipped_tasks = len(tasks) - idx
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
log_progress("")
|
|
165
|
+
|
|
166
|
+
# Calculate suite results
|
|
167
|
+
suite_duration = time.time() - suite_start_time
|
|
168
|
+
total_tasks = len(tasks)
|
|
169
|
+
pass_rate = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0
|
|
170
|
+
|
|
171
|
+
# Determine overall status
|
|
172
|
+
if completed_tasks == total_tasks:
|
|
173
|
+
status = "completed"
|
|
174
|
+
message = f"✅ Suite '{suite_name}' completed successfully"
|
|
175
|
+
elif completed_tasks > 0:
|
|
176
|
+
status = "partial"
|
|
177
|
+
message = f"⚠️ Suite '{suite_name}' partially completed"
|
|
178
|
+
else:
|
|
179
|
+
status = "failed"
|
|
180
|
+
message = f"❌ Suite '{suite_name}' failed"
|
|
181
|
+
|
|
182
|
+
# Final summary
|
|
183
|
+
log_progress("=" * 60)
|
|
184
|
+
log_progress(f"📊 Suite Execution Summary: {suite_name}")
|
|
185
|
+
log_progress("=" * 60)
|
|
186
|
+
log_progress(f"✅ Completed: {completed_tasks}/{total_tasks}")
|
|
187
|
+
log_progress(f"❌ Failed: {failed_tasks}")
|
|
188
|
+
log_progress(f"⏭️ Skipped: {skipped_tasks}")
|
|
189
|
+
log_progress(f"📈 Pass Rate: {pass_rate:.1f}%")
|
|
190
|
+
log_progress(f"⏱️ Duration: {suite_duration:.1f}s")
|
|
191
|
+
log_progress("=" * 60)
|
|
192
|
+
|
|
193
|
+
result = {
|
|
194
|
+
"status": status,
|
|
195
|
+
"message": message,
|
|
196
|
+
"suite_name": suite_name,
|
|
197
|
+
"total_tasks": total_tasks,
|
|
198
|
+
"completed_tasks": completed_tasks,
|
|
199
|
+
"failed_tasks": failed_tasks,
|
|
200
|
+
"skipped_tasks": skipped_tasks,
|
|
201
|
+
"pass_rate": round(pass_rate, 1),
|
|
202
|
+
"duration_seconds": round(suite_duration, 1),
|
|
203
|
+
"task_results": results
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# Save latest suite execution to state
|
|
207
|
+
state.latest_suite = result
|
|
208
|
+
|
|
209
|
+
return result
|
quash_mcp/tools/usage.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Usage tool - View usage statistics and costs from backend.
|
|
3
|
+
Queries the backend API for usage statistics instead of local tracking.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def usage(api_key: Optional[str] = None, show_recent: int = 5) -> Dict[str, Any]:
|
|
10
|
+
"""
|
|
11
|
+
View usage statistics for Quash executions from backend.
|
|
12
|
+
|
|
13
|
+
Note: In v0.2.0, all usage tracking happens on the backend.
|
|
14
|
+
This tool would need backend API support to fetch usage stats.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
api_key: Specific API key to query (optional - shows all if not provided)
|
|
18
|
+
show_recent: Number of recent executions to show (default: 5)
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Dict with usage statistics and execution history
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# TODO: Implement backend API call to fetch usage stats
|
|
25
|
+
# For now, return a message directing users to the web portal
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
"status": "info",
|
|
29
|
+
"message": "📊 Usage statistics are tracked on the backend. Please visit https://quashbugs.com/dashboard to view your usage, costs, and execution history.",
|
|
30
|
+
"note": "All usage tracking, token counts, and costs are now managed server-side for security."
|
|
31
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quash-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Model Context Protocol server for Quash - AI-powered mobile automation agent
|
|
5
|
+
Project-URL: Homepage, https://quashbugs.com
|
|
6
|
+
Project-URL: Repository, https://github.com/quash/quash-mcp
|
|
7
|
+
Project-URL: Documentation, https://docs.quashbugs.com
|
|
8
|
+
Project-URL: Issues, https://github.com/quash/quash-mcp/issues
|
|
9
|
+
Author-email: Quash Team <hello@quashbugs.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: ai,android,mcp,mobile-automation,quash,testing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: adbutils==2.10.0
|
|
21
|
+
Requires-Dist: apkutils==2.0.0
|
|
22
|
+
Requires-Dist: click>=8.1.0
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: mcp>=0.9.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Quash MCP - AI-Powered Mobile Automation
|
|
34
|
+
|
|
35
|
+
A Model Context Protocol (MCP) server for mobile automation testing with Quash. Control Android devices and run automated tests from **any MCP-compatible host** using natural language.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- 🤖 **AI-Powered Automation**: Control your Android device using plain English
|
|
40
|
+
- 📱 **Device Connection**: Works with emulators and physical devices
|
|
41
|
+
- ⚙️ **Flexible Configuration**: Customize AI model, temperature, vision, reasoning, and more
|
|
42
|
+
- 🔄 **Real-Time Execution**: Live progress streaming during task execution
|
|
43
|
+
- 🎯 **Suite Execution**: Run multiple tasks in sequence with retry logic
|
|
44
|
+
- 📊 **Usage Tracking**: Monitor API costs and token usage
|
|
45
|
+
- 🔐 **Secure**: API key authentication via Quash platform
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install quash-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
All dependencies (including ADB tools and device connectivity) are automatically installed. **AI execution happens on the Quash backend**, keeping the client lightweight and proprietary logic protected.
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Get Your API Key
|
|
58
|
+
|
|
59
|
+
1. Visit [quashbugs.com](https://quashbugs.com) (or your deployment URL)
|
|
60
|
+
2. Sign in with Google
|
|
61
|
+
3. Go to Dashboard → API Keys
|
|
62
|
+
4. Create a new API key
|
|
63
|
+
|
|
64
|
+
### 2. Add to Your MCP Host
|
|
65
|
+
|
|
66
|
+
Quash MCP works with any MCP-compatible host. Configure it to use `python3 -m quash_mcp` which works across all Python environments:
|
|
67
|
+
|
|
68
|
+
#### Manual Configuration (Recommended)
|
|
69
|
+
|
|
70
|
+
Add to your MCP host's config file:
|
|
71
|
+
|
|
72
|
+
**Config file locations:**
|
|
73
|
+
- **Claude Desktop (macOS)**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
74
|
+
- **Claude Desktop (Linux)**: `~/.config/claude/claude_desktop_config.json`
|
|
75
|
+
- **Claude Desktop (Windows)**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
76
|
+
- **Claude Code**: `~/.claude.json` (project-specific under `projects.<path>.mcpServers`)
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"quash": {
|
|
82
|
+
"command": "python3",
|
|
83
|
+
"args": ["-m", "quash_mcp"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Why `python3 -m quash_mcp`?**
|
|
90
|
+
- Works in any Python environment (conda, venv, system)
|
|
91
|
+
- No PATH configuration needed
|
|
92
|
+
- Uses whichever Python has quash-mcp installed
|
|
93
|
+
|
|
94
|
+
#### Alternative: Direct Command (if in PATH)
|
|
95
|
+
|
|
96
|
+
If `quash-mcp` is in your PATH:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"quash": {
|
|
102
|
+
"command": "quash-mcp"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Then restart your MCP host.
|
|
109
|
+
|
|
110
|
+
### 3. Start Automating
|
|
111
|
+
|
|
112
|
+
Ask your AI assistant (via your MCP host):
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
"Setup Quash and connect to my Android device"
|
|
116
|
+
"Configure with my API key: mhg_xxxx..."
|
|
117
|
+
"Execute task: Open Settings and enable WiFi"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Available Tools
|
|
121
|
+
|
|
122
|
+
### 1. `build`
|
|
123
|
+
Setup and verify all dependencies.
|
|
124
|
+
|
|
125
|
+
**Example:**
|
|
126
|
+
```
|
|
127
|
+
Can you run the build tool to setup my system for Quash?
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2. `connect`
|
|
131
|
+
Connect to an Android device or emulator.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
- `device_serial` (optional): Device serial number
|
|
135
|
+
|
|
136
|
+
**Example:**
|
|
137
|
+
```
|
|
138
|
+
Connect to my Android device
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. `configure`
|
|
142
|
+
Configure agent execution parameters.
|
|
143
|
+
|
|
144
|
+
**Parameters:**
|
|
145
|
+
- `quash_api_key`: Your Quash API key from the web portal
|
|
146
|
+
- `model`: LLM model (e.g., "anthropic/claude-sonnet-4", "openai/gpt-4o")
|
|
147
|
+
- `temperature`: 0-2 (default: 0.2)
|
|
148
|
+
- `max_steps`: Maximum execution steps (default: 15)
|
|
149
|
+
- `vision`: Enable screenshots (default: false)
|
|
150
|
+
- `reasoning`: Enable multi-step planning (default: false)
|
|
151
|
+
- `reflection`: Enable self-improvement (default: false)
|
|
152
|
+
- `debug`: Verbose logging (default: false)
|
|
153
|
+
|
|
154
|
+
**Example:**
|
|
155
|
+
```
|
|
156
|
+
Configure Quash with my API key mhg_xxx, use Claude Sonnet 4, and enable vision
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 4. `execute`
|
|
160
|
+
Run an automation task on the device.
|
|
161
|
+
|
|
162
|
+
**Parameters:**
|
|
163
|
+
- `task`: Natural language task description
|
|
164
|
+
|
|
165
|
+
**Example:**
|
|
166
|
+
```
|
|
167
|
+
Execute task: Open Settings and navigate to WiFi settings
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 5. `runsuite`
|
|
171
|
+
Execute multiple tasks in sequence with retry logic.
|
|
172
|
+
|
|
173
|
+
**Parameters:**
|
|
174
|
+
- `suite_name`: Name of the test suite
|
|
175
|
+
- `tasks`: Array of tasks with retry and failure handling options
|
|
176
|
+
|
|
177
|
+
**Example:**
|
|
178
|
+
```
|
|
179
|
+
Run a test suite with these tasks: [
|
|
180
|
+
{"prompt": "Open Settings", "type": "setup"},
|
|
181
|
+
{"prompt": "Enable WiFi", "type": "test", "retries": 2},
|
|
182
|
+
{"prompt": "Close Settings", "type": "teardown"}
|
|
183
|
+
]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 6. `usage`
|
|
187
|
+
View API usage statistics and costs.
|
|
188
|
+
|
|
189
|
+
**Example:**
|
|
190
|
+
```
|
|
191
|
+
Show me my Quash usage statistics
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Complete Workflow Example
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
User: "Setup Quash on my machine"
|
|
198
|
+
→ Runs build tool
|
|
199
|
+
→ Returns: All dependencies installed ✓
|
|
200
|
+
|
|
201
|
+
User: "Connect to my Android emulator"
|
|
202
|
+
→ Runs connect tool
|
|
203
|
+
→ Returns: Connected to emulator-5554 ✓
|
|
204
|
+
|
|
205
|
+
User: "Configure to use Claude Sonnet 4 with vision and my API key is mhg_xxx..."
|
|
206
|
+
→ Runs configure tool
|
|
207
|
+
→ Returns: Configuration set ✓
|
|
208
|
+
|
|
209
|
+
User: "Execute task: Open Instagram and go to my profile"
|
|
210
|
+
→ Runs execute tool with live streaming
|
|
211
|
+
→ Returns: Task completed ✓
|
|
212
|
+
|
|
213
|
+
User: "Show me my usage statistics"
|
|
214
|
+
→ Runs usage tool
|
|
215
|
+
→ Returns: Total cost: $0.15, 10 executions ✓
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Requirements
|
|
219
|
+
|
|
220
|
+
- **Python 3.11+** - Required for the MCP server
|
|
221
|
+
- **Android Device** - Emulator or physical device with USB debugging enabled
|
|
222
|
+
- **Quash API Key** - Get from [quashbugs.com](https://quashbugs.com)
|
|
223
|
+
|
|
224
|
+
Dependencies automatically installed:
|
|
225
|
+
- Android Debug Bridge (ADB) - via `adbutils`
|
|
226
|
+
- Quash Portal APK - via `apkutils`
|
|
227
|
+
- MCP protocol support - via `mcp`
|
|
228
|
+
- HTTP client - via `httpx`
|
|
229
|
+
|
|
230
|
+
## Architecture
|
|
231
|
+
|
|
232
|
+
**v0.2.0 uses a client-server architecture:**
|
|
233
|
+
- **Client (quash-mcp)**: Lightweight MCP server handling device connections and API calls
|
|
234
|
+
- **Server (Quash backend)**: Proprietary AI execution engine (LLMs, agents, pricing logic)
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
quash-mcp/
|
|
238
|
+
├── quash_mcp/
|
|
239
|
+
│ ├── __main__.py # Module entry point for python -m
|
|
240
|
+
│ ├── server.py # Main MCP server entry point
|
|
241
|
+
│ ├── backend_client.py # API communication with Quash backend
|
|
242
|
+
│ ├── state.py # Session state management
|
|
243
|
+
│ └── tools/
|
|
244
|
+
│ ├── build.py # Dependency checker and installer
|
|
245
|
+
│ ├── connect.py # Device connectivity
|
|
246
|
+
│ ├── configure.py # Agent configuration
|
|
247
|
+
│ ├── execute.py # Task execution (calls backend API)
|
|
248
|
+
│ ├── runsuite.py # Suite execution (calls backend API)
|
|
249
|
+
│ └── usage.py # Usage statistics (from backend)
|
|
250
|
+
└── pyproject.toml
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Troubleshooting
|
|
254
|
+
|
|
255
|
+
**"No devices found"**
|
|
256
|
+
- Start Android emulator via Android Studio > AVD Manager
|
|
257
|
+
- Connect physical device with USB debugging enabled
|
|
258
|
+
- For WiFi debugging: `adb tcpip 5555 && adb connect <device-ip>:5555`
|
|
259
|
+
|
|
260
|
+
**"Portal not ready"**
|
|
261
|
+
- The `connect` tool automatically installs the Portal APK
|
|
262
|
+
- If it fails, manually enable the Quash Portal accessibility service in Settings > Accessibility
|
|
263
|
+
|
|
264
|
+
**"Invalid API key"**
|
|
265
|
+
- Make sure you've run `configure` with a valid API key from quashbugs.com
|
|
266
|
+
- API keys start with `mhg_` prefix
|
|
267
|
+
- Check your API key hasn't been revoked in the web portal
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
quash_mcp/__init__.py,sha256=LImiWCRgjAbb5DZXBq2DktUEAbftvnO61Vil4Ayun9A,39
|
|
2
|
+
quash_mcp/__main__.py,sha256=WCg5OlnXhr6i0XJHAUGpbhliMy3qE2SJkFzVD4wO-lw,239
|
|
3
|
+
quash_mcp/backend_client.py,sha256=FmwBVf_1nmWk18kXWuWXnDTYgL6x9kKqiNVULlW-x48,6844
|
|
4
|
+
quash_mcp/server.py,sha256=scUGnplxjsvyYLK2q6hrjl-5Chkdnat9pODDtLzsQFY,15519
|
|
5
|
+
quash_mcp/state.py,sha256=Tnt795GnZcas-h62Y6KYyIZVopeoWPM0TbRwOeVFYj4,4394
|
|
6
|
+
quash_mcp/tools/__init__.py,sha256=r4fMAjHDjHUbimRwYW7VYUDkQHs12UVsG_IBmWpeX9s,249
|
|
7
|
+
quash_mcp/tools/build.py,sha256=PAFx4YXXbJ_BFXrixZfHR593vN7WzrqW6J0Zg7Wwr2s,29326
|
|
8
|
+
quash_mcp/tools/build_old.py,sha256=6M9gaqZ_dX4B7UFTxSMD8T1BX0zEwQUL7RJ8ItNfB54,6016
|
|
9
|
+
quash_mcp/tools/configure.py,sha256=cv4RTolu6qae-XzyACSJUDrALfd0gYC-XE5s66_zfNk,4439
|
|
10
|
+
quash_mcp/tools/connect.py,sha256=odrbxAYxLQNzdvlEX1XVLb4BXEZ276AG9wXOpLsOJ6Q,4783
|
|
11
|
+
quash_mcp/tools/execute.py,sha256=waWnaD0dEVcOJgRBbqZo3HnxME1s6YUOn8aRbm4R3X4,6081
|
|
12
|
+
quash_mcp/tools/runsuite.py,sha256=gohLk9FpN8v7F0a69fspqOqUexTcslpYf3qU-iIZZ3s,7220
|
|
13
|
+
quash_mcp/tools/usage.py,sha256=g76A6FO36fThoyRFG7q92QmS3Kh1pIKOrhYOzUdIubA,1155
|
|
14
|
+
quash_mcp-0.2.0.dist-info/METADATA,sha256=iNrSIhKB2rU5vlhHdJ4yunvnaf6B5GqkKV8XyTjnZcc,8097
|
|
15
|
+
quash_mcp-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
quash_mcp-0.2.0.dist-info/entry_points.txt,sha256=9sbDxrx0ApGDVRS-IE3mQgSao3DwKnnV_k-_ipFn9QI,52
|
|
17
|
+
quash_mcp-0.2.0.dist-info/RECORD,,
|