textual-debugger 0.1.3__tar.gz → 0.1.5__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.3 → textual_debugger-0.1.5}/PKG-INFO +107 -7
  2. {textual_debugger-0.1.3/src/tdb → textual_debugger-0.1.5}/README.md +105 -6
  3. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/pyproject.toml +3 -1
  4. {textual_debugger-0.1.3 → textual_debugger-0.1.5/src/tdb}/README.md +105 -6
  5. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/__init__.py +1 -1
  6. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app_handlers/inspection.py +39 -123
  7. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/cli.py +20 -8
  8. textual_debugger-0.1.5/src/tdb/mcp/__init__.py +10 -0
  9. textual_debugger-0.1.5/src/tdb/mcp/__main__.py +6 -0
  10. textual_debugger-0.1.5/src/tdb/mcp/server.py +306 -0
  11. textual_debugger-0.1.5/src/tdb/mcp/session.py +205 -0
  12. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/server/app.py +14 -0
  13. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/server/handlers.py +156 -105
  14. textual_debugger-0.1.5/src/tdb/session/inspect_service.py +265 -0
  15. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/source_analysis.py +20 -4
  16. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/textual_debugger.egg-info/PKG-INFO +107 -7
  17. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/textual_debugger.egg-info/SOURCES.txt +5 -0
  18. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/textual_debugger.egg-info/entry_points.txt +1 -0
  19. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/textual_debugger.egg-info/requires.txt +1 -0
  20. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/LICENSE +0 -0
  21. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/setup.cfg +0 -0
  22. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/__main__.py +0 -0
  23. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/_timeouts.py +0 -0
  24. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app.py +0 -0
  25. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app_handlers/__init__.py +0 -0
  26. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app_handlers/dap_events.py +0 -0
  27. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app_handlers/routing.py +0 -0
  28. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app_handlers/ui_panels.py +0 -0
  29. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/app_helpers.py +0 -0
  30. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/breakpoint_hook.py +0 -0
  31. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/dap/__init__.py +0 -0
  32. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/dap/client.py +0 -0
  33. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/dap/messages.py +0 -0
  34. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/dap/protocol.py +0 -0
  35. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/dap/types.py +0 -0
  36. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/inspection.py +0 -0
  37. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/inspection_full.py +0 -0
  38. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/keybindings/__init__.py +0 -0
  39. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/persist.py +0 -0
  40. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/post_mortem.py +0 -0
  41. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/processes_cache.py +0 -0
  42. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/server/__init__.py +0 -0
  43. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/server/event_handler.py +0 -0
  44. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/server/rpc_types.py +0 -0
  45. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/server/runner.py +0 -0
  46. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/__init__.py +0 -0
  47. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/child_processes.py +0 -0
  48. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/controller.py +0 -0
  49. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/event_bus.py +0 -0
  50. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/messages.py +0 -0
  51. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/post_mortem_loader.py +0 -0
  52. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/state.py +0 -0
  53. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/statement_stepper.py +0 -0
  54. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/terminal.py +0 -0
  55. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/session/textual_handler.py +0 -0
  56. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/__init__.py +0 -0
  57. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/_inspection_modal.py +0 -0
  58. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/async_tasks_modal.py +0 -0
  59. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/breakpoint_view.py +0 -0
  60. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/code_view.py +0 -0
  61. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/console_view.py +0 -0
  62. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/evaluate_console.py +0 -0
  63. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/full_contents_modal.py +0 -0
  64. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/menu_bar.py +0 -0
  65. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/modals.py +0 -0
  66. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/processes_modal.py +0 -0
  67. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/stack_view.py +0 -0
  68. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/status_bar.py +0 -0
  69. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/threads_modal.py +0 -0
  70. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/tdb/widgets/variable_view.py +0 -0
  71. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  72. {textual_debugger-0.1.3 → textual_debugger-0.1.5}/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.3
3
+ Version: 0.1.5
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"
@@ -90,7 +91,8 @@ It specifically supports modules
90
91
 
91
92
  - supports remote attachment to debugpy-enabled Python programs
92
93
 
93
- - includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
94
+ - includes a JSON-RPC server mode, an MCP mode, and a `SKILL.md` file that enable
95
+ programmatic debug control, making it suitable for
94
96
  automated, headless debugging workflows and AI-assisted debugging
