quash-mcp 0.2.13__tar.gz → 0.2.14__tar.gz
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-0.2.13 → quash_mcp-0.2.14}/PKG-INFO +1 -1
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/local_test.py +5 -3
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/pyproject.toml +1 -1
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/backend_client.py +3 -2
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/execute_v3.py +60 -48
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/.gitignore +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/README.md +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/SETUP_CLAUDE_CODE.md +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/__init__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/__main__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/device/__init__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/device/adb_tools.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/device/portal.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/device/state_capture.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/models.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/server.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/state.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/__init__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/build.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/build_old.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/configure.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/connect.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/execute.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/execute_v2_backup.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/runsuite.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/quash_mcp/tools/usage.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/test_backend_integration.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.14}/test_tools_loading.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quash-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: Model Context Protocol server for Quash - AI-powered mobile automation agent
|
|
5
5
|
Project-URL: Homepage, https://quashbugs.com
|
|
6
6
|
Project-URL: Repository, https://github.com/quash/quash-mcp
|
|
@@ -13,13 +13,15 @@ from quash_mcp.models import SessionDTO, ConfigInfo, UIStateInfo
|
|
|
13
13
|
async def main():
|
|
14
14
|
# Initialize the session state
|
|
15
15
|
state = get_state()
|
|
16
|
-
state.config["api_key"] = "
|
|
16
|
+
state.config["api_key"] = "mhg_AMncG3jkTtEntd8vTqJk3nHl6rnxceRr"
|
|
17
17
|
state.device_serial = "emulator-5554"
|
|
18
18
|
state.portal_ready = True # Assume portal is ready for local testing
|
|
19
19
|
|
|
20
20
|
# Define the task
|
|
21
|
-
task = "Open
|
|
22
|
-
|
|
21
|
+
# task = "Open the google search app and search for bakeries."
|
|
22
|
+
task = "Open settings and scroll 3 times."
|
|
23
|
+
# task = "Go home"
|
|
24
|
+
# task = "Open chrome, search for quashbugs.com and scroll down."
|
|
23
25
|
|
|
24
26
|
# Define a progress callback
|
|
25
27
|
def progress_callback(message):
|
|
@@ -20,7 +20,7 @@ class BackendClient:
|
|
|
20
20
|
|
|
21
21
|
def __init__(self):
|
|
22
22
|
# Get backend URL from environment variable, default to production backend
|
|
23
|
-
self.base_url = os.getenv("MAHORAGA_BACKEND_URL", "
|
|
23
|
+
self.base_url = os.getenv("MAHORAGA_BACKEND_URL", "http://localhost:8000")
|
|
24
24
|
self.timeout = 300.0 # 5 minutes for long-running LLM calls
|
|
25
25
|
logger.info(f"🔧 Backend client initialized: URL={self.base_url}")
|
|
26
26
|
|
|
@@ -35,7 +35,8 @@ class BackendClient:
|
|
|
35
35
|
Dict with validation result:
|
|
36
36
|
{
|
|
37
37
|
"valid": bool,
|
|
38
|
-
"user": {"email": str, "name": str
|
|
38
|
+
"user": {"email": str, "name": str},
|
|
39
|
+
"organization_credits": float,
|
|
39
40
|
"openrouter_api_key": str,
|
|
40
41
|
"error": str (if invalid)
|
|
41
42
|
}
|
|
@@ -17,6 +17,7 @@ from ..state import get_state
|
|
|
17
17
|
from ..backend_client import get_backend_client
|
|
18
18
|
from ..device.state_capture import get_device_state
|
|
19
19
|
from ..device.adb_tools import AdbTools
|
|
20
|
+
import logging
|
|
20
21
|
|
|
21
22
|
# Import mahoraga components for tool functions
|
|
22
23
|
try:
|
|
@@ -236,12 +237,12 @@ async def execute_v3(
|
|
|
236
237
|
|
|
237
238
|
# Check credits
|
|
238
239
|
user_info = validation_result.get("user", {})
|
|
239
|
-
|
|
240
|
+
organization_credits = validation_result.get("organization_credits", 0)
|
|
240
241
|
|
|
241
|
-
if
|
|
242
|
+
if organization_credits <= 0:
|
|
242
243
|
return {
|
|
243
244
|
"status": "error",
|
|
244
|
-
"message": f"❌ Insufficient credits. Current balance: ${
|
|
245
|
+
"message": f"❌ Insufficient credits. Current balance: ${organization_credits:.2f}",
|
|
245
246
|
"user": user_info
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -250,7 +251,7 @@ async def execute_v3(
|
|
|
250
251
|
if progress_callback:
|
|
251
252
|
progress_callback(message)
|
|
252
253
|
|
|
253
|
-
log_progress(f"✅ API Key validated - Credits: ${
|
|
254
|
+
log_progress(f"✅ API Key validated - Credits: ${organization_credits:.2f}")
|
|
254
255
|
log_progress(f"👤 User: {user_info.get('name', 'Unknown')}")
|
|
255
256
|
log_progress(f"🚀 Starting task: {task}")
|
|
256
257
|
log_progress(f"📱 Device: {state.device_serial}")
|
|
@@ -315,6 +316,15 @@ async def execute_v3(
|
|
|
315
316
|
# Add wrapped function to globals so code can call it directly
|
|
316
317
|
executor_globals[tool_name] = make_printing_wrapper(tool_function)
|
|
317
318
|
|
|
319
|
+
# Override the 'complete' function to be a no-op
|
|
320
|
+
# The backend already handles completion via the 'completed' flag
|
|
321
|
+
def complete_no_op(success=True, reason=""):
|
|
322
|
+
"""No-op wrapper for complete() - completion is handled by backend."""
|
|
323
|
+
print(f"complete() called: success={success}, reason='{reason}'")
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
executor_globals['complete'] = complete_no_op
|
|
327
|
+
|
|
318
328
|
log_progress(f"🔧 Loaded {len(filtered_tools)} tool functions: {list(filtered_tools.keys())}")
|
|
319
329
|
except Exception as e:
|
|
320
330
|
log_progress(f"⚠️ Warning: Could not load tool functions: {e}")
|
|
@@ -402,50 +412,8 @@ async def execute_v3(
|
|
|
402
412
|
log_progress(f"🤔 Reasoning: {reasoning}")
|
|
403
413
|
|
|
404
414
|
|
|
405
|
-
# 3.
|
|
406
|
-
|
|
407
|
-
success = step_result.get("success", False)
|
|
408
|
-
final_message = step_result.get("final_message", "Task completed")
|
|
409
|
-
|
|
410
|
-
duration = time.time() - start_time
|
|
411
|
-
|
|
412
|
-
if success:
|
|
413
|
-
log_progress(f"✅ Task completed successfully!")
|
|
414
|
-
else:
|
|
415
|
-
log_progress(f"❌ Task marked as failed")
|
|
416
|
-
|
|
417
|
-
# Finalize session on backend
|
|
418
|
-
finalize_result = await backend.finalize_session(session=session)
|
|
419
|
-
|
|
420
|
-
if success:
|
|
421
|
-
log_progress(f"✅ Task completed successfully in {len(session.steps)} steps")
|
|
422
|
-
log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
|
|
423
|
-
|
|
424
|
-
return {
|
|
425
|
-
"status": "success",
|
|
426
|
-
"steps_taken": len(session.steps),
|
|
427
|
-
"final_message": final_message,
|
|
428
|
-
"message": f"✅ Success: {final_message}",
|
|
429
|
-
"tokens": finalize_result.get("total_tokens"),
|
|
430
|
-
"cost": finalize_result.get("total_cost"),
|
|
431
|
-
"duration_seconds": duration
|
|
432
|
-
}
|
|
433
|
-
else:
|
|
434
|
-
log_progress(f"❌ Task failed: {final_message}")
|
|
435
|
-
log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
|
|
436
|
-
|
|
437
|
-
return {
|
|
438
|
-
"status": "failed",
|
|
439
|
-
"steps_taken": len(session.steps),
|
|
440
|
-
"final_message": final_message,
|
|
441
|
-
"message": f"❌ Failed: {final_message}",
|
|
442
|
-
"tokens": finalize_result.get("total_tokens"),
|
|
443
|
-
"cost": finalize_result.get("total_cost"),
|
|
444
|
-
"duration_seconds": duration
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
# 4. Execute action locally (only if task is not complete)
|
|
415
|
+
# 3. Execute action locally FIRST (if provided)
|
|
416
|
+
# NOTE: Backend should have already removed complete() from the code
|
|
449
417
|
if code and action_type == "execute_code":
|
|
450
418
|
log_progress(f"⚡ Executing action...")
|
|
451
419
|
|
|
@@ -514,6 +482,8 @@ async def execute_v3(
|
|
|
514
482
|
|
|
515
483
|
session.chat_history.append(ChatHistoryMessage(role="user", content=f"Execution Result:\n```\n{feedback}\n```"))
|
|
516
484
|
|
|
485
|
+
# Introduce a small delay to allow UI effects to settle before checking completion
|
|
486
|
+
time.sleep(1.0) # Added delay
|
|
517
487
|
|
|
518
488
|
except Exception as e:
|
|
519
489
|
error_msg = f"Error during execution: {str(e)}"
|
|
@@ -526,6 +496,48 @@ async def execute_v3(
|
|
|
526
496
|
session.chat_history.append(ChatHistoryMessage(role="user", content="No code was provided. Please provide code to execute."))
|
|
527
497
|
|
|
528
498
|
|
|
499
|
+
# 4. Check if task is complete AFTER executing action
|
|
500
|
+
if step_result.get("completed", False):
|
|
501
|
+
success = step_result.get("success", False)
|
|
502
|
+
final_message = step_result.get("final_message", "Task completed")
|
|
503
|
+
|
|
504
|
+
duration = time.time() - start_time
|
|
505
|
+
|
|
506
|
+
if success:
|
|
507
|
+
log_progress(f"✅ Task completed successfully!")
|
|
508
|
+
else:
|
|
509
|
+
log_progress(f"❌ Task marked as failed")
|
|
510
|
+
|
|
511
|
+
# Finalize session on backend
|
|
512
|
+
finalize_result = await backend.finalize_session(session=session)
|
|
513
|
+
|
|
514
|
+
if success:
|
|
515
|
+
log_progress(f"✅ Task completed successfully in {len(session.steps)} steps")
|
|
516
|
+
log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
"status": "success",
|
|
520
|
+
"steps_taken": len(session.steps),
|
|
521
|
+
"final_message": final_message,
|
|
522
|
+
"message": f"✅ Success: {final_message}",
|
|
523
|
+
"tokens": finalize_result.get("total_tokens"),
|
|
524
|
+
"cost": finalize_result.get("total_cost"),
|
|
525
|
+
"duration_seconds": duration
|
|
526
|
+
}
|
|
527
|
+
else:
|
|
528
|
+
log_progress(f"❌ Task failed: {final_message}")
|
|
529
|
+
log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
"status": "failed",
|
|
533
|
+
"steps_taken": len(session.steps),
|
|
534
|
+
"final_message": final_message,
|
|
535
|
+
"message": f"❌ Failed: {final_message}",
|
|
536
|
+
"tokens": finalize_result.get("total_tokens"),
|
|
537
|
+
"cost": finalize_result.get("total_cost"),
|
|
538
|
+
"duration_seconds": duration
|
|
539
|
+
}
|
|
540
|
+
|
|
529
541
|
|
|
530
542
|
# Max steps reached
|
|
531
543
|
log_progress(f"⚠️ Reached maximum steps ({max_steps})")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|