Merge pull request #493 from YeIIcw/docs/mcp-server-locally

Add Local Desktop Mode for MCP Server with updated docs
This commit is contained in:
Adam
2025-11-17 14:56:10 +00:00
committed by GitHub
11 changed files with 914 additions and 15 deletions

View File

@@ -1287,7 +1287,15 @@ class MacOSAutomationHandler(BaseAutomationHandler):
if not isinstance(screenshot, Image.Image):
return {"success": False, "error": "Failed to capture screenshot"}
# Resize image to reduce size (max width 1920, maintain aspect ratio)
max_width = 1920
if screenshot.width > max_width:
ratio = max_width / screenshot.width
new_height = int(screenshot.height * ratio)
screenshot = screenshot.resize((max_width, new_height), Image.Resampling.LANCZOS)
buffered = BytesIO()
# Use PNG format with optimization to reduce file size
screenshot.save(buffered, format="PNG", optimize=True)
buffered.seek(0)
image_data = base64.b64encode(buffered.getvalue()).decode()

View File

@@ -0,0 +1,63 @@
#!/bin/bash
# Quick Test Commands for MCP Server Local Desktop Option
# Run these commands to test the implementation
set -e # Exit on error
echo "======================================================================"
echo "Testing MCP Server Local Desktop Option"
echo "======================================================================"
echo ""
# Change to repo root
cd "$(dirname "$0")/.."
# Test 1: Quick Logic Test (No setup required)
echo "Test 1: Quick Logic Test (No setup required)"
echo "----------------------------------------------------------------------"
python tests/quick_test_local_option.py
echo ""
# Test 2: Automated Tests (Requires pytest and packages)
echo "Test 2: Automated Tests (Requires pytest and packages installed)"
echo "----------------------------------------------------------------------"
if command -v pytest &> /dev/null; then
echo "Running pytest..."
pytest tests/test_mcp_server_local_option.py -v || echo "Note: Some tests may require full setup"
else
echo "⚠️ pytest not found. Install with: pip install pytest"
fi
echo ""
# Test 3: Existing MCP server tests
echo "Test 3: Existing MCP Server Tests"
echo "----------------------------------------------------------------------"
if command -v pytest &> /dev/null; then
echo "Running existing session management tests..."
pytest tests/test_mcp_server_session_management.py -v || echo "Note: Some tests may fail if dependencies are missing"
else
echo "⚠️ pytest not found. Install with: pip install pytest"
fi
echo ""
# Summary
echo "======================================================================"
echo "Test Summary"
echo "======================================================================"
echo "✅ Quick logic test completed"
echo ""
echo "Next steps for comprehensive testing:"
echo "1. Install dependencies:"
echo " pip install -e libs/python/core"
echo " pip install -e libs/python/computer"
echo " pip install -e libs/python/agent"
echo " pip install -e libs/python/mcp-server"
echo " pip install -e libs/python/computer-server"
echo ""
echo "2. For manual end-to-end testing, see:"
echo " tests/MANUAL_TEST_LOCAL_OPTION.md"
echo ""
echo "3. For detailed testing info, see:"
echo " tests/TESTING_SUMMARY.md"
echo ""

View File

@@ -10,6 +10,7 @@ This module provides:
import asyncio
import logging
import os
import time
import uuid
import weakref
@@ -57,7 +58,14 @@ class ComputerPool:
logger.debug("Creating new computer instance")
from computer import Computer
computer = Computer(verbosity=logging.INFO)
# Check if we should use host computer server
use_host = os.getenv("CUA_USE_HOST_COMPUTER_SERVER", "false").lower() in (
"true",
"1",
"yes",
)
computer = Computer(verbosity=logging.INFO, use_host_computer_server=use_host)
await computer.run()
self._in_use.add(computer)
return computer

View File