95
97
 
96
98
  - can spawn the debuggee in an external terminal to enable debugging TUI applications
@@ -119,6 +121,9 @@ as open source.
119
121
  [Claude for Open Source](https://claude.com/contact-sales/claude-for-oss) program.
120
122
  `tdb` was made almost entirely with Claude Code.
121
123
 
124
+ - OpenAI, for providing access to Codex through the
125
+ [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
126
+
122
127
  ## Gallery
123
128
  <p align="center">
124
129
  <img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
@@ -441,6 +446,17 @@ Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep
441
446
  capture cheap even for pathological object graphs; cycles are handled via identity
442
447
  memoization.
443
448
 
449
+ ### Post-Mortem within a Docker Container
450
+
451
+ The `textual-debugger` GitHub repository's `examples/` directory has three files
452
+ that show how to run a `tdb`-enabled Python program under `tmux` in a Docker
453
+ container so that you can attach to the container and inspect the program
454
+ in `tdb` post-mortem analysis mode if the program hits an unhandled exception:
455
+
456
+ - [post_mortem_example.py](https://github.com/AlDanial/tdb/blob/main/examples/post_mortem_example.py)
457
+ - [post_mortem_entrypoint.sh](https://github.com/AlDanial/tdb/blob/main/examples/post_mortem_entrypoint.sh)
458
+ - [Dockerfile.post_mortem](https://github.com/AlDanial/tdb/blob/main/examples/Dockerfile.post_mortem)
459
+
444
460
  ### Live Breakpoint Hook
445
461
 
446
462
  `tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
@@ -738,11 +754,12 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
738
754
  | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
739
755
  | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
740
756
  | `list_breakpoints` | `[]` | Show all breakpoints |
741
- | `continue` | `[]` | Resume execution |
742
- | `next` | `[]` | Step over |
743
- | `step_in` | `[]` | Step into |
744
- | `step_out` | `[]` | Step out |
745
- | `pause` | `[]` | Pause execution |
757
+ | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running — call pause or wait again"` (success) |
758
+ | `next` | `[]` or `[timeout_s]` | Step over |
759
+ | `step_in` | `[]` or `[timeout_s]` | Step into |
760
+ | `step_out` | `[]` or `[timeout_s]` | Step out |
761
+ | `pause` | `[]` | Pause execution; bypasses the dispatch lock so it can interrupt an in-flight blocking action |
762
+ | `wait_for_stop` | `[]` or `[timeout_s]` | Wait for the next stop without issuing a step (use after `continue` returns `"still running"` to keep waiting) |
746
763
  | `inspect` | `["expr1", "expr2", ...]` | Evaluate multiple expressions |
747
764
  | `evaluate` | `["expression"]` | Evaluate a single expression |
748
765
  | `stack_up` | `[]` | Move up the call stack |
@@ -771,6 +788,89 @@ curl -N http://127.0.0.1:8150/events
771
788
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
772
789
  Each is JSON with `event`, `data`, and `timestamp` fields.
773
790
 
791
+ ## MCP Integration
792
+
793
+ tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
794
+ the debugger as a curated set of tools an AI agent can call. The MCP
795
+ server is a third in-process consumer of the same dispatch handlers the
796
+ TUI and the HTTP server use — so an agent gets the same lock semantics,
797
+ including the pause-during-continue bypass, and the same DAP-backed
798
+ inspection surface.
799
+
800
+ For a worked end-to-end example (prompting an agent to find two
801
+ runtime bugs in a sample program) see
802
+ [docs/tutorial-mcp-debugging.md](docs/tutorial-mcp-debugging.md) and
803
+ its companion `examples/sales_report_buggy.py`.
804
+
805
+ ### Running the MCP server
806
+
807
+ Configure your MCP client (Claude Desktop, an IDE extension, etc.) to
808
+ launch `tdb-mcp` over stdio. Example `claude_desktop_config.json`:
809
+
810
+ ```json
811
+ {
812
+ "mcpServers": {
813
+ "tdb": {
814
+ "command": "tdb-mcp"
815
+ }
816
+ }
817
+ }
818
+ ```
819
+
820
+ Three equivalent invocation forms: `tdb-mcp` (the dedicated entry
821
+ point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
822
+ `python -m tdb.mcp` (module form). Pick whichever matches how your MCP
823
+ client expects to launch servers.
824
+
825
+ ### Tool surface (16 tools, curated)
826
+
827
+ | Cluster | Tools |
828
+ |---------|-------|
829
+ | Lifecycle | `debug_launch`, `debug_attach`, `quit` |
830
+ | Control | `control(action, timeout_s=30)` — `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
831
+ | Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
832
+ | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
833
+ | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
834
+
835
+ `control` is intentionally one tool that takes an action enum — the six
836
+ underlying RPC actions share a return shape, and agents perform
837
+ measurably better with a small surface than with one tool per action.
838
+ `threads` / `tasks` / `processes` overload list-vs-inspect via a
839
+ single optional argument for the same reason.
840
+
841
+ ### Agent flow for a long-running step
842
+
843
+ ```
844
+ agent → control(action="continue", timeout_s=30)
845
+ mcp → "still running — call pause or wait again"
846
+ agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
847
+ mcp → "<file>:<line>"
848
+ agent → inspect(["x", "len(buf)"])
849
+ mcp → "x = 7\nlen(buf) = 1024"
850
+ ```
851
+
852
+ `pause` bypasses the dispatch lock so it can interrupt a `continue`
853
+ that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
854
+ policy — see `tdb/server/app.py`).
855
+
856
+ ### Security caveat
857
+
858
+ `inspect` calls debugpy's `evaluate`, which is **arbitrary Python
859
+ execution in the debuggee process**. This is inherent to a debugger and
860
+ not a tdb-specific concern, but MCP clients (and the humans running
861
+ them) should apply appropriate permission models: don't auto-approve
862
+ `inspect` against untrusted expressions, and don't expose `tdb-mcp` on
863
+ a network — stdio transport only by design.
864
+
865
+ ### Deferred / out of scope (v1)
866
+
867
+ - SSE-style event push — `control` and `wait_for_stop` make polling
868
+ efficient enough; events would also need uneven MCP-client support.
869
+ - HTTP / streamable-HTTP transports — would require auth (which the
870
+ HTTP RPC server also currently lacks); stdio inherits the trust of
871
+ the process that spawned it.
872
+ - Multi-session — one debug session per MCP process.
873
+
774
874
  ## CLI Reference
775
875
 
776
876
  ```
@@ -25,7 +25,8 @@ It specifically supports modules
25
25
 
26
26
  - supports remote attachment to debugpy-enabled Python programs
27
27
 
28
- - includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
28
+ - includes a JSON-RPC server mode, an MCP mode, and a `SKILL.md` file that enable
29
+ programmatic debug control, making it suitable for
29
30
  automated, headless debugging workflows and AI-assisted debugging
30
31
 
31
32
  - can spawn the debuggee in an external terminal to enable debugging TUI applications
@@ -54,6 +55,9 @@ as open source.
54
55
  [Claude for Open Source](https://claude.com/contact-sales/claude-for-oss) program.
55
56
  `tdb` was made almost entirely with Claude Code.
56
57
 
58
+ - OpenAI, for providing access to Codex through the
59
+ [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
60
+
57
61
  ## Gallery
58
62
  <p align="center">
59
63
  <img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
@@ -376,6 +380,17 @@ Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep
376
380
  capture cheap even for pathological object graphs; cycles are handled via identity
377
381
  memoization.
378
382
 
383
+ ### Post-Mortem within a Docker Container
384
+
385
+ The `textual-debugger` GitHub repository's `examples/` directory has three files
386
+ that show how to run a `tdb`-enabled Python program under `tmux` in a Docker
387
+ container so that you can attach to the container and inspect the program
388
+ in `tdb` post-mortem analysis mode if the program hits an unhandled exception:
389
+
390
+ - [post_mortem_example.py](https://github.com/AlDanial/tdb/blob/main/examples/post_mortem_example.py)
391
+ - [post_mortem_entrypoint.sh](https://github.com/AlDanial/tdb/blob/main/examples/post_mortem_entrypoint.sh)
392
+ - [Dockerfile.post_mortem](https://github.com/AlDanial/tdb/blob/main/examples/Dockerfile.post_mortem)
393
+
379
394
  ### Live Breakpoint Hook
380
395
 
381
396
  `tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
@@ -673,11 +688,12 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
673
688
  | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
674
689
  | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
675
690
  | `list_breakpoints` | `[]` | Show all breakpoints |
676
- | `continue` | `[]` | Resume execution |
677
- | `next` | `[]` | Step over |
678
- | `step_in` | `[]` | Step into |
679
- | `step_out` | `[]` | Step out |
680
- | `pause` | `[]` | Pause execution |
691
+ | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running — call pause or wait again"` (success) |
692
+ | `next` | `[]` or `[timeout_s]` | Step over |
693
+ | `step_in` | `[]` or `[timeout_s]` | Step into |
694
+ | `step_out` | `[]` or `[timeout_s]` | Step out |
695
+ | `pause` | `[]` | Pause execution; bypasses the dispatch lock so it can interrupt an in-flight blocking action |
696
+ | `wait_for_stop` | `[]` or `[timeout_s]` | Wait for the next stop without issuing a step (use after `continue` returns `"still running"` to keep waiting) |
681
697
  | `inspect` | `["expr1", "expr2", ...]` | Evaluate multiple expressions |
682
698
  | `evaluate` | `["expression"]` | Evaluate a single expression |
683
699
  | `stack_up` | `[]` | Move up the call stack |
@@ -706,6 +722,89 @@ curl -N http://127.0.0.1:8150/events
706
722
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
707
723
  Each is JSON with `event`, `data`, and `timestamp` fields.
708
724
 
725
+ ## MCP Integration
726
+
727
+ tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
728
+ the debugger as a curated set of tools an AI agent can call. The MCP
729
+ server is a third in-process consumer of the same dispatch handlers the
730
+ TUI and the HTTP server use — so an agent gets the same lock semantics,
731
+ including the pause-during-continue bypass, and the same DAP-backed
732
+ inspection surface.
733
+
734
+ For a worked end-to-end example (prompting an agent to find two
735
+ runtime bugs in a sample program) see
736
+ [docs/tutorial-mcp-debugging.md](docs/tutorial-mcp-debugging.md) and
737
+ its companion `examples/sales_report_buggy.py`.
738
+
739
+ ### Running the MCP server
740
+
741
+ Configure your MCP client (Claude Desktop, an IDE extension, etc.) to
742
+ launch `tdb-mcp` over stdio. Example `claude_desktop_config.json`:
743
+
744
+ ```json
745
+ {
746
+ "mcpServers": {
747
+ "tdb": {
748
+ "command": "tdb-mcp"
749
+ }
750
+ }
751
+ }
752
+ ```
753
+
754
+ Three equivalent invocation forms: `tdb-mcp` (the dedicated entry
755
+ point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
756
+ `python -m tdb.mcp` (module form). Pick whichever matches how your MCP
757
+ client expects to launch servers.
758
+
759
+ ### Tool surface (16 tools, curated)
760
+
761
+ | Cluster | Tools |
762
+ |---------|-------|
763
+ | Lifecycle | `debug_launch`, `debug_attach`, `quit` |
764
+ | Control | `control(action, timeout_s=30)` — `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
765
+ | Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
766
+ | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
767
+ | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
768
+
769
+ `control` is intentionally one tool that takes an action enum — the six
770
+ underlying RPC actions share a return shape, and agents perform
771
+ measurably better with a small surface than with one tool per action.
772
+ `threads` / `tasks` / `processes` overload list-vs-inspect via a
773
+ single optional argument for the same reason.
774
+
775
+ ### Agent flow for a long-running step
776
+
777
+ ```
778
+ agent → control(action="continue", timeout_s=30)
779
+ mcp → "still running — call pause or wait again"
780
+ agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
781
+ mcp → "<file>:<line>"
782
+ agent → inspect(["x", "len(buf)"])
783
+ mcp → "x = 7\nlen(buf) = 1024"
784
+ ```
785
+
786
+ `pause` bypasses the dispatch lock so it can interrupt a `continue`
787
+ that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
788
+ policy — see `tdb/server/app.py`).
789
+
790
+ ### Security caveat
791
+
792
+ `inspect` calls debugpy's `evaluate`, which is **arbitrary Python
793
+ execution in the debuggee process**. This is inherent to a debugger and
794
+ not a tdb-specific concern, but MCP clients (and the humans running
795
+ them) should apply appropriate permission models: don't auto-approve
796
+ `inspect` against untrusted expressions, and don't expose `tdb-mcp` on
797
+ a network — stdio transport only by design.
798
+
799
+ ### Deferred / out of scope (v1)
800
+
801
+ - SSE-style event push — `control` and `wait_for_stop` make polling
802
+ efficient enough; events would also need uneven MCP-client support.
803
+ - HTTP / streamable-HTTP transports — would require auth (which the
804
+ HTTP RPC server also currently lacks); stdio inherits the trust of
805
+ the process that spawned it.
806
+ - Multi-session — one debug session per MCP process.
807
+
709
808
  ## CLI Reference
710
809
 
711
810
  ```
@@ -25,7 +25,8 @@
25
25
  "pygments>=2.17.0",
26
26
  "fastapi>=0.115.0",
27
27
  "uvicorn>=0.30.0",
28
- ]
28
+ "mcp>=1.28.0",
29
+ ]
29
30
 
30
31
  # extras needed to run the examples and tests
31
32
  [project.optional-dependencies]
@@ -43,6 +44,7 @@
43
44
 
44
45
  [project.scripts]
45
46
  tdb = "tdb.cli:main"
47
+ tdb-mcp = "tdb.mcp.server:main"
46
48
 
47
49
  [build-system]
48
50
  requires = ["setuptools>=68.0"]
@@ -25,7 +25,8 @@ It specifically supports modules
25
25
 
26
26
  - supports remote attachment to debugpy-enabled Python programs
27
27
 
28
- - includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
28
+ - includes a JSON-RPC server mode, an MCP mode, and a `SKILL.md` file that enable
29
+ programmatic debug control, making it suitable for
29
30
  automated, headless debugging workflows and AI-assisted debugging
30
31
 
31
32
  - can spawn the debuggee in an external terminal to enable debugging TUI applications
@@ -54,6 +55,9 @@ as open source.
54
55
  [Claude for Open Source](https://claude.com/contact-sales/claude-for-oss) program.
55
56
  `tdb` was made almost entirely with Claude Code.
56
57
 
58
+ - OpenAI, for providing access to Codex through the
59
+ [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
60
+
57
61
  ## Gallery
58
62
  <p align="center">
59
63
  <img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
@@ -376,6 +380,17 @@ Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep
376
380
  capture cheap even for pathological object graphs; cycles are handled via identity
377
381
  memoization.
378
382
 
383
+ ### Post-Mortem within a Docker Container
384
+
385
+ The `textual-debugger` GitHub repository's `examples/` directory has three files
386
+ that show how to run a `tdb`-enabled Python program under `tmux` in a Docker
387
+ container so that you can attach to the container and inspect the program
388
+ in `tdb` post-mortem analysis mode if the program hits an unhandled exception:
389
+
390
+ - [post_mortem_example.py](https://github.com/AlDanial/tdb/blob/main/examples/post_mortem_example.py)
391
+ - [post_mortem_entrypoint.sh](https://github.com/AlDanial/tdb/blob/main/examples/post_mortem_entrypoint.sh)
392
+ - [Dockerfile.post_mortem](https://github.com/AlDanial/tdb/blob/main/examples/Dockerfile.post_mortem)
393
+
379
394
  ### Live Breakpoint Hook
380
395
 
381
396
  `tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
@@ -673,11 +688,12 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
673
688
  | `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
674
689
  | `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
675
690
  | `list_breakpoints` | `[]` | Show all breakpoints |
676
- | `continue` | `[]` | Resume execution |
677
- | `next` | `[]` | Step over |
678
- | `step_in` | `[]` | Step into |
679
- | `step_out` | `[]` | Step out |
680
- | `pause` | `[]` | Pause execution |
691
+ | `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running — call pause or wait again"` (success) |
692
+ | `next` | `[]` or `[timeout_s]` | Step over |
693
+ | `step_in` | `[]` or `[timeout_s]` | Step into |
694
+ | `step_out` | `[]` or `[timeout_s]` | Step out |
695
+ | `pause` | `[]` | Pause execution; bypasses the dispatch lock so it can interrupt an in-flight blocking action |
696
+ | `wait_for_stop` | `[]` or `[timeout_s]` | Wait for the next stop without issuing a step (use after `continue` returns `"still running"` to keep waiting) |
681
697
  | `inspect` | `["expr1", "expr2", ...]` | Evaluate multiple expressions |
682
698
  | `evaluate` | `["expression"]` | Evaluate a single expression |
683
699
  | `stack_up` | `[]` | Move up the call stack |
@@ -706,6 +722,89 @@ curl -N http://127.0.0.1:8150/events
706
722
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
707
723
  Each is JSON with `event`, `data`, and `timestamp` fields.
708
724
 
725
+ ## MCP Integration
726
+
727
+ tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
728
+ the debugger as a curated set of tools an AI agent can call. The MCP
729
+ server is a third in-process consumer of the same dispatch handlers the
730
+ TUI and the HTTP server use — so an agent gets the same lock semantics,
731
+ including the pause-during-continue bypass, and the same DAP-backed
732
+ inspection surface.
733
+
734
+ For a worked end-to-end example (prompting an agent to find two
735
+ runtime bugs in a sample program) see
736
+ [docs/tutorial-mcp-debugging.md](docs/tutorial-mcp-debugging.md) and
737
+ its companion `examples/sales_report_buggy.py`.
738
+
739
+ ### Running the MCP server
740
+
741
+ Configure your MCP client (Claude Desktop, an IDE extension, etc.) to
742
+ launch `tdb-mcp` over stdio. Example `claude_desktop_config.json`:
743
+
744
+ ```json
745
+ {
746
+ "mcpServers": {
747
+ "tdb": {
748
+ "command": "tdb-mcp"
749
+ }
750
+ }
751
+ }
752
+ ```
753
+
754
+ Three equivalent invocation forms: `tdb-mcp` (the dedicated entry
755
+ point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
756
+ `python -m tdb.mcp` (module form). Pick whichever matches how your MCP
757
+ client expects to launch servers.
758
+
759
+ ### Tool surface (16 tools, curated)
760
+
761
+ | Cluster | Tools |
762
+ |---------|-------|
763
+ | Lifecycle | `debug_launch`, `debug_attach`, `quit` |
764
+ | Control | `control(action, timeout_s=30)` — `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
765
+ | Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
766
+ | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
767
+ | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
768
+
769
+ `control` is intentionally one tool that takes an action enum — the six
770
+ underlying RPC actions share a return shape, and agents perform
771
+ measurably better with a small surface than with one tool per action.
772
+ `threads` / `tasks` / `processes` overload list-vs-inspect via a
773
+ single optional argument for the same reason.
774
+
775
+ ### Agent flow for a long-running step
776
+
777
+ ```
778
+ agent → control(action="continue", timeout_s=30)
779
+ mcp → "still running — call pause or wait again"
780
+ agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
781
+ mcp → "<file>:<line>"
782
+ agent → inspect(["x", "len(buf)"])
783
+ mcp → "x = 7\nlen(buf) = 1024"
784
+ ```
785
+
786
+ `pause` bypasses the dispatch lock so it can interrupt a `continue`
787
+ that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
788
+ policy — see `tdb/server/app.py`).
789
+
790
+ ### Security caveat
791
+
792
+ `inspect` calls debugpy's `evaluate`, which is **arbitrary Python
793
+ execution in the debuggee process**. This is inherent to a debugger and
794
+ not a tdb-specific concern, but MCP clients (and the humans running
795
+ them) should apply appropriate permission models: don't auto-approve
796
+ `inspect` against untrusted expressions, and don't expose `tdb-mcp` on
797
+ a network — stdio transport only by design.
798
+
799
+ ### Deferred / out of scope (v1)
800
+
801
+ - SSE-style event push — `control` and `wait_for_stop` make polling
802
+ efficient enough; events would also need uneven MCP-client support.
803
+ - HTTP / streamable-HTTP transports — would require auth (which the
804
+ HTTP RPC server also currently lacks); stdio inherits the trust of
805
+ the process that spawned it.
806
+ - Multi-session — one debug session per MCP process.
807
+
709
808
  ## CLI Reference
710
809
 
711
810
  ```
@@ -1,6 +1,6 @@
1
1
  from tdb.breakpoint_hook import breakpoint
2
2
  from tdb.post_mortem import exception_hook
3
3
 
4
- __version__ = "0.1.3"
4
+ __version__ = "0.1.5"
5
5
 
6
6
  __all__ = ["breakpoint", "exception_hook"]