Debugger Tools

February 25, 2026 ยท View on GitHub

Tools for debugging Common Lisp applications at runtime via Swank connection.

Prerequisites

The debugger tools require an active Swank connection. Connect first:

{ "name": "swank_connect", "arguments": { "port": 4006 } }

Or use the unified interface:

{ "name": "repl_connect", "arguments": { "type": "swank", "port": 4006 } }

Tools Overview

Core Debugger Tools

ToolPurposeApproval Required
debugger_framesGet stack framesNo
debugger_restartsList available restartsNo
step_frameStep execution (into/over/out)No
breakpoint_setSet breakpoint on functionYes
breakpoint_removeRemove breakpoint by IDNo
breakpoint_listList all breakpointsNo

Unified REPL Debugger Tools

ToolPurposeSwank Backend
repl_backtraceGet current backtraceswank:backtrace
repl_frame_localsGet frame variablesswank:frame-locals-and-catch-tags
repl_get_restartsList available restartsswank:compute-restarts-for-emacs
repl_invoke_restartInvoke restart by indexswank:invoke-nth-restart
repl_stepStep into next expressionswank:sldb-step-into
repl_nextStep over next expressionswank:sldb-step-next
repl_outStep out of current frameswank:sldb-step-out
repl_continueContinue executionswank:sldb-continue
repl_set_breakpointSet breakpointswank:break
repl_remove_breakpointRemove breakpointswank:break-remove
repl_list_breakpointsList breakpointsswank:break-list

debugger_frames

Get the current debugger stack frames.

Parameters

NameTypeRequiredDefaultDescription
threadstringNonilThread ID
startintegerNo0Start frame index
endintegerNo20End frame index

Example Request

{
  "name": "debugger_frames",
  "arguments": { "start": 0, "end": 10 }
}

Example Response

{
  "frames": [
    {
      "index": 0,
      "function": "MY-APP:COMPUTE-DATA",
      "source": { "file": "src/compute.lisp", "line": 42 }
    },
    {
      "index": 1,
      "function": "MY-APP:PROCESS-REQUEST",
      "source": { "file": "src/handler.lisp", "line": 15 }
    }
  ],
  "total": 5
}

debugger_restarts

List available debugger restarts.

Parameters

NameTypeRequiredDefaultDescription
threadstringNonilThread ID

Example Response

{
  "restarts": [
    {
      "index": 0,
      "name": "ABORT",
      "description": "Return to SLIME's top level"
    },
    {
      "index": 1,
      "name": "RETRY",
      "description": "Retry the current operation"
    },
    { "index": 2, "name": "CONTINUE", "description": "Continue from error" }
  ],
  "count": 3
}

step_frame

Step execution in a frame with the specified mode.

Parameters

NameTypeRequiredDefaultDescription
frameintegerYes-Frame index
modestringNo"into"Step mode: "into", "over", or "out"

Example

// Step into
{"name": "step_frame", "arguments": {"frame": 0, "mode": "into"}}

// Step over
{"name": "step_frame", "arguments": {"frame": 0, "mode": "over"}}

// Step out
{"name": "step_frame", "arguments": {"frame": 0, "mode": "out"}}

Response

{
  "mode": "into",
  "status": "stepping",
  "message": "Stepping into next function call"
}

breakpoint_set

Set a breakpoint on a function.

Parameters

NameTypeRequiredDescription
functionNamestringYesFunction name (package:symbol)
conditionstringNoLisp condition for conditional breakpoint
hitCountintegerNoBreak after N hits

Approval Required

This tool requires user approval for :set-breakpoint operation.

Example

{
  "name": "breakpoint_set",
  "arguments": {
    "functionName": "my-app:process-item",
    "condition": "(> count 10)"
  }
}

Response

{
  "breakpoint-id": 1,
  "function": "my-app:process-item",
  "status": "active",
  "message": "Breakpoint set via Swank"
}

breakpoint_remove

Remove a breakpoint by ID.

Parameters

NameTypeRequiredDescription
breakpointIdintegerYesBreakpoint ID from breakpoint_set

Example

{
  "name": "breakpoint_remove",
  "arguments": { "breakpointId": 1 }
}

breakpoint_list

List all active breakpoints.

Example Response

{
  "breakpoints": [
    { "id": 1, "function": "my-app:process-item", "enabled": true }
  ],
  "count": 1
}

Debugging Workflow Example

1. Trigger the Debugger

{ "name": "repl_eval", "arguments": { "code": "(error \"test error\")" } }

Response includes debug state (not a timeout):

{
  "result": {
    "debug": true,
    "thread": 12345,
    "level": 1,
    "condition": "test error [Condition of type SIMPLE-ERROR]",
    "restarts": [
      ["RETRY", "Retry SLIME evaluation request."],
      ["*ABORT", "Return to SLIME's top level."],
      ["ABORT", "abort thread (#<THREAD ...>)"]
    ],
    "frames": [
      [0, "(SB-INT:SIMPLE-EVAL-IN-LEXENV (ERROR \"test error\") ...)"],
      [1, "(EVAL (ERROR \"test error\"))"]
    ]
  }
}

Key insight: When :debug event arrives from Swank, the pending request is fulfilled with debug state instead of waiting for a :return message (which would never come while in debugger).

2. Get Available Restarts

{ "name": "repl_get_restarts", "arguments": {} }

Returns cached restarts from the debug event.

3. Invoke a Restart

{ "name": "repl_invoke_restart", "arguments": { "restart_index": 3 } }

Invokes restart #3 (ABORT in this example), which exits the debugger.

4. Verify Normal Operation

{ "name": "repl_eval", "arguments": { "code": "(+ 1 2)" } }

Returns {"result": {"ok": [3]}} - debugger has been exited.

Error Handling

Not Connected

{
  "error": true,
  "message": "Not connected to any REPL"
}

Swank Package Not Loaded

{
  "error": true,
  "message": "SWANK package not loaded. Connect to a Swank server first."
}

Request Timeout

{
  "error": true,
  "message": "Request timeout"
}

Implementation Notes

The debugger tools use the Swank RPC protocol to communicate with the remote Lisp:

  1. All operations are remote: The MCP does not access SBCL internals directly
  2. Thread-aware: Operations can target specific threads via Swank
  3. Async events: Debugger events are queued and can be processed asynchronously
  4. Symbol resolution: Swank symbols are resolved at runtime to allow compilation without Swank loaded locally

See Also