@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Quick test to verify the local desktop option logic without full setup.
This script tests the environment variable parsing and logic flow
without requiring VMs, computer-server, or MCP clients to be running.
"""
import os
import sys
def test_env_var_parsing():
"""Test that environment variable is parsed correctly."""
print("Testing CUA_USE_HOST_COMPUTER_SERVER environment variable parsing...")
print("-" * 60)
test_cases = [
# (env_value, expected_result, description)
("true", True, "lowercase 'true'"),
("True", True, "capitalized 'True'"),
("TRUE", True, "uppercase 'TRUE'"),
("1", True, "numeric '1'"),
("yes", True, "lowercase 'yes'"),
("Yes", True, "capitalized 'Yes'"),
("false", False, "lowercase 'false'"),
("False", False, "capitalized 'False'"),
("FALSE", False, "uppercase 'FALSE'"),
("0", False, "numeric '0'"),
("no", False, "lowercase 'no'"),
("", False, "empty string"),
("random", False, "random value"),
(None, False, "not set (None)"),
]
passed = 0
failed = 0
for env_value, expected, description in test_cases:
# Simulate the logic from session_manager.py line 59
if env_value is None:
actual = os.getenv("CUA_USE_HOST_COMPUTER_SERVER", "false").lower() in (
"true",
"1",
"yes",
)
else:
os.environ["CUA_USE_HOST_COMPUTER_SERVER"] = env_value
actual = os.getenv("CUA_USE_HOST_COMPUTER_SERVER", "false").lower() in (
"true",
"1",
"yes",
)
status = "✓ PASS" if actual == expected else "✗ FAIL"
if actual == expected:
passed += 1
else:
failed += 1
print(
f"{status} | Value: {env_value!r:15} | Expected: {expected!s:5} | Got: {actual!s:5} | {description}"
)
# Clean up
os.environ.pop("CUA_USE_HOST_COMPUTER_SERVER", None)
print("-" * 60)
print(f"Results: {passed} passed, {failed} failed")
return failed == 0
def test_session_manager_logic():
"""Test the logic flow in session_manager.py without actual Computer creation."""
print("\nTesting session_manager.py logic flow...")
print("-" * 60)
# Read the actual session_manager.py to verify the logic
import pathlib
session_manager_path = (
pathlib.Path(__file__).parent.parent
/ "libs"
/ "python"
/ "mcp-server"
/ "mcp_server"
/ "session_manager.py"
)
if not session_manager_path.exists():
print(f"✗ FAIL | session_manager.py not found at {session_manager_path}")
return False
content = session_manager_path.read_text()
# Check for the key logic
checks = [
('os.getenv("CUA_USE_HOST_COMPUTER_SERVER"', "Environment variable check present"),
("use_host_computer_server=use_host", "use_host_computer_server parameter passed"),
("Computer(", "Computer instantiation present"),
]
all_checks_passed = True
for check_str, description in checks:
if check_str in content:
print(f"✓ PASS | {description}")
else:
print(f"✗ FAIL | {description} - not found")
all_checks_passed = False
print("-" * 60)
return all_checks_passed
def test_documentation_consistency():
"""Verify documentation mentions the new feature."""
print("\nTesting documentation consistency...")
print("-" * 60)
import pathlib
docs_to_check = [
("configuration.mdx", "CUA_USE_HOST_COMPUTER_SERVER"),
("usage.mdx", "Targeting Your Local Desktop"),
]
docs_path = (
pathlib.Path(__file__).parent.parent
/ "docs"
/ "content"
/ "docs"
/ "libraries"
/ "mcp-server"
)
all_docs_ok = True
for doc_file, expected_content in docs_to_check:
doc_path = docs_path / doc_file
if not doc_path.exists():
print(f"✗ FAIL | {doc_file} not found")
all_docs_ok = False
continue
content = doc_path.read_text()
if expected_content in content:
print(f"✓ PASS | {doc_file} contains '{expected_content}'")
else:
print(f"✗ FAIL | {doc_file} missing '{expected_content}'")
all_docs_ok = False
print("-" * 60)
return all_docs_ok
def print_usage_examples():
"""Print usage examples for both modes."""
print("\n" + "=" * 60)
print("USAGE EXAMPLES")
print("=" * 60)
print("\n1. DEFAULT MODE (VM):")
print("-" * 60)
print(
"""
{
"mcpServers": {
"cua-agent": {
"command": "/bin/bash",
"args": ["~/.cua/start_mcp_server.sh"],
"env": {
"CUA_MODEL_NAME": "anthropic/claude-3-5-sonnet-20241022"
}
}
}
}
Note: CUA_USE_HOST_COMPUTER_SERVER is not set, so VM mode is used (safe).
"""
)
print("\n2. LOCAL DESKTOP MODE:")
print("-" * 60)
print(
"""
Step 1: Start computer-server locally:
python -m computer_server
Step 2: Configure MCP client:
{
"mcpServers": {
"cua-agent": {
"command": "/bin/bash",
"args": ["~/.cua/start_mcp_server.sh"],
"env": {
"CUA_MODEL_NAME": "anthropic/claude-3-5-sonnet-20241022",
"CUA_USE_HOST_COMPUTER_SERVER": "true"
}
}
}
}
⚠️ WARNING: AI will have direct access to your desktop!
"""
)
def main():
"""Run all quick tests."""
print("=" * 60)
print("QUICK TEST: MCP Server Local Desktop Option")
print("=" * 60)
print()
results = []
# Run tests
results.append(("Environment Variable Parsing", test_env_var_parsing()))
results.append(("Session Manager Logic", test_session_manager_logic()))
results.append(("Documentation Consistency", test_documentation_consistency()))
# Print summary
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
for test_name, passed in results:
status = "✓ PASSED" if passed else "✗ FAILED"
print(f"{status} | {test_name}")
all_passed = all(result for _, result in results)
if all_passed:
print("\n🎉 All quick tests passed!")
print_usage_examples()
print("\nNext steps:")
print("1. Run full automated tests: pytest tests/test_mcp_server_local_option.py")
print("2. Follow manual testing guide: tests/MANUAL_TEST_LOCAL_OPTION.md")
return 0
else:
print("\n❌ Some tests failed. Please review the output above.")
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,138 @@
"""
Test script to verify MCP Server local desktop option works correctly.
This test verifies:
1. Default behavior: Computer uses VM
2. New behavior: Computer uses host when CUA_USE_HOST_COMPUTER_SERVER=true
"""
import asyncio
import os
import sys
from pathlib import Path
# Add the mcp-server module to path
mcp_server_path = Path(__file__).parent.parent / "libs" / "python" / "mcp-server"
sys.path.insert(0, str(mcp_server_path.parent.parent.parent / "libs" / "python"))
import pytest
@pytest.mark.asyncio
async def test_default_vm_mode():
"""Test that the default mode uses VM (not host computer server)."""
# Ensure environment variable is not set or is false
os.environ.pop("CUA_USE_HOST_COMPUTER_SERVER", None)
from mcp_server.session_manager import ComputerPool
pool = ComputerPool(max_size=1)
try:
computer = await pool.acquire()
# Verify the computer was initialized
assert computer is not None
# Check that use_host_computer_server was set to False (default)
# This should start a VM
print("✓ Default mode: Computer initialized (VM mode expected)")
await pool.release(computer)
finally:
await pool.shutdown()
@pytest.mark.asyncio
async def test_local_desktop_mode():
"""Test that setting CUA_USE_HOST_COMPUTER_SERVER=true uses host."""
# Set environment variable to true
os.environ["CUA_USE_HOST_COMPUTER_SERVER"] = "true"
# Need to reload module to pick up new env var
import importlib
import mcp_server.session_manager
from mcp_server.session_manager import ComputerPool
importlib.reload(mcp_server.session_manager)
pool = mcp_server.session_manager.ComputerPool(max_size=1)
try:
computer = await pool.acquire()
# Verify the computer was initialized
assert computer is not None
# Check that use_host_computer_server was set to True
print("✓ Local mode: Computer initialized (host mode expected)")
await pool.release(computer)
finally:
await pool.shutdown()
# Clean up env var
os.environ.pop("CUA_USE_HOST_COMPUTER_SERVER", None)
@pytest.mark.asyncio
async def test_env_var_parsing():
"""Test that various values of CUA_USE_HOST_COMPUTER_SERVER are parsed correctly."""
test_cases = [
("true", True),
("True", True),
("TRUE", True),
("1", True),
("yes", True),
("false", False),
("False", False),
("FALSE", False),
("0", False),
("no", False),
("", False),
("random", False),
]
for value, expected in test_cases:
os.environ["CUA_USE_HOST_COMPUTER_SERVER"] = value
# Check parsing logic
use_host = os.getenv("CUA_USE_HOST_COMPUTER_SERVER", "false").lower() in (
"true",
"1",
"yes",
)
assert (
use_host == expected
), f"Failed for value '{value}': expected {expected}, got {use_host}"
print(f"✓ Env var '{value}' correctly parsed as {expected}")
os.environ.pop("CUA_USE_HOST_COMPUTER_SERVER", None)
if __name__ == "__main__":
print("Testing MCP Server Local Desktop Option")
print("=" * 60)
print("\n1. Testing environment variable parsing...")
asyncio.run(test_env_var_parsing())
print("\n2. Testing default VM mode...")
try:
asyncio.run(test_default_vm_mode())
except Exception as e:
print(f"✗ Default VM mode test failed: {e}")
print("Note: This may require lume/VM setup to fully test")
print("\n3. Testing local desktop mode...")
try:
asyncio.run(test_local_desktop_mode())
except Exception as e:
print(f"✗ Local desktop mode test failed: {e}")
print("Note: This may require computer-server to be running locally")
print("\n" + "=" * 60)
print("Tests completed!")