textual-debugger 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/PKG-INFO +181 -31
  2. {textual_debugger-0.1.2/src/tdb → textual_debugger-0.1.4}/README.md +179 -30
  3. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/pyproject.toml +3 -1
  4. {textual_debugger-0.1.2 → textual_debugger-0.1.4/src/tdb}/README.md +179 -30
  5. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/__init__.py +1 -1
  6. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app.py +143 -5
  7. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/dap_events.py +12 -3
  8. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/inspection.py +82 -123
  9. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/ui_panels.py +10 -0
  10. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/cli.py +159 -17
  11. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/client.py +55 -0
  12. textual_debugger-0.1.4/src/tdb/inspection_full.py +230 -0
  13. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/keybindings/__init__.py +2 -0
  14. textual_debugger-0.1.4/src/tdb/mcp/__init__.py +10 -0
  15. textual_debugger-0.1.4/src/tdb/mcp/__main__.py +6 -0
  16. textual_debugger-0.1.4/src/tdb/mcp/server.py +306 -0
  17. textual_debugger-0.1.4/src/tdb/mcp/session.py +205 -0
  18. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/app.py +14 -0
  19. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/handlers.py +156 -105
  20. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/runner.py +51 -14
  21. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/controller.py +51 -2
  22. textual_debugger-0.1.4/src/tdb/session/inspect_service.py +265 -0
  23. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/code_view.py +27 -3
  24. textual_debugger-0.1.4/src/tdb/widgets/full_contents_modal.py +223 -0
  25. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/variable_view.py +85 -0
  26. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/PKG-INFO +181 -31
  27. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/SOURCES.txt +7 -0
  28. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/entry_points.txt +1 -0
  29. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/requires.txt +1 -0
  30. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/LICENSE +0 -0
  31. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/setup.cfg +0 -0
  32. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/__main__.py +0 -0
  33. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/_timeouts.py +0 -0
  34. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/__init__.py +0 -0
  35. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/routing.py +0 -0
  36. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_helpers.py +0 -0
  37. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/breakpoint_hook.py +0 -0
  38. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/__init__.py +0 -0
  39. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/messages.py +0 -0
  40. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/protocol.py +0 -0
  41. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/types.py +0 -0
  42. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/inspection.py +0 -0
  43. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/persist.py +0 -0
  44. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/post_mortem.py +0 -0
  45. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/processes_cache.py +0 -0
  46. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/__init__.py +0 -0
  47. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/event_handler.py +0 -0
  48. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/rpc_types.py +0 -0
  49. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/__init__.py +0 -0
  50. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/child_processes.py +0 -0
  51. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/event_bus.py +0 -0
  52. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/messages.py +0 -0
  53. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/post_mortem_loader.py +0 -0
  54. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/state.py +0 -0
  55. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/statement_stepper.py +0 -0
  56. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/terminal.py +0 -0
  57. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/textual_handler.py +0 -0
  58. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/source_analysis.py +0 -0
  59. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/__init__.py +0 -0
  60. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/_inspection_modal.py +0 -0
  61. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/async_tasks_modal.py +0 -0
  62. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/breakpoint_view.py +0 -0
  63. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/console_view.py +0 -0
  64. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/evaluate_console.py +0 -0
  65. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/menu_bar.py +0 -0
  66. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/modals.py +0 -0
  67. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/processes_modal.py +0 -0
  68. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/stack_view.py +0 -0
  69. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/status_bar.py +0 -0
  70. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/threads_modal.py +0 -0
  71. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  72. {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: textual-debugger
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A TUI Python debugger based on textual and debugpy
5
5
  Author-email: Al Danial <al.danial@gmail.com>
6
6
  License: textual-debugger is licensed to you under the MIT/X Consortium license:
@@ -51,6 +51,7 @@ Requires-Dist: debugpy>=1.8.0
51
51
  Requires-Dist: pygments>=2.17.0
52
52
  Requires-Dist: fastapi>=0.115.0
53
53
  Requires-Dist: uvicorn>=0.30.0
54
+ Requires-Dist: mcp>=1.28.0
54
55
  Provides-Extra: dev
55
56
  Requires-Dist: pytest; extra == "dev"
56
57
  Requires-Dist: pytest-cov; extra == "dev"
@@ -88,9 +89,9 @@ It specifically supports modules
88
89
  - `threading` (with a thread inspector)
89
90
  - `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
90
91
 
91
- - supports remote attachment to any debugpy-enabled Python program
92
+ - supports remote attachment to debugpy-enabled Python programs
92
93
 
93
- - includes a JSON-RPC server mode for programmatic debug control, making it suitable for
94
+ - includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
94
95
  automated, headless debugging workflows and AI-assisted debugging
95
96
 
96
97
  - can spawn the debuggee in an external terminal to enable debugging TUI applications
@@ -182,10 +183,20 @@ tdb --no-stop-on-entry my_program.py
182
183
  tdb --terminal xterm my_program.py
183
184
 
184
185
  # attach to a remote Python program that has a debugpy server on port 5678
185
- tdb -r remotehost:5678 my_program.py
186
+ # (source code is automatically downloaded from the remote host)
187
+ tdb -r remotehost:5678
186
188
 
187
- # prevent `argparse` confusion by separating `tdb` switches from
188
- # the debuggee switches by prefixing the debuggee with `--`
189
+ # attach to a remote Python program that has a debugpy server on port 5678
190
+ # and set a breakpoint where tdb and the remote program have the same
191
+ # source code layout
192
+ tdb -r remotehost:5678 -k my_program.py:42
193
+
194
+ # attach to a remote Python program that has a debugpy server on port 5678
195
+ # and set a breakpoint where code on the local host is at a different location
196
+ # than code on the remote host
197
+ tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_program.py:42
198
+
199
+ # separate tdb arguments from debuggee arguments with `--`
189
200
  tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
190
201
  ```
191
202
 
@@ -199,7 +210,7 @@ python -m tdb my_program.py
199
210
 
200
211
  ```
201
212
  ┌─ Header ──────────────────────────────────────────────┐
202
- ├─ Menu Bar (File | Configure | Help) ──────────────────┤
213
+ ├─ Menu Bar (File / Configure / Help)───────────────────┤
203
214
  │ │ │
204
215
  │ Code View │ Console View (stdout) │
205
216
  │ (source + breakpoints) ├───────────────────────────┤
@@ -270,7 +281,7 @@ Switch from Navigate back to Debug mode with `Escape`.
270
281
 
271
282
  > **Note:** Many terminals send the byte sequence `ESC+f` for `Alt+F`, which Textual's
272
283
  ANSI parser rewrites to `Ctrl+Right` (the readline "forward-word" convention).
273
- `tdb` binds both so `Alt+F` works as expected regardless.
284
+ `tdb` binds both so `Alt+F` works as expected.
274
285
 
275
286
  ### Debugging Controls
276
287
 
@@ -288,8 +299,10 @@ those for gdb/pdb, with some aliases and extras thrown in for convenience.
288
299
  | `u` / `d` | Navigate stack up (caller) / down (callee) |
289
300
  | `j` / `k` | Move cursor down / up (with count: `5j`, `10k`) |
290
301
  | `G` | Go to last line (with count: `42G` jumps to line 42) |
302
+ | `e` | Re-display the last error (traceback) |
291
303
  | `R` | Restart the debug session |
292
- | `Ctrl+Q` | Quit |
304
+ | `q q` | Quit |
305
+ | `Ctrl+q` | Quit |
293
306
 
294
307
  > **Note:** `f` ("finish") and `r` ("return") are both aliases for step-out. DAP's only
295
308
  "exit-a-function" primitive is `stepOut`, which runs the rest of the current function
@@ -310,7 +323,7 @@ print(results) # next stop lands here, not on each sub-line above
310
323
 
311
324
  lands on `print(results)`, not on each interior sub-line of the `gather` call. Switch to
312
325
  **Line** mode (Configure > Step Mode) to get debugpy's native per-line behavior, which
313
- stops on each physical lineuseful for inspecting how a complex expression is built up.
326
+ stops on each physical line--useful for inspecting how a complex expression is built up.
314
327
  The choice is saved to `~/.config/tdb/config.json`.
315
328
 
316
329
  ### Breakpoints
@@ -337,6 +350,11 @@ The Variable View shows a tree of scopes (Locals, Globals) with all variables in
337
350
  frame. Expand nodes to drill into complex objects. Children are loaded lazily on demand.
338
351
  Variable values can be changed in the Evaluate Console.
339
352
 
353
+ Double-click a variable, or highlight the variable with the text cursor in
354
+ the Variables View and press `Enter`
355
+ to display that variable in a modal. This simplifies inspection of
356
+ large or deeply nested data structures.
357
+
340
358
  ### Call Stack
341
359
 
342
360
  The Stack View shows the full call stack. Click a frame to navigate to its source
@@ -344,7 +362,8 @@ location and inspect its variables.
344
362
 
345
363
  ### Evaluate Console
346
364
 
347
- A REPL at the bottom-left evaluates expressions in the current scope:
365
+ A read-evaluate-print loop (REPL) at the bottom left permits
366
+ interactive evaluation of expressions in the current scope:
348
367
 
349
368
  ```
350
369
  >>> len(items)
@@ -370,8 +389,11 @@ The Console View captures stdout (normal text) and stderr (red text) from the de
370
389
  in real time.
371
390
 
372
391
  If your program prints a lot, or prompts for input, or uses colors or
373
- terminal control codes, consider running it in an external terminal
374
- with `--terminal` for the best experience.
392
+ terminal control codes, run the program in an external terminal
393
+ with `--terminal` for a better experience.
394
+ The `--terminal` switch requires a graphical environment and a compatible
395
+ terminal emulator.
396
+
375
397
 
376
398
  ### Crash Detection
377
399
 
@@ -381,6 +403,9 @@ When the debuggee raises an unhandled exception, `tdb`:
381
403
  3. Populates the Stack View with the exception's call stack
382
404
  4. Lets you press `R` to restart or `Escape` to dismiss
383
405
 
406
+ > Note: after dismissing the traceback modal, you
407
+ > can display it again by hitting `e` when focus is in the Code View.
408
+
384
409
  ### Post-Mortem Exception Hook
385
410
 
386
411
  You can have `tdb` pop open automatically when *any* Python program crashes without the
@@ -389,7 +414,6 @@ need to launch through `tdb` up front. Install the hook once at the top of your
389
414
  ```python
390
415
  import sys
391
416
  import tdb
392
-
393
417
  sys.excepthook = tdb.exception_hook
394
418
  ```
395
419
 
@@ -407,11 +431,11 @@ In post-mortem mode you can:
407
431
  - Read the full traceback (including chained `cause`/`context` exceptions) in the Console View
408
432
  - Jump around the source with the full Code View (syntax highlighting, goto-line, etc.)
409
433
 
410
- Stepping, `continue`, breakpoints, restart, and Evaluate are disabled. The original
411
- interpreter is gone, the view is a frozen snapshot. Press `q` to exit.
434
+ Stepping, continue, breakpoints, restart, and the Evaluate View are disabled. The original
435
+ interpreter is gone since the view is a frozen snapshot. Press `q` to exit.
412
436
 
413
437
  The hook is a no-op when stdin/stdout aren't a tty (e.g. when your program is piped or
414
- run from cron), so it's safe to leave installed in production-style code. Snapshots are
438
+ run from cron), so it is safe to leave installed in production code. Snapshots are
415
439
  written to a temp file that is deleted as soon as `tdb` exits.
416
440
 
417
441
  Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep the
@@ -422,7 +446,6 @@ memoization.
422
446
 
423
447
  `tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
424
448
  `pdb.set_trace()`) used to pause at a specific line to inspect, then
425
- here=
426
449
  continuing--use `tdb.breakpoint()`:
427
450
 
428
451
  ```python
@@ -445,7 +468,7 @@ spawns `python -m tdb -r <port>` as a subprocess so the TUI takes over the termi
445
468
  and pauses the calling thread at the line that called `tdb.breakpoint()` (the hook
446
469
  auto-steps out of its own helper so you land in your own frame, not inside
447
470
  `breakpoint_hook.py`). Stepping (`n`/`s`/`o`), `continue`, and setting/removing breakpoints
448
- all work normally; quitting `tdb` (`Ctrl+Q`) detaches without killing the program, and
471
+ all work normally; quitting `tdb` (`Ctrl+q`) detaches without killing the program, and
449
472
  debugpy auto-resumes any threads still paused.
450
473
 
451
474
  This differs from `tdb.exception_hook` in one way:
@@ -458,6 +481,12 @@ drive the user's program forward.
458
481
  As with `exception_hook`, the call is a no-op when stdin/stdout aren't a tty, so it's
459
482
  safe to leave in code paths that sometimes run headless.
