Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ After adding the MCP server:
- **Status Check**: After restart, type `/mcp` in Claude Code to see connected servers

### Available Tools After Integration
Once connected, the following 21 MCP tools become available:
Once connected, the following 22 MCP tools become available:
- `create_debug_session` - Start a new debug session
- `list_debug_sessions` - List active debug sessions
- `list_supported_languages` - Show available language adapters
Expand All @@ -469,8 +469,11 @@ Once connected, the following 21 MCP tools become available:
- `get_scopes` - Get variable scopes for a stack frame
- `evaluate_expression` - Evaluate expressions in debug context
- `get_source_context` - Get source code around current position
- `get_output` - Read captured debuggee stdout/stderr (buffered per launch, cursor-based)
- `redefine_classes` - Hot-swap changed Java classes into a running JVM (Java only)

Each session also exposes its captured output as an MCP resource (`debug://sessions/{id}/output`, plain-text transcript) with `resources/subscribe` support — subscribed clients receive coalesced `resources/updated` notifications as the debuggee prints.

**Dev proxy only** (these 3 tools are injected by the dev proxy process itself, not by the main mcp-debugger server):
- `dev_restart_debugger` - Restart the backend (pass `rebuild: true` to build first)
- `dev_rebuild_and_restart` - Run `npm run build` then restart the backend
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ mcp-debugger exposes debugging operations as MCP tools that can be called with s
| `pause_execution` | Pause running execution | ✅ Implemented |
| `evaluate_expression` | Evaluate expressions in debug context | ✅ Implemented |
| `get_source_context` | Get source code context | ✅ Implemented |
| `get_output` | Read captured debuggee output (stdout/stderr) | ✅ Implemented |
| `close_debug_session` | Close a session | ✅ Implemented |
| `redefine_classes` | Hot-swap changed Java classes into a running JVM (Java only) | ✅ Implemented |

