33import base64
44import os
55import sys
6+ import subprocess
7+ import time
68
79import mcp .types as types
810# Import vnc_client from the current directory
@@ -476,6 +478,181 @@ def handle_remote_macos_mouse_move(arguments: dict[str, Any]) -> list[types.Text
476478Target dimensions: { target_width } x{ target_height }
477479Scale factors: { scale_factors ['x' ]:.4f} x, { scale_factors ['y' ]:.4f} y"""
478480 )]
481+ finally :
482+ # Close VNC connection
483+ vnc .close ()
484+
485+
486+ def handle_remote_macos_open_application (arguments : dict [str , Any ]) -> List [types .TextContent ]:
487+ """
488+ Opens or activates an application on the remote MacOS machine using VNC.
489+
490+ Args:
491+ arguments: Dictionary containing:
492+ - identifier: App name, path, or bundle ID
493+
494+ Returns:
495+ List containing a TextContent with the result
496+ """
497+ # Use environment variables
498+ host = MACOS_HOST
499+ port = MACOS_PORT
500+ password = MACOS_PASSWORD
501+ username = MACOS_USERNAME
502+ encryption = VNC_ENCRYPTION
503+
504+ identifier = arguments .get ("identifier" )
505+ if not identifier :
506+ raise ValueError ("identifier is required" )
507+
508+ start_time = time .time ()
509+
510+ # Initialize VNC client
511+ vnc = VNCClient (host = host , port = port , password = password , username = username , encryption = encryption )
512+
513+ # Connect to remote MacOs machine
514+ success , error_message = vnc .connect ()
515+ if not success :
516+ error_msg = f"Failed to connect to remote MacOs machine at { host } :{ port } . { error_message } "
517+ return [types .TextContent (type = "text" , text = error_msg )]
518+
519+ try :
520+ # Send Command+Space to open Spotlight
521+ cmd_key = 0xffeb # Command key
522+ space_key = 0x20 # Space key
523+
524+ # Press Command+Space
525+ vnc .send_key_event (cmd_key , True )
526+ vnc .send_key_event (space_key , True )
527+
528+ # Release Command+Space
529+ vnc .send_key_event (space_key , False )
530+ vnc .send_key_event (cmd_key , False )
531+
532+ # Small delay to let Spotlight open
533+ time .sleep (0.5 )
534+
535+ # Type the application name
536+ vnc .send_text (identifier )
537+
538+ # Small delay to let Spotlight find the app
539+ time .sleep (0.5 )
540+
541+ # Press Enter to launch
542+ enter_key = 0xff0d
543+ vnc .send_key_event (enter_key , True )
544+ vnc .send_key_event (enter_key , False )
545+
546+ end_time = time .time ()
547+ processing_time = round (end_time - start_time , 3 )
548+
549+ return [types .TextContent (
550+ type = "text" ,
551+ text = f"Launched application: { identifier } \n Processing time: { processing_time } s"
552+ )]
553+
554+ finally :
555+ # Close VNC connection
556+ vnc .close ()
557+
558+
559+ def handle_remote_macos_mouse_drag (arguments : dict [str , Any ]) -> list [types .TextContent | types .ImageContent | types .EmbeddedResource ]:
560+ """Perform a mouse drag operation on a remote MacOs machine."""
561+ # Use environment variables
562+ host = MACOS_HOST
563+ port = MACOS_PORT
564+ password = MACOS_PASSWORD
565+ username = MACOS_USERNAME
566+ encryption = VNC_ENCRYPTION
567+
568+ # Get required parameters from arguments
569+ start_x = arguments .get ("start_x" )
570+ start_y = arguments .get ("start_y" )
571+ end_x = arguments .get ("end_x" )
572+ end_y = arguments .get ("end_y" )
573+ source_width = int (arguments .get ("source_width" , 1366 ))
574+ source_height = int (arguments .get ("source_height" , 768 ))
575+ button = int (arguments .get ("button" , 1 ))
576+ steps = int (arguments .get ("steps" , 10 ))
577+ delay_ms = int (arguments .get ("delay_ms" , 10 ))
578+
579+ # Validate required parameters
580+ if any (x is None for x in [start_x , start_y , end_x , end_y ]):
581+ raise ValueError ("start_x, start_y, end_x, and end_y coordinates are required" )
582+
583+ # Ensure source dimensions are positive
584+ if source_width <= 0 or source_height <= 0 :
585+ raise ValueError ("Source dimensions must be positive values" )
586+
587+ # Initialize VNC client
588+ vnc = VNCClient (host = host , port = port , password = password , username = username , encryption = encryption )
589+
590+ # Connect to remote MacOs machine
591+ success , error_message = vnc .connect ()
592+ if not success :
593+ error_msg = f"Failed to connect to remote MacOs machine at { host } :{ port } . { error_message } "
594+ return [types .TextContent (type = "text" , text = error_msg )]
595+
596+ try :
597+ # Get target screen dimensions
598+ target_width = vnc .width
599+ target_height = vnc .height
600+
601+ # Scale coordinates
602+ scaled_start_x = int ((start_x / source_width ) * target_width )
603+ scaled_start_y = int ((start_y / source_height ) * target_height )
604+ scaled_end_x = int ((end_x / source_width ) * target_width )
605+ scaled_end_y = int ((end_y / source_height ) * target_height )
606+
607+ # Ensure coordinates are within the screen bounds
608+ scaled_start_x = max (0 , min (scaled_start_x , target_width - 1 ))
609+ scaled_start_y = max (0 , min (scaled_start_y , target_height - 1 ))
610+ scaled_end_x = max (0 , min (scaled_end_x , target_width - 1 ))
611+ scaled_end_y = max (0 , min (scaled_end_y , target_height - 1 ))
612+
613+ # Calculate step sizes
614+ dx = (scaled_end_x - scaled_start_x ) / steps
615+ dy = (scaled_end_y - scaled_start_y ) / steps
616+
617+ # Move to start position
618+ if not vnc .send_pointer_event (scaled_start_x , scaled_start_y , 0 ):
619+ return [types .TextContent (type = "text" , text = "Failed to move to start position" )]
620+
621+ # Press button
622+ button_mask = 1 << (button - 1 )
623+ if not vnc .send_pointer_event (scaled_start_x , scaled_start_y , button_mask ):
624+ return [types .TextContent (type = "text" , text = "Failed to press mouse button" )]
625+
626+ # Perform drag
627+ for step in range (1 , steps + 1 ):
628+ current_x = int (scaled_start_x + dx * step )
629+ current_y = int (scaled_start_y + dy * step )
630+ if not vnc .send_pointer_event (current_x , current_y , button_mask ):
631+ return [types .TextContent (type = "text" , text = f"Failed during drag at step { step } " )]
632+ time .sleep (delay_ms / 1000.0 ) # Convert ms to seconds
633+
634+ # Release button at final position
635+ if not vnc .send_pointer_event (scaled_end_x , scaled_end_y , 0 ):
636+ return [types .TextContent (type = "text" , text = "Failed to release mouse button" )]
637+
638+ # Prepare the response with useful details
639+ scale_factors = {
640+ "x" : target_width / source_width ,
641+ "y" : target_height / source_height
642+ }
643+
644+ return [types .TextContent (
645+ type = "text" ,
646+ text = f"""Mouse drag (button { button } ) completed:
647+ From source ({ start_x } , { start_y } ) to ({ end_x } , { end_y } )
648+ From target ({ scaled_start_x } , { scaled_start_y } ) to ({ scaled_end_x } , { scaled_end_y } )
649+ Source dimensions: { source_width } x{ source_height }
650+ Target dimensions: { target_width } x{ target_height }
651+ Scale factors: { scale_factors ['x' ]:.4f} x, { scale_factors ['y' ]:.4f} y
652+ Steps: { steps }
653+ Delay: { delay_ms } ms"""
654+ )]
655+
479656 finally :
480657 # Close VNC connection
481658 vnc .close ()
0 commit comments