Skip to content

feat(java): implement pause command and per-breakpoint suspend policy#25

Merged
debugmcpdev merged 4 commits intodebugmcp:mainfrom
Finomosec:feat/pause-and-suspend-policy
Mar 20, 2026
Merged

feat(java): implement pause command and per-breakpoint suspend policy#25
debugmcpdev merged 4 commits intodebugmcp:mainfrom
Finomosec:feat/pause-and-suspend-policy

Conversation

@Finomosec
Copy link
Copy Markdown
Contributor

@Finomosec Finomosec commented Mar 18, 2026

Summary

  • JDI Adapter: Implement DAP pause command — supports both VM-wide (vm.suspend()) and thread-specific (ThreadReference.suspend()) pause based on threadId parameter
  • Breakpoint Suspend Policy: Add optional suspendPolicy parameter ("all" | "thread") to set_breakpoint MCP tool, controlling whether all threads or only the event thread are suspended on breakpoint hit
  • Thread-aware resume: continue now uses thread-specific resume when the last stop only suspended a single thread
  • Thread-specific pause: Add optional threadId parameter to pause_execution MCP tool
  • List threads: New list_threads MCP tool exposing DAP threads command

Motivation

Several gaps in the JDI Java adapter:

  1. pause_execution MCP tool was wired up in server.ts + SessionManager but the JDI adapter had no case "pause" handler — requests fell through to "Unsupported command"
  2. All breakpoints used SUSPEND_ALL, freezing the entire VM on every hit. For multi-threaded applications (e.g. web servers), SUSPEND_EVENT_THREAD is often preferable to keep other threads running
  3. No way to list threads or pause individual threads
  4. No list_threads tool despite JDI handleThreads being implemented

Changes

File Change
JdiDapServer.java handlePause(), suspendPolicy in setBreakpointOnClass(), thread-aware handleContinue(), lastStopAllThreads tracking
packages/shared/src/models/index.ts suspendPolicy?: 'all' | 'thread' on Breakpoint interface
src/server.ts suspendPolicy + threadId params, list_threads tool, handleListThreads()
src/session/session-manager-operations.ts suspendPolicy + threadId passthrough, listThreads() method

Test plan

  • npx tsc --noEmit — no type errors
  • npx vitest run tests/core/unit/server/ — 78 tests pass (includes new tests for pause+threadId, list_threads, suspendPolicy)
  • npx vitest run tests/unit/server-coverage.test.ts — 40 tests pass
  • Manual: attach to JVM, call pause_execution (VM-wide) — VM pauses, stack readable
  • Manual: call pause_execution with threadId: 1 — specific thread pauses
  • Manual: set breakpoint with suspendPolicy: "all" — all threads stop on hit
  • Manual: set breakpoint with suspendPolicy: "thread" — only event thread stops on hit

🤖 Generated with Claude Code

Add DAP pause support to JDI adapter (vm.suspend / thread.suspend)
and introduce optional suspendPolicy parameter for breakpoints to
control whether all threads or only the event thread are suspended
on breakpoint hit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Wire threadId through MCP tool → server → SessionManager → DAP
so callers can pause a specific thread instead of the entire VM.
Defaults to 0 (all threads) when omitted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Finomosec
Copy link
Copy Markdown
Contributor Author

Finomosec commented Mar 18, 2026

Manual Test Report — Java/JDI Debugging

Setup: mcp-debugger feat/pause-and-suspend-policy → Java Swing application via JDWP attach on localhost:5009

  1. pause_execution (VM-wide, no threadId)

pause_execution(sessionId) → {success: true}
get_stack_trace → Reference.waitForReferencePendingList (3 frames)
continue_execution → success
Result: PASS

  1. pause_execution (thread-specific, threadId: 1)

pause_execution(sessionId, threadId: 1) → {success: true}
get_stack_trace → Reference.waitForReferencePendingList (3 frames, single thread paused)
continue_execution → success
Result: PASS

  1. Breakpoint with suspendPolicy: "all"

set_breakpoint(file: "...", line: 315, condition: "true", suspendPolicy: "all") → verified
— Triggered code manually → breakpoint hit
get_stack_trace → App.send() line 315 (9 frames)
continue_execution → success
Result: PASS

  1. Breakpoint with suspendPolicy: "thread"

set_breakpoint(file: "...", line: 315, condition: "true", suspendPolicy: "thread") → verified
— Triggered code manually → breakpoint hit
get_stack_trace → App.send() line 315 (18 frames, AWT EventDispatchThread only)
continue_execution → success
Result: PASS

  ┌────────────────────────────────────┬────────┐
  │              Feature               │ Result │
  ├────────────────────────────────────┼────────┤
  │ pause_execution (VM-wide)          │ PASS   │
  ├────────────────────────────────────┼────────┤
  │ pause_execution (thread-specific)  │ PASS   │
  ├────────────────────────────────────┼────────┤
  │ breakpoint suspendPolicy: "all"    │ PASS   │
  ├────────────────────────────────────┼────────┤
  │ breakpoint suspendPolicy: "thread" │ PASS   │
  └────────────────────────────────────┴────────┘

Finomosec and others added 2 commits March 18, 2026 17:05
- Add list_threads tool to expose DAP threads command via MCP
- Wire threadId through SessionManager.listThreads → DAP
- Add unit tests for: list_threads, pause with threadId,
  set_breakpoint with suspendPolicy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add targeted tests for uncovered PR diff lines:
- listThreads in session-manager-operations (success, errors, edge cases)
- pause with optional threadId parameter
- setBreakpoint with suspendPolicy in DAP request
- handleListThreads error paths in server
- list_threads missing sessionId validation

Remove dead code in handleListThreads where SessionTerminatedError,
SessionNotFoundError, and ProxyNotRunningError checks were unreachable
(all are McpError subclasses caught by the preceding instanceof check).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@debugmcpdev debugmcpdev merged commit ae8ca4b into debugmcp:main Mar 20, 2026
7 checks passed
@debugmcpdev
Copy link
Copy Markdown
Collaborator

Thanks @Finomosec! Great contribution — the pause implementation fills a real gap in the JDI adapter, and the per-breakpoint suspend policy is a smart addition for multi-threaded Java debugging. The thorough manual test report was appreciated too.

We also noticed that list_threads and threadId on pause are standard DAP and work across all adapters, not just Java — so this PR benefits the whole project beyond JDI. We've added pause test programs for all 6 languages to our integration test suite to keep this covered going forward.

debugmcpdev pushed a commit that referenced this pull request Mar 21, 2026
Add long-running pause_test example programs for all 6 languages
(Python, JavaScript, Java, Go, Rust, .NET) to enable testing of
pause_execution across all adapters. Each program runs an infinite
loop with a 500ms sleep and incrementing counter variable.

Also fix inaccurate documentation on pause_execution's threadId
parameter — it's standard DAP, not Java-only as PR #25 claimed.

- Replace examples/javascript/pause_test.js (ran instantly) with
  infinite setInterval loop
- Delete examples/pause_test.py (used input(), unusable in
  automated debugging)
- Remove "Only supported by the Java/JDI adapter" from threadId
  description in server.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants