AiDD MCP Server
by skydeckai
import json
import platform
import subprocess
from typing import Any, Dict, List
from mcp import types
# Import platform-specific libraries if available
try:
import Quartz
QUARTZ_AVAILABLE = True
except ImportError:
QUARTZ_AVAILABLE = False
try:
import pygetwindow as gw
PYGETWINDOW_AVAILABLE = True
except ImportError:
PYGETWINDOW_AVAILABLE = False
def get_active_apps_tool():
"""Define the get_active_apps tool."""
return {
"name": "get_active_apps",
"description": "Get a list of currently active applications running on the user's system. "
"This tool retrieves applications that have visible windows or are currently running. "
"The response provides detailed information about running applications, which can help "
"with context-aware assistance, troubleshooting, or when you need to know what software "
"the user is currently working with.",
"inputSchema": {
"type": "object",
"properties": {
"with_details": {
"type": "boolean",
"description": "Whether to include additional details about each application, such as process IDs, window status, etc. Default is False."
}
},
"required": []
},
}
def _get_active_apps_macos(with_details: bool = False) -> List[Dict[str, Any]]:
"""Get a list of currently active applications on macOS."""
active_apps = []
# Attempt to use Quartz directly first, as it's more reliable
if QUARTZ_AVAILABLE:
try:
window_list = Quartz.CGWindowListCopyWindowInfo(
Quartz.kCGWindowListOptionOnScreenOnly,
Quartz.kCGNullWindowID
)
# Create a map of app names to their details
app_map = {}
for window in window_list:
owner = window.get('kCGWindowOwnerName', '')
# Skip empty app names or system components we don't want to include
if not owner or owner in ["SystemUIServer", "osascript"]:
continue
# Create new entry for this app if we haven't seen it before
if owner not in app_map:
app_map[owner] = {
"name": owner,
"has_windows": False,
"window_count": 0,
"visible_windows": [] if with_details else None
}
# Count this window
app_map[owner]["window_count"] += 1
# Check if this is a visible application window
layer = window.get('kCGWindowLayer', 999)
name = window.get('kCGWindowName', '')
# Layer 0 typically indicates a standard application window
if layer <= 0:
app_map[owner]["has_windows"] = True
# Add details about this window if detailed info was requested
if with_details and name:
app_map[owner]["visible_windows"].append({
"name": name,
"id": window.get('kCGWindowNumber', 0)
})
# Convert the map to a list
active_apps = list(app_map.values())
# If we got results from Quartz, we're done
if active_apps:
return active_apps
except Exception as e:
print(f"Error getting applications with Quartz: {str(e)}")
# Fall back to AppleScript if Quartz failed or isn't available
if not active_apps:
try:
# Modified AppleScript that tries to avoid including itself
script = '''
tell application "System Events"
set appList to {}
set allProcesses to application processes whose background only is false
repeat with proc in allProcesses
set procName to name of proc
set procVisible to (windows of proc is not {})
# Skip the scripting process itself
if procName is not "osascript" and procName is not "System Events" then
set end of appList to {name:procName, has_windows:procVisible}
end if
end repeat
return appList
end tell
'''
result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
if result.returncode == 0:
# Parse the output from AppleScript
output = result.stdout.strip()
if output:
# AppleScript returns a list of records, we need to parse it
lines = output.split(", {")
for i, line in enumerate(lines):
if i == 0:
line = line.replace("{", "")
if i == len(lines) - 1:
line = line.replace("}", "")
if "name:" in line and "has_windows:" in line:
parts = line.split(", ")
app_info = {}
for part in parts:
if "name:" in part:
app_info["name"] = part.replace("name:", "").strip()
elif "has_windows:" in part:
app_info["has_windows"] = part.replace("has_windows:", "").strip().lower() == "true"
if app_info:
active_apps.append(app_info)
except Exception as e:
print(f"Error getting apps with AppleScript: {str(e)}")
# Add window details if requested and if we got results from AppleScript
if active_apps and with_details and QUARTZ_AVAILABLE:
try:
window_list = Quartz.CGWindowListCopyWindowInfo(
Quartz.kCGWindowListOptionOnScreenOnly,
Quartz.kCGNullWindowID
)
# Create a map of app names to window details
app_details = {}
for window in window_list:
owner = window.get('kCGWindowOwnerName', '')
if not owner:
continue
if owner not in app_details:
app_details[owner] = {
"window_count": 0,
"windows": []
}
app_details[owner]["window_count"] += 1
# Add window details if this is a visible window
layer = window.get('kCGWindowLayer', 999)
name = window.get('kCGWindowName', '')
if layer <= 0 and name:
app_details[owner]["windows"].append({
"name": name,
"id": window.get('kCGWindowNumber', 0)
})
# Enhance the active_apps list with these details
for app in active_apps:
app_name = app["name"]
if app_name in app_details:
app["window_count"] = app_details[app_name]["window_count"]
app["visible_windows"] = app_details[app_name]["windows"]
except Exception as e:
print(f"Error getting window details with Quartz: {str(e)}")
return active_apps
def _get_active_apps_windows(with_details: bool = False) -> List[Dict[str, Any]]:
"""Get a list of currently active applications on Windows."""
active_apps = []
# Basic list without details
if not with_details:
try:
# Use a PowerShell command to get running applications
script = '''
Get-Process | Where-Object {$_.MainWindowTitle -ne ""} |
Select-Object ProcessName, MainWindowTitle |
ConvertTo-Json
'''
cmd = ["powershell", "-Command", script]
process = subprocess.run(cmd, capture_output=True, text=True)
if process.returncode == 0:
try:
apps_data = json.loads(process.stdout)
# Handle single item (not in a list)
if isinstance(apps_data, dict):
apps_data = [apps_data]
for app in apps_data:
active_apps.append({
"name": app.get("ProcessName", ""),
"has_windows": True,
"window_title": app.get("MainWindowTitle", "")
})
except json.JSONDecodeError:
print("Failed to parse JSON from PowerShell output")
except Exception as e:
print(f"Error getting basic app list on Windows: {str(e)}")
# More detailed list with PyGetWindow if available
elif PYGETWINDOW_AVAILABLE:
try:
# Get the list of windows
all_windows = gw.getAllWindows()
# Group by application (approximate, since we only have window titles)
app_windows = {}
for window in all_windows:
if not window.title:
continue
# Try to extract application name from window title
# This is an approximation and might not be accurate for all applications
title_parts = window.title.split(' - ')
app_name = title_parts[-1] if len(title_parts) > 1 else window.title
if app_name not in app_windows:
app_windows[app_name] = {
"name": app_name,
"has_windows": True,
"window_count": 0,
"visible_windows": []
}
app_windows[app_name]["window_count"] += 1
app_windows[app_name]["visible_windows"].append({
"name": window.title,
"width": window.width,
"height": window.height
})
active_apps = list(app_windows.values())
except Exception as e:
print(f"Error getting detailed app list with PyGetWindow: {str(e)}")
# Fallback to a basic PowerShell approach
if not active_apps:
try:
script = '''
Get-Process | Where-Object {$_.MainWindowHandle -ne 0} |
Select-Object ProcessName | ConvertTo-Json
'''
cmd = ["powershell", "-Command", script]
process = subprocess.run(cmd, capture_output=True, text=True)
if process.returncode == 0:
try:
apps_data = json.loads(process.stdout)
# Handle single item (not in a list)
if isinstance(apps_data, dict):
apps_data = [apps_data]
for app in apps_data:
active_apps.append({
"name": app.get("ProcessName", ""),
"has_windows": True
})
except json.JSONDecodeError:
print("Failed to parse JSON from PowerShell output")
except Exception as e:
print(f"Error getting fallback app list on Windows: {str(e)}")
return active_apps
def _get_active_apps_linux(with_details: bool = False) -> List[Dict[str, Any]]:
"""Get a list of currently active applications on Linux."""
active_apps = []
# Try using wmctrl if available
try:
# Check if wmctrl is installed
check_process = subprocess.run(["which", "wmctrl"], capture_output=True)
if check_process.returncode == 0:
# Get window list with wmctrl
wmctrl_process = subprocess.run(["wmctrl", "-l"], capture_output=True, text=True)
if wmctrl_process.returncode == 0:
window_data = wmctrl_process.stdout.strip().split('\n')
# Process each window line
app_windows = {}
for line in window_data:
if not line:
continue
parts = line.split(None, 3) # Split by whitespace, max 3 splits
if len(parts) >= 4:
window_id, desktop, host, title = parts
# Try to determine app name from window title
app_name = title.split(' - ')[-1] if ' - ' in title else title
if app_name not in app_windows:
app_windows[app_name] = {
"name": app_name,
"has_windows": True,
"window_count": 0,
"visible_windows": []
}
app_windows[app_name]["window_count"] += 1
if with_details:
app_windows[app_name]["visible_windows"].append({
"name": title,
"id": window_id,
"desktop": desktop
})
active_apps = list(app_windows.values())
except Exception as e:
print(f"Error getting apps with wmctrl: {str(e)}")
# If wmctrl failed or isn't available, try using ps
if not active_apps:
try:
# List GUI applications
cmd = ["ps", "-e", "-o", "comm="]
process = subprocess.run(cmd, capture_output=True, text=True)
if process.returncode == 0:
all_processes = process.stdout.strip().split('\n')
# Filter for likely GUI applications (very basic heuristic)
gui_indicators = ["-bin", "x11", "gtk", "qt", "wayland", "gnome", "kde"]
for proc in all_processes:
proc = proc.strip()
if not proc:
continue
# Skip system processes that typically don't have UIs
if proc.startswith(("ps", "bash", "sh", "zsh", "systemd", "login", "dbus")):
continue
# Include if it looks like a GUI app
if any(indicator in proc.lower() for indicator in gui_indicators) or "/" not in proc:
active_apps.append({
"name": proc,
"has_windows": True # Assuming these have windows, though we can't be sure
})
except Exception as e:
print(f"Error getting apps with ps: {str(e)}")
return active_apps
def get_active_apps(with_details: bool = False) -> Dict[str, Any]:
"""
Get a list of currently active applications on the user's system.
Args:
with_details: Whether to include additional details about each application
Returns:
Dictionary with platform, success status, and list of active applications
"""
system_name = platform.system().lower()
# Get active apps based on platform
if system_name == "darwin" or system_name == "macos":
active_apps = _get_active_apps_macos(with_details)
elif system_name == "windows":
active_apps = _get_active_apps_windows(with_details)
elif system_name == "linux":
active_apps = _get_active_apps_linux(with_details)
else:
return {
"success": False,
"platform": system_name,
"error": f"Unsupported platform: {system_name}. This tool currently supports macOS, Windows, and Linux.",
"apps": []
}
# If no apps were found, provide a descriptive error message
if not active_apps:
error_message = "No active applications could be detected. "
if system_name == "darwin":
error_message += ("This is most likely due to missing screen recording permissions. "
"Please go to System Settings > Privacy & Security > Screen Recording "
"and ensure that your terminal or IDE application has permission to record the screen.")
elif system_name == "windows":
error_message += "This might be due to insufficient permissions or no applications with visible windows."
elif system_name == "linux":
error_message += "This might be due to wmctrl not being installed or no applications with visible windows."
return {
"success": False,
"platform": system_name,
"error": error_message,
"apps": []
}
# Sort by name
active_apps.sort(key=lambda app: app.get("name", "").lower())
return {
"success": True,
"platform": system_name,
"app_count": len(active_apps),
"apps": active_apps
}
async def handle_get_active_apps(arguments: dict) -> List[types.TextContent]:
"""Handle getting active applications."""
with_details = arguments.get("with_details", False)
result = get_active_apps(with_details)
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]