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.

@@ -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
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ quash-mcp = quash_mcp.server:main