main.py•10.4 kB
"""
Command-line interface for the Python Debugging MCP Tool.
Provides commands to interact with debug sessions, set breakpoints,
and inspect local variables.
"""
import json
import sys
from pathlib import Path
from typing import Optional
import typer
from rich.console import Console
from rich.table import Table
app = typer.Typer(
name="mcp-debug",
help="Python Debugging MCP Tool - Debug Python code via breakpoints",
)
console = Console()
@app.command("start-session")
def start_session(
entry: str = typer.Argument(..., help="Project-relative path to Python script"),
args: Optional[list[str]] = typer.Option(
None, "--arg", help="Command-line arguments for the script"
),
env: Optional[list[str]] = typer.Option(
None, "--env", help="Environment variables (KEY=VALUE format)"
),
python_path: Optional[str] = typer.Option(
None, "--python-path", help="Path to Python interpreter"
),
output_format: str = typer.Option(
"json", "--format", "-f", help="Output format: json or table"
),
) -> None:
"""
Start a new debug session.
Example:
mcp-debug start-session src/main.py
mcp-debug start-session tests/test.py --arg --verbose --env DEBUG=true
"""
from mcp_debug_tool.schemas import StartSessionRequest
from mcp_debug_tool.sessions import SessionManager
# Parse environment variables
env_dict = {}
if env:
for env_var in env:
if "=" in env_var:
key, value = env_var.split("=", 1)
env_dict[key] = value
else:
console.print(f"[red]Invalid env format: {env_var}. Use KEY=VALUE[/red]")
raise typer.Exit(1)
# Create request
request = StartSessionRequest(
entry=entry,
args=args or None,
env=env_dict if env_dict else None,
pythonPath=python_path,
)
# Create session manager
workspace_root = Path.cwd()
session_manager = SessionManager(workspace_root)
try:
response = session_manager.create_session(request)
if output_format == "json":
console.print_json(data={"sessionId": response.sessionId})
else:
table = Table(title="Session Created")
table.add_column("Session ID", style="cyan")
table.add_row(response.sessionId)
console.print(table)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
raise typer.Exit(1)
@app.command("run-to-breakpoint")
def run_to_breakpoint(
session_id: str = typer.Argument(..., help="Session ID"),
file: str = typer.Argument(..., help="Project-relative file path"),
line: int = typer.Argument(..., help="Line number (1-based)"),
output_format: str = typer.Option(
"json", "--format", "-f", help="Output format: json or table"
),
) -> None:
"""
Run to a breakpoint and capture local variables.
Example:
mcp-debug run-to-breakpoint session-123 src/main.py 15
mcp-debug run-to-breakpoint session-123 src/main.py 15 --format table
"""
from mcp_debug_tool.schemas import BreakpointRequest
from mcp_debug_tool.sessions import SessionManager
workspace_root = Path.cwd()
session_manager = SessionManager(workspace_root)
request = BreakpointRequest(file=file, line=line)
try:
response = session_manager.run_to_breakpoint(session_id, request)
if output_format == "json":
console.print_json(data=response.model_dump())
else:
_print_breakpoint_table(response)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
raise typer.Exit(1)
@app.command("continue")
def continue_execution(
session_id: str = typer.Argument(..., help="Session ID"),
file: str = typer.Argument(..., help="Project-relative file path"),
line: int = typer.Argument(..., help="Line number (1-based)"),
output_format: str = typer.Option(
"json", "--format", "-f", help="Output format: json or table"
),
) -> None:
"""
Continue execution to the next breakpoint.
Example:
mcp-debug continue session-123 src/main.py 25
mcp-debug continue session-123 src/main.py 25 --format table
"""
from mcp_debug_tool.schemas import BreakpointRequest
from mcp_debug_tool.sessions import SessionManager
workspace_root = Path.cwd()
session_manager = SessionManager(workspace_root)
request = BreakpointRequest(file=file, line=line)
try:
response = session_manager.continue_execution(session_id, request)
if output_format == "json":
console.print_json(data=response.model_dump())
else:
_print_continue_table(response)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
raise typer.Exit(1)
@app.command("state")
def get_state(
session_id: str = typer.Argument(..., help="Session ID"),
output_format: str = typer.Option(
"json", "--format", "-f", help="Output format: json or table"
),
) -> None:
"""
Get the current state of a debug session.
Example:
mcp-debug state session-123
mcp-debug state session-123 --format table
"""
from mcp_debug_tool.sessions import SessionManager
workspace_root = Path.cwd()
session_manager = SessionManager(workspace_root)
try:
response = session_manager.get_state(session_id)
if output_format == "json":
console.print_json(data=response.model_dump())
else:
_print_state_table(response)
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
raise typer.Exit(1)
@app.command("end")
def end_session(
session_id: str = typer.Argument(..., help="Session ID"),
output_format: str = typer.Option(
"json", "--format", "-f", help="Output format: json or table"
),
) -> None:
"""
End a debug session.
Example:
mcp-debug end session-123
"""
from mcp_debug_tool.sessions import SessionManager
workspace_root = Path.cwd()
session_manager = SessionManager(workspace_root)
try:
response = session_manager.end_session(session_id)
if output_format == "json":
console.print_json(data={"ended": response.ended})
else:
if response.ended:
console.print("[green]✓ Session ended successfully[/green]")
else:
console.print("[yellow]Session end status unclear[/yellow]")
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
raise typer.Exit(1)
def _print_breakpoint_table(response) -> None:
"""Print breakpoint response as a table."""
table = Table(title="Breakpoint Result")
# Status
table.add_column("Field", style="cyan")
table.add_column("Value", style="magenta")
table.add_row("Hit", "✓ Yes" if response.hit else "✗ No")
table.add_row("Completed", "✓ Yes" if response.completed else "✗ No")
if response.frameInfo:
table.add_row("Frame File", response.frameInfo.file)
table.add_row("Frame Line", str(response.frameInfo.line))
if response.error:
table.add_row("Error Type", response.error.type)
table.add_row("Error Message", response.error.message)
console.print(table)
# Print locals if available
if response.locals:
locals_table = Table(title="Local Variables")
locals_table.add_column("Name", style="cyan")
locals_table.add_column("Type", style="green")
locals_table.add_column("Value", style="yellow")
locals_table.add_column("Truncated", style="red")
for name, value in response.locals.items():
locals_table.add_row(
name,
value.get("type", "unknown"),
value.get("repr", ""),
"✓" if value.get("isTruncated", False) else "✗",
)
console.print(locals_table)
def _print_continue_table(response) -> None:
"""Print continue response as a table."""
table = Table(title="Continue Result")
table.add_column("Field", style="cyan")
table.add_column("Value", style="magenta")
table.add_row("Hit", "✓ Yes" if response.hit else "✗ No")
table.add_row("Completed", "✓ Yes" if response.completed else "✗ No")
if response.frameInfo:
table.add_row("Frame File", response.frameInfo.file)
table.add_row("Frame Line", str(response.frameInfo.line))
if response.error:
table.add_row("Error Type", response.error.type)
table.add_row("Error Message", response.error.message)
console.print(table)
# Print locals if available
if response.locals:
locals_table = Table(title="Local Variables")
locals_table.add_column("Name", style="cyan")
locals_table.add_column("Type", style="green")
locals_table.add_column("Value", style="yellow")
for name, value in response.locals.items():
locals_table.add_row(
name,
value.get("type", "unknown"),
value.get("repr", ""),
)
console.print(locals_table)
def _print_state_table(response) -> None:
"""Print state response as a table."""
table = Table(title="Session State")
table.add_column("Field", style="cyan")
table.add_column("Value", style="magenta")
table.add_row("Status", response.status)
if response.lastBreakpoint:
table.add_row("Last BP File", response.lastBreakpoint.file)
table.add_row("Last BP Line", str(response.lastBreakpoint.line))
table.add_row("Last BP Hit Count", str(response.lastBreakpoint.hitCount))
if response.timings:
if response.timings.lastRunMs is not None:
table.add_row("Last Run (ms)", f"{response.timings.lastRunMs:.2f}")
table.add_row("Total CPU Time (ms)", f"{response.timings.totalCpuTimeMs:.2f}")
console.print(table)
if __name__ == "__main__":
app()