Skip to main content
Glama
wehnsdaefflae

Interactive Automation MCP Server

interaction_logger.py10.4 kB
#!/usr/bin/env python3 """ Interaction Logger for debugging MCP automation sessions Captures detailed interaction flows with timestamps and structured data """ import json import logging import time from datetime import datetime from pathlib import Path from typing import Any from .models import LogEventData class InteractionLogger: """Structured logger for debugging interactive automation sessions""" def __init__(self, session_id: str, log_dir: str = "logs/interactions"): self.session_id = session_id self.log_dir = Path(log_dir) self.log_dir.mkdir(parents=True, exist_ok=True) # Create session-specific log file timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") self.log_file = self.log_dir / f"session_{session_id}_{timestamp}.json" # Initialize log structure self.events: list[LogEventData] = [] self.session_start_time = time.time() # Also create a human-readable log self.readable_log = self.log_dir / f"session_{session_id}_{timestamp}.txt" # Log session start self.log_event("session_start", {"session_id": session_id}) def log_event(self, event_type: str, data: dict[str, Any] | None = None) -> None: """Log a structured event with timestamp""" timestamp = time.time() relative_time = timestamp - self.session_start_time event = LogEventData( event_type=event_type, timestamp=timestamp, relative_time=round(relative_time, 3), data=data or {}, ) self.events.append(event) # Write to JSON file immediately (for real-time debugging) self._write_json() # Write human-readable version self._write_readable(event) def log_screen_content( self, content: str, description: str = "screen_capture" ) -> None: """Log current screen content""" # Clean up ANSI escape codes for readable version clean_content = self._clean_ansi(content) self.log_event( "screen_content", { "description": description, "raw_content": content, "clean_content": clean_content, "content_length": len(content), }, ) def log_input_sent(self, input_text: str, input_type: str = "user_input") -> None: """Log input sent to session""" self.log_event( "input_sent", { "input_text": input_text, "input_type": input_type, "input_length": len(input_text), "input_repr": repr(input_text), # Shows escape sequences }, ) def log_wait_start(self, pattern: str, timeout: int) -> None: """Log start of expect/wait operation""" self.log_event( "wait_start", {"pattern": pattern, "timeout": timeout, "action": "waiting_for_pattern"}, ) def log_wait_result( self, success: bool, matched_text: str | None = None, timeout_occurred: bool = False, ) -> None: """Log result of expect/wait operation""" self.log_event( "wait_result", { "success": success, "matched_text": matched_text, "timeout_occurred": timeout_occurred, }, ) def log_command_execution( self, command: str, working_dir: str | None = None ) -> None: """Log command execution""" self.log_event( "command_execution", {"command": command, "working_directory": working_dir} ) def log_session_state( self, state: str, details: dict[str, Any] | None = None ) -> None: """Log session state changes""" self.log_event("session_state", {"state": state, "details": details or {}}) def log_automation_step( self, step_number: int, step_type: str, step_data: dict[str, Any] ) -> None: """Log automation step execution""" self.log_event( "automation_step", { "step_number": step_number, "step_type": step_type, "step_data": step_data, }, ) def log_error( self, error_type: str, error_message: str, stack_trace: str | None = None ) -> None: """Log errors and exceptions""" self.log_event( "error", { "error_type": error_type, "error_message": error_message, "stack_trace": stack_trace, }, ) def close_session( self, exit_code: int | None = None, final_output: str | None = None ) -> None: """Close the logging session""" self.log_event( "session_end", { "exit_code": exit_code, "total_duration": time.time() - self.session_start_time, "total_events": len(self.events), }, ) if final_output: self.log_screen_content(final_output, "final_output") # Write final summary self._write_summary() def _write_json(self) -> None: """Write events to JSON file""" try: with open(self.log_file, "w") as f: json.dump( { "session_id": self.session_id, "session_start_time": self.session_start_time, "events": [event.to_dict() for event in self.events], }, f, indent=2, ) except Exception as e: logging.error(f"Failed to write interaction log: {e}") def _write_readable(self, event: LogEventData) -> None: """Write human-readable log entry""" try: with open(self.readable_log, "a") as f: self._write_header(f, event) self._write_event_data(f, event) f.write("\n") f.flush() except Exception as e: logging.error(f"Failed to write readable log: {e}") def _write_header(self, f: Any, event: LogEventData) -> None: """Write log entry header""" timestamp = datetime.fromtimestamp(event.timestamp).isoformat() rel_time = event.relative_time event_type = event.event_type f.write(f"\n[{timestamp}] (+{rel_time}s) {event_type.upper()}\n") f.write("-" * 60 + "\n") def _write_event_data(self, f: Any, event: LogEventData) -> None: """Write event-specific data""" data = event.data event_type = event.event_type if event_type == "screen_content": self._write_screen_content(f, data) elif event_type == "input_sent": self._write_input_sent(f, data) elif event_type == "wait_start": self._write_wait_start(f, data) elif event_type == "wait_result": self._write_wait_result(f, data) else: self._write_generic_data(f, data) def _write_screen_content(self, f: Any, data: dict[str, Any]) -> None: """Write screen content data""" f.write(f"Description: {data.get('description', 'N/A')}\n") f.write(f"Content Length: {data.get('content_length', 0)} chars\n") f.write("Screen Content:\n") f.write("=" * 40 + "\n") f.write(data.get("clean_content", "") + "\n") f.write("=" * 40 + "\n") def _write_input_sent(self, f: Any, data: dict[str, Any]) -> None: """Write input sent data""" f.write(f"Input Type: {data.get('input_type', 'unknown')}\n") f.write(f"Input Text: {data.get('input_text', '')}\n") f.write(f"Input Repr: {data.get('input_repr', '')}\n") def _write_wait_start(self, f: Any, data: dict[str, Any]) -> None: """Write wait start data""" f.write(f"Pattern: {data.get('pattern', '')}\n") f.write(f"Timeout: {data.get('timeout', 0)}s\n") def _write_wait_result(self, f: Any, data: dict[str, Any]) -> None: """Write wait result data""" f.write(f"Success: {data.get('success', False)}\n") f.write(f"Matched: {data.get('matched_text', 'N/A')}\n") f.write(f"Timeout: {data.get('timeout_occurred', False)}\n") def _write_generic_data(self, f: Any, data: dict[str, Any]) -> None: """Write generic data dump""" for key, value in data.items(): f.write(f"{key}: {value}\n") def _write_summary(self) -> None: """Write session summary""" try: summary_file = self.log_dir / f"session_{self.session_id}_summary.txt" with open(summary_file, "w") as f: f.write("INTERACTION LOG SUMMARY\n") f.write(f"Session ID: {self.session_id}\n") f.write(f"Total Events: {len(self.events)}\n") f.write(f"Duration: {time.time() - self.session_start_time:.2f}s\n") f.write("Log Files:\n") f.write(f" - JSON: {self.log_file}\n") f.write(f" - Readable: {self.readable_log}\n") f.write("\nEvent Types:\n") # Count event types event_counts: dict[str, int] = {} for event in self.events: event_type = event.event_type event_counts[event_type] = event_counts.get(event_type, 0) + 1 for event_type, count in sorted(event_counts.items()): f.write(f" - {event_type}: {count}\n") except Exception as e: logging.error(f"Failed to write summary: {e}") @staticmethod def _clean_ansi(text: str) -> str: """Remove ANSI escape codes for readable output""" import re ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") return ansi_escape.sub("", text) def get_log_files(self) -> dict[str, str]: """Get paths to all log files for this session""" return { "json": str(self.log_file), "readable": str(self.readable_log), "summary": str(self.log_dir / f"session_{self.session_id}_summary.txt"), }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wehnsdaefflae/MCPAutomationServer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server