460
483
 
484
+ Quitting `tdb` while paused in a `tdb.breakpoint()` session detaches the debugger and
485
+ lets the program continue running normally.
486
+ This behavior matches hitting `c` while in a conventional (that is, the Python
487
+ standard library's) `breakpoint()` session.
488
+ If you want to kill the program instead, use `Ctrl+c` in the terminal running the debuggee.
489
+
461
490
  ### Async Task Inspector
462
491
 
463
492
  For programs using `asyncio`, the menu bar shows an **Async Tasks (N)** label with the count of
@@ -467,13 +496,17 @@ active tasks (updated each time the program stops). Click it to open a full-scre
467
496
  primitive (`Lock.acquire`, `Queue.get`, `asyncio.sleep`, …), and coroutine
468
497
  - **Right pane**: detail view with full stack trace and an expandable variable tree (same
469
498
  as the main Variables View) for the selected task
470
- - Press `g` to switch the right pane to the **wait graph** a tree showing each blocked
499
+ - Press `g` to switch the right pane to the **wait graph** which is a tree showing
500
+ each blocked
471
501
  task, the asyncio primitive it's parked on, and the task(s) holding that primitive.
472
502
  Cycles (deadlocks) are highlighted in red both in the task table and as a "Deadlock
473
503
  cycles" section at the top of the graph. Selecting a node in the tree highlights the
474
504
  corresponding task in the table.
475
505
  - Navigate with arrow keys; press `r` to refresh, `Escape` to close
476
506
 
507
+ > Note: Async task relationships may be directed acyclic graphs (DAGs) rather than trees
508
+ > but I don't know of a way to visualize DAGs in textual.
509
+
477
510
  RPC equivalents:
478
511
 
479
512
  ```bash
@@ -585,6 +618,36 @@ All debugging features (breakpoints, stepping, variable inspection, threads, pro
585
618
  async tasks) work in remote attach mode. The Code View automatically navigates to the
586
619
  source file when the program stops.
587
620
 
621
+ **Mapping remote paths to local copies (`--local-root` / `--remote-root`):** when the
622
+ debuggee lives on another machine, or in a container, or simply in a different directory
623
+ on the same machine, the source paths it reports (and the paths it expects breakpoints
624
+ to refer to) won't match anything on the `tdb` host. To bridge that gap, give `tdb` one
625
+ or more `--local-root` / `--remote-root` pairs. Each `--local-root` points at a local
626
+ directory containing a copy of the code; each `--remote-root` is the corresponding path
627
+ on the debuggee. The two flags must be supplied in equal numbers and are paired in CLI
628
+ order via `zip()`, so the first `--local-root` matches the first `--remote-root`, the
629
+ second matches the second, and so on. `debugpy` then translates paths bidirectionally:
630
+ breakpoints set on a local file land on the matching remote file, and source paths
631
+ returned in stopped-events / stack-traces are rewritten back to the local copy so the
632
+ Code View loads directly from disk (no DAP `source` round-trip needed).
633
+
634
+ These flags are required whenever you want to set a `-k` breakpoint against a remote
635
+ debuggee whose code lives at a different path than your local copy. For example, if the
636
+ remote runs `program.py` at `/path/to/code/program.py` and your local copy is at
637
+ `/local/project/code/program.py`, set a breakpoint at line 321 with:
638
+
639
+ ```bash
640
+ tdb -r RHOST:15678 \
641
+ --local-root /local/project/code \
642
+ --remote-root /path/to/code \
643
+ -k program.py:321
644
+ ```
645
+
646
+ With `--local-root` set, a relative `-k FILE:LINE` is resolved by searching each
647
+ `--local-root` directory in CLI order (first match wins); absolute paths still work as
648
+ before. Multiple pairs can be supplied to mirror multiple source trees (e.g. an
649
+ application directory and a shared library directory) in one invocation.
650
+
588
651
  ### External Terminal Support
589
652
 
590
653
  Some Python programs, notably text user interfaces, use terminal control
@@ -676,11 +739,12 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
676
739
  | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
677
740
  | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
678
741
  | `list_breakpoints` | `[]` | Show all breakpoints |
679
- | `continue` | `[]` | Resume execution |
680
- | `next` | `[]` | Step over |
681
- | `step_in` | `[]` | Step into |
682
- | `step_out` | `[]` | Step out |
683
- | `pause` | `[]` | Pause execution |
742
+ | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running — call pause or wait again"` (success) |
743
+ | `next` | `[]` or `[timeout_s]` | Step over |
744
+ | `step_in` | `[]` or `[timeout_s]` | Step into |
745
+ | `step_out` | `[]` or `[timeout_s]` | Step out |
746
+ | `pause` | `[]` | Pause execution; bypasses the dispatch lock so it can interrupt an in-flight blocking action |
747
+ | `wait_for_stop` | `[]` or `[timeout_s]` | Wait for the next stop without issuing a step (use after `continue` returns `"still running"` to keep waiting) |
684
748
  | `inspect` | `["expr1", "expr2", ...]` | Evaluate multiple expressions |
685
749
  | `evaluate` | `["expression"]` | Evaluate a single expression |
686
750
  | `stack_up` | `[]` | Move up the call stack |
@@ -709,25 +773,111 @@ curl -N http://127.0.0.1:8150/events
709
773
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
710
774
  Each is JSON with `event`, `data`, and `timestamp` fields.
711
775
 
776
+ ## MCP Integration
777
+
778
+ tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
779
+ the debugger as a curated set of tools an AI agent can call. The MCP
780
+ server is a third in-process consumer of the same dispatch handlers the
781
+ TUI and the HTTP server use — so an agent gets the same lock semantics,
782
+ including the pause-during-continue bypass, and the same DAP-backed
783
+ inspection surface.
784
+
785
+ For a worked end-to-end example (prompting an agent to find two
786
+ runtime bugs in a sample program) see
787
+ [docs/tutorial-mcp-debugging.md](docs/tutorial-mcp-debugging.md) and
788
+ its companion `examples/sales_report_buggy.py`.
789
+
790
+ ### Running the MCP server
791
+
792
+ Configure your MCP client (Claude Desktop, an IDE extension, etc.) to
793
+ launch `tdb-mcp` over stdio. Example `claude_desktop_config.json`:
794
+
795
+ ```json
796
+ {
797
+ "mcpServers": {
798
+ "tdb": {
799
+ "command": "tdb-mcp"
800
+ }
801
+ }
802
+ }
803
+ ```
804
+
805
+ Three equivalent invocation forms: `tdb-mcp` (the dedicated entry
806
+ point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
807
+ `python -m tdb.mcp` (module form). Pick whichever matches how your MCP
808
+ client expects to launch servers.
809
+
810
+ ### Tool surface (16 tools, curated)
811
+
812
+ | Cluster | Tools |
813
+ |---------|-------|
814
+ | Lifecycle | `debug_launch`, `debug_attach`, `quit` |
815
+ | Control | `control(action, timeout_s=30)` — `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
816
+ | Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
817
+ | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
818
+ | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
819
+
820
+ `control` is intentionally one tool that takes an action enum — the six
821
+ underlying RPC actions share a return shape, and agents perform
822
+ measurably better with a small surface than with one tool per action.
823
+ `threads` / `tasks` / `processes` overload list-vs-inspect via a
824
+ single optional argument for the same reason.
825
+
826
+ ### Agent flow for a long-running step
827
+
828
+ ```
829
+ agent → control(action="continue", timeout_s=30)
830
+ mcp → "still running — call pause or wait again"
831
+ agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
832
+ mcp → "<file>:<line>"
833
+ agent → inspect(["x", "len(buf)"])
834
+ mcp → "x = 7\nlen(buf) = 1024"
835
+ ```
836
+
837
+ `pause` bypasses the dispatch lock so it can interrupt a `continue`
838
+ that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
839
+ policy — see `tdb/server/app.py`).
840
+
841
+ ### Security caveat
842
+
843
+ `inspect` calls debugpy's `evaluate`, which is **arbitrary Python
844
+ execution in the debuggee process**. This is inherent to a debugger and
845
+ not a tdb-specific concern, but MCP clients (and the humans running
846
+ them) should apply appropriate permission models: don't auto-approve
847
+ `inspect` against untrusted expressions, and don't expose `tdb-mcp` on
848
+ a network — stdio transport only by design.
849
+
850
+ ### Deferred / out of scope (v1)
851
+
852
+ - SSE-style event push — `control` and `wait_for_stop` make polling
853
+ efficient enough; events would also need uneven MCP-client support.
854
+ - HTTP / streamable-HTTP transports — would require auth (which the
855
+ HTTP RPC server also currently lacks); stdio inherits the trust of
856
+ the process that spawned it.
857
+ - Multi-session — one debug session per MCP process.
858
+
712
859
  ## CLI Reference
713
860
 
714
861
  ```
715
- usage: tdb [-h] [-v/--version] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
716
- [--no-just-my-code] [--no-subprocess] [--python PYTHON]
717
- [-d/--doc] [--doc-text] [--keybindings {default,vim,emacs}]
862
+ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
863
+ [--no-just-my-code] [--no-subprocess] [--python PYTHON] [--pv]
864
+ [--keybindings {default,vim,emacs}]
718
865
  [--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
719
- [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT]
866
+ [--local-root PATH] [--remote-root PATH]
867
+ [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text]
720
868
  [program] [args ...]
721
-
722
869
  ```
723
870
 
724
871
  | Flag | Description |
725
872
  |------|-------------|
726
873
  | `-r HOST:PORT` | Attach to a remote debugpy server |
874
+ | `--local-root PATH` | Local directory containing a copy of remote code (repeat to mirror multiple trees). Pair with `--remote-root`; counts must match. Required when `-k` sets a breakpoint against a remote debuggee whose code lives at a different path. |
875
+ | `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
727
876
  | `-k`, `--breakpoint FILE:LINE|LINE` | Set a breakpoint (may be repeated). Passing `-k` implies `--no-stop-on-entry` so the program runs straight to the first breakpoint. |
728
877
  | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
729
878
  | `--cwd DIR` | Working directory for the debuggee |
730
879
  | `--python PATH` | Python interpreter for the debuggee |
880
+ | `--pv` | Shorthand for --python .venv/bin/python |
731
881
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
732
882
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
733
883
  including library frames, regardless of this flag. |
@@ -759,7 +909,7 @@ into) handle multi-line source statements:
759
909
  | Value | Behavior |
760
910
  |-------|----------|
761
911
  | `"statement"` (default) | A multi-line statement (e.g. a `gather(...)` call spanning five lines) is one step. The debugger keeps issuing DAP steps until execution leaves the statement, then stops on the next logical line. |
762
- | `"line"` | debugpy's native per-line behavior stops on every physical line, including each interior sub-line of a multi-line expression. |
912
+ | `"line"` | debugpy's native per-line behavior (stops on every physical line, including each interior sub-line of a multi-line expression)|
763
913
 
764
914
  Change it from the menu (**Configure > Step Mode**); the choice is saved immediately and
765
915
  applies to all future sessions. Breakpoint hits, exceptions, and pauses always interrupt