Expand Down
44 changes: 44 additions & 0 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This document provides a complete reference for all tools available in mcp-debug
- [get_local_variables](#get_local_variables)
- [evaluate_expression](#evaluate_expression)
- [get_source_context](#get_source_context)
- [get_output](#get_output)

---

Expand Down Expand Up @@ -694,6 +695,49 @@ Gets source code context around a specific line in a file.

---

### get_output

Gets the debuggee's output (stdout/stderr/console) captured for a session. Output is delivered by the debug adapter as DAP `output` events and buffered per launch (issue #218).

**Parameters:**
- `sessionId` (string, required): The ID of the debug session.
- `since` (number, optional): Sequence cursor — only entries with `seq` greater than this are returned. Pass `nextSince` from the previous response to fetch only new output. Default: `0` (start of the buffer).
- `limit` (number, optional): Maximum entries to return (default: 100, max: 1000).

**Response:**
```json
{
"success": true,
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"entries": [
{ "seq": 1, "category": "stdout", "output": "Factorial of 5: 120\n", "timestamp": 1754140800123 },
{ "seq": 2, "category": "stderr", "output": "warning: deprecated\n", "timestamp": 1754140800345 }
],
"nextSince": 2,
"hasMore": false,
"dropped": 0
}
```

**Notes:**
- The buffer holds the last 1000 entries per launch; older entries are evicted and counted in `dropped`. Individual entries longer than 8192 characters are cut and flagged `"truncated": true`.
- Adapter-internal `telemetry` events are filtered out at capture time; all other categories (`stdout`, `stderr`, `console`, `important`, ...) are kept. Adapters that omit a category default to `console`.
- Works while the program is running and after it finishes — output stays readable until `close_debug_session`. Re-launching a session starts a fresh buffer (seq restarts at 1).
- `hasMore: true` means more entries matched than `limit` allowed; call again with `since: nextSince`.
- Incremental polling recipe: call once, remember `nextSince`, and pass it as `since` on the next call — you'll only ever see new output.
- Adapter support: Python (`redirectOutput`), JavaScript (`outputCapture: 'std'`), and Java forward debuggee stdio as output events; Go and .NET typically do as well. Ruby currently routes debuggee stdio to the adapter process, so no entries are captured (tracked upstream).

#### Output resources & subscriptions

Each session also exposes its captured output as an MCP resource:

- **URI:** `debug://sessions/{sessionId}/output` (`text/plain`) — the verbatim console transcript (all categories interleaved in arrival order).
- **`resources/list`** enumerates one output resource per session; the list changes on session create/close (`notifications/resources/list_changed`).
- **`resources/subscribe`** to a session's URI to receive `notifications/resources/updated` pings as output arrives. Pings are coalesced (~150 ms), so notification volume is independent of how fast the debuggee prints — on a ping, re-read the resource or call `get_output` with your cursor.
- Subscriptions are tracked per server instance and cleaned up when the session closes.

---

## Additional Tools

The following tools are also available but are not fully documented with examples here:
Expand Down
5 changes: 3 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,10 @@ You can also evaluate arbitrary expressions in the current debug context:

## Fully Implemented Features

All 21 tools are fully implemented, including:
All 22 tools are fully implemented, including:

- **pause_execution**: Sends a DAP pause request and returns immediately; paused state is updated asynchronously. The session normally must be in the `running` state, but calling pause on an already paused session succeeds as a no-op.
- **get_output**: Returns the debuggee's stdout/stderr/console output, buffered per launch from DAP output events. Cursor-based (`since`/`nextSince`) for incremental polling; output stays readable after the program exits until the session is closed. The same data is exposed as a subscribable MCP resource (`debug://sessions/{id}/output`).
- **evaluate_expression**: Evaluates arbitrary expressions in the current debug context. When `frameId` is not specified, the server infers it by fetching the stack trace and using the topmost frame -- this works reliably only when a single frame exists or the top frame is the desired context. Callers should provide `frameId` explicitly when debugging code with multiple stack frames. Expressions with side effects are allowed (can modify program state).

## Best Practices
Expand All @@ -396,4 +397,4 @@ All 21 tools are fully implemented, including:

---

*Last updated: 2026-03-21 - All 21 tools including list_threads, pause_execution, and evaluate_expression are fully implemented (v0.23.0)*
*Last updated: 2026-08-02 - All 22 tools including get_output (debuggee output capture, issue #218) are fully implemented*
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export type {
Breakpoint,
DebugSession,
DebugSessionInfo,
SessionOutputEntry,

// Debug info types
Variable,
Expand Down
18 changes: 18 additions & 0 deletions packages/shared/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,24 @@ export interface SessionStopInfo {
timestamp: number;
}

/**
* One captured debuggee output event (issue #218).
* Buffered per launch; exposed via the get_output tool and the
* debug://sessions/{id}/output resource.
*/
export interface SessionOutputEntry {
/** Monotonic per-launch sequence number, starting at 1 */
seq: number;
/** DAP output category: 'stdout', 'stderr', 'console', 'important', ... ('console' when the adapter omits it) */
category: string;
/** Output text as emitted by the adapter (chunking is adapter-defined; may end with a newline) */
output: string;
/** Epoch milliseconds when the server received the event */
timestamp: number;
/** Present and true when the entry exceeded the per-entry size cap and was cut */
truncated?: boolean;
}

export interface DebugSessionInfo {
id: string;
language: DebugLanguage;
Expand Down
10 changes: 9 additions & 1 deletion src/dap-core/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,15 @@ function handleDapEvent(
args: []
});
break;


case 'output':
commands.push({
type: 'emitEvent',
event: 'output',
args: [message.body]
});
break;

default:
// Forward unknown events as generic DAP events
commands.push({
Expand Down
7 changes: 6 additions & 1 deletion src/proxy/proxy-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface ProxyManagerEvents {
'continued': () => void;
'terminated': () => void;
'exited': () => void;
'output': (body: DebugProtocol.OutputEvent['body']) => void;

// Proxy lifecycle events
'initialized': () => void;
Expand Down Expand Up @@ -1000,7 +1001,11 @@ export class ProxyManager extends EventEmitter implements IProxyManager {
case 'exited':
this.emit('exited');
break;


case 'output':
this.emit('output', message.body as DebugProtocol.OutputEvent['body']);
break;

// Forward other events as generic DAP events
default:
this.emit('dap-event', message.event, message.body);
Expand Down
Loading
Loading