# Directory Structure
```
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .vscode-test.mjs
├── .vscodeignore
├── CHANGELOG.md
├── eslint.config.mjs
├── examples
│ ├── python
│ │ ├── .vscode
│ │ │ └── launch.json
│ │ └── longest_substring_with_k_distinct.py
│ └── python_simple
│ ├── .vscode
│ │ └── launch.json
│ └── main.py
├── images
│ ├── claude-debugs-for-you.png
│ └── claude-debugs-logo-4x.png
├── LICENSE
├── mcp
│ ├── .gitignore
│ ├── build.js
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── debug-server.ts
│ ├── extension.ts
│ └── test
│ └── extension.test.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/mcp/.gitignore:
--------------------------------------------------------------------------------
```
out
dist
node_modules
.vscode-test/
*.vsix
build/
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
out
dist
node_modules
.vscode-test/
*.vsix
**/*.map
```
--------------------------------------------------------------------------------
/.vscode-test.mjs:
--------------------------------------------------------------------------------
```
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});
```
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
```
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*
*.visx
mcp/**
!mcp/build/index.js
.idea
```
--------------------------------------------------------------------------------
/mcp/README.md:
--------------------------------------------------------------------------------
```markdown
# Model Context Protocol Server
This is the model context protocol part of the code.
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# <img src="./images/claude-debugs-for-you.png" width="64" height="64" alt="description" align="center"> Claude Debugs For You
[](https://marketplace.visualstudio.com/items?itemName=JasonMcGhee.claude-debugs-for-you)
_aka Vibe Debugging_
### Enable Claude (or any other LLM) to interactively debug your code
This is an [MCP](https://docs.anthropic.com/en/docs/build-with-claude/mcp) Server and VS Code extension which enables claude to interactively debug and evaluate expressions.
That means it should also work with other models / clients etc. but I only demonstrate it with Claude Desktop and Continue here.
It's language-agnostic, assuming debugger console support and valid launch.json for debugging in VSCode.
## Getting Started
1. Download the extension from [releases](https://github.com/jasonjmcghee/claude-debugs-for-you/releases/) or [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JasonMcGhee.claude-debugs-for-you)
2. Install the extension
- If using `.vsix` directly, go to the three dots in "Extensions" in VS Code and choose "Install from VSIX..."
3. You will see a new status menu item "Claude Debugs For You" which shows if it is running properly (check) or failed to startup (x)
<img width="314" alt="Screenshot 2025-03-22 at 9 51 22 PM" src="https://github.com/user-attachments/assets/2cd65e0d-4c1d-4fb6-b9ea-3995149b4043" />
You can click this status menu for the commands available
<img width="510" alt="Screenshot 2025-03-22 at 9 59 22 PM" src="https://github.com/user-attachments/assets/54e339e3-81f8-4ef2-a201-6742aa2c97a8" />
### Follow one of the options below, depending on your setup
<details>
<summary>If using stdio (classic, required for Claude Desktop)</summary>
4. Copy the stdio server path to your clipboard by searching vs code commands for "Copy MCP Debug Server stdio path to clipboard"
5. Paste the following (BUT UPDATE THE PATH TO THE COPIED ONE!) in your `claude_desktop_config.json` or edit accordingly if you use other MCP servers
```
{
"mcpServers": {
"debug": {
"command": "node",
"args": [
"/path/to/mcp-debug.js"
]
}
}
}
```
6. Start Claude desktop (or other MCP client)
1. Note: You may need to restart it, if it was already running.
2. You can skip this step if using Continue/Cursor or other built-in to VS Code
</details>
<details>
<summary>If using `/sse` (e.g. Cursor)</summary>
4. Retrieve the MCP server sse address by using the "Copy MCP Debug Server sse address to clipboard" command
1. You can just write it out server URL of "http://localhost:4711/sse", or whatever port you setup in settings.
5. Add it wherever you need to based on your client
1. You may need to hit "refresh" depending on client: this is required in Cursor
6. Start MCP client
1. Note: You may need to restart it, if it was already running.
2. You can skip this step if using Continue/Cursor or other built-in to VS Code
</details>
### You're ready to debug!
_[VS Code Debugging Documentation](https://code.visualstudio.com/Docs/editor/debugging)_
Open a project containing a `.vscode/launch.json` with the first configuration setup to debug a specific file with `${file}`.
See [Run an Example](#run-an-example) below, and/or watch a demo video.
## Contributing
Find bugs or have an idea that will improve this? Please open a pull request or log an issue.
Does this readme suck? Help me improve it!
## Demo
### Using [Continue](https://github.com/continuedev/continue)
It figures out the problem, and then suggests a fix, which we just click to apply
https://github.com/user-attachments/assets/3a0a879d-2db7-4a3f-ab43-796c22a0f1ef
<details>
<summary>How do I set this up with Continue? / Show MCP Configuration</summary>
[Read the docs!](https://docs.continue.dev/customize/tools)
Configuration:
```json
{
...
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "stdio",
"command": "node",
"args": [
"/Users/jason/Library/Application Support/Code/User/globalStorage/jasonmcghee.claude-debugs-for-you/mcp-debug.js"
]
}
}
]
}
}
```
You'll also need to choose a model capable of using tools.
When the list of tools pops up, make sure to click "debug" in the list of your tools, and set it to be "Automatic".
### Troubleshooting
If you are seeing MCP errors in continue, try disabling / re-enabling the continue plugin
</details>
If helpful, this is what my configuration looks like! But it's nearly the same as Claude Desktop.
### Using Claude Desktop
In this example, I made it intentionally very cautious (make no assumptions etc - same prompt as below) but you can ask it to do whatever.
https://github.com/user-attachments/assets/ef6085f7-11a2-4eea-bb60-b5a54873b5d5
## Developing
- Clone / Open this repo with VS Code
- Run `npm run install` and `npm run compile`
- Hit "run" which will open a new VSCode
- Otherwise same as "Getting Started applies"
- To rebuild, `npm run compile`
## Package
```bash
vsce package
```
## Run an Example
Open `examples/python` in a VS Code window
Enter the prompt:
```
i am building `longest_substring_with_k_distinct` and for some reason it's not working quite right. can you debug it step by step using breakpoints and evaluating expressions to figure out where it goes wrong? make sure to use the debug tool to get access and debug! don't make any guesses as to the problem up front. DEBUG!
```
## Other things worth mentioning
When you start multiple vs code windows, you'll see a pop-up. You can gracefully hand-off "Claude Debugs For You" between windows.
You can also disable autostart. Then you'll just need to click the status menu and select "Start Server".
<img width="395" alt="Screenshot 2025-03-22 at 10 08 52 PM" src="https://github.com/user-attachments/assets/2b6d1b61-a2c6-4447-8054-b4dd02a716e8" />
## Short list of ideas
- [ ] It should use ripgrep to find what you ask for, rather than list files + get file content.
- [x] Add support for conditional breakpoints
- [ ] Add "fix" tool by allowing MCP to insert a CodeLens or "auto fix" suggestion so the user can choose to apply a recommended change or not.
- Your idea here!
```
--------------------------------------------------------------------------------
/examples/python_simple/main.py:
--------------------------------------------------------------------------------
```python
def foo():
return 1
def bar():
return 2
def main():
baz = 5
return baz + foo() + bar()
if __name__ == '__main__':
main()
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.extension-test-runner"
]
}
```
--------------------------------------------------------------------------------
/mcp/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
```json
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
```
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}
```
--------------------------------------------------------------------------------
/src/test/extension.test.ts:
--------------------------------------------------------------------------------
```typescript
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});
```
--------------------------------------------------------------------------------
/mcp/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-debug",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"bin": {
"mcp-debug": "./build/index.js"
},
"scripts": {
"build": "node build.js",
"type-check": "tsc --noEmit"
},
"files": [
"build"
],
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.1"
},
"devDependencies": {
"@types/node": "^22.10.7",
"esbuild": "^0.24.2",
"typescript": "^5.7.3"
}
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"outDir": "out",
"lib": [
"ES2022"
],
"sourceMap": true,
"rootDir": "src",
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"include": ["src/**/*"],
"exclude": ["node_modules", "mcp"]
}
```
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
```
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
export default [{
files: ["**/*.ts"],
}, {
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
}];
```
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}",
"sourceMaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
}
]
}
```
--------------------------------------------------------------------------------
/examples/python/longest_substring_with_k_distinct.py:
--------------------------------------------------------------------------------
```python
def longest_substring_with_k_distinct(s: str, k: int) -> int:
if not s or k <= 0:
return 0
char_count = {}
max_length = 0
start = 0
for end in range(len(s)):
# Add current character to window
char_count[s[end]] = char_count.get(s[end], 0) + 1
# Shrink window while we have more than k distinct characters
while len(char_count) > k:
char_count[s[start]] -= 1
if char_count[s[start]] == 0:
del char_count[s[start]]
start += 1
# Update max_length if current window is longer
curr_length = end - start
max_length = max(max_length, curr_length)
return max_length
if __name__ == "__main__":
longest_substring_with_k_distinct("aaabaabaaa", 2)
```
--------------------------------------------------------------------------------
/mcp/build.js:
--------------------------------------------------------------------------------
```javascript
// build.js
import * as esbuild from 'esbuild';
import { chmod } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
// Get __dirname equivalent in ES modules
const __dirname = dirname(fileURLToPath(import.meta.url));
async function build() {
// First build the bundle
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'node',
target: 'node20',
outfile: 'build/index.js',
minify: true,
sourcemap: true,
external: [],
format: 'cjs',
banner: {
js: '#!/usr/bin/env node',
},
loader: { '.ts': 'ts' },
tsconfig: 'tsconfig.json',
});
// Make the output file executable
await chmod('build/index.js', 0o755);
}
build().catch((err) => {
console.error('Build failed:', err);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
# Change Log
All notable changes to the "claude-debugs-for-you" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## 0.1.1
- Fixes issue with Claude Desktop not detecting tools
## 0.1.0
- Fixes /sse use via fixing to properly use zod
## 0.0.9
- Fixes bug with tool descriptions
## 0.0.8
- Adds support to resume debugging if already running and LLM requests launch.
## 0.0.7
- Introduces the status menu
- Adds multi-window support
- Improves configuration capabilities and experience
- Simplifies first-time setup
## 0.0.6
- Adds automatic startup and stability improvements
## [0.0.4]
- Add /sse support
## [0.0.3]
- Change built mcp server to be CJS instead of ESM by @jasonjmcghee in #4
- Adds Windows compatibility by fixing a bug by @dkattan in #3, fixing #2
## [0.0.2]
- Adds ability to configure the port of the MCP Server
## [0.0.1]
- Initial release (built initial prototype in hackathon)
- Added support for conditional breakpoints
- Added support for automatically opening the file for debug
- Restructured to work well with .visx and to be language agnostic
```
--------------------------------------------------------------------------------
/examples/python_simple/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
},
{
"name": "Python: Main Script",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/main.py",
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"args": []
},
{
"name": "Python: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"justMyCode": true
}
]
}
```
--------------------------------------------------------------------------------
/examples/python/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
},
{
"name": "Python: Main Script",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/main.py",
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"args": []
},
{
"name": "Python: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"justMyCode": true
}
]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "claude-debugs-for-you",
"displayName": "Claude Debugs for You",
"description": "Enable an MCP Client, such as Claude Desktop to directly debug code with breakpoints",
"version": "0.1.1",
"repository": "https://github.com/jasonjmcghee/claude-debugs-for-you",
"author": "Jason McGhee",
"publisher": "JasonMcGhee",
"icon": "images/claude-debugs-for-you.png",
"engines": {
"vscode": "^1.96.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onStartupFinished"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "claude-debugs-for-you.showCommands",
"title": "Claude Debugs For You: Show All Commands"
},
{
"command": "vscode-mcp-debug.restart",
"title": "Claude Debugs For You: (Re)Start Server"
},
{
"command": "vscode-mcp-debug.stop",
"title": "Claude Debugs For You: Stop Server"
},
{
"command": "vscode-mcp-debug.setPort",
"title": "Claude Debugs For You: Set Port"
},
{
"command": "vscode-mcp-debug.toggleAutostart",
"title": "Claude Debugs For You: Toggle Autostart"
}
],
"menus": {
"commandPalette": [
{
"command": "vscode-mcp-debug.setPort",
"when": "false"
},
{
"command": "vscode-mcp-debug.toggleAutostart",
"when": "false"
}
]
},
"configuration": {
"title": "Claude Debugs For You",
"properties": {
"mcpDebug.port": {
"type": "number",
"default": 4711,
"description": "Port number for the debug server"
},
"mcpDebug.showServerPathOnStartup": {
"type": "boolean",
"default": true,
"description": "Whether to show the server path on startup"
},
"mcpDebug.autostart": {
"type": "boolean",
"default": true,
"description": "Automatically start 'Claude Debugs For You' when opening VS Code"
}
}
}
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "cd mcp && npm run build && cd - && tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src",
"test": "vscode-test"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.4.1"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/vscode": "^1.96.0",
"@typescript-eslint/eslint-plugin": "^8.17.0",
"@typescript-eslint/parser": "^8.17.0",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1",
"eslint": "^9.16.0",
"typescript": "^5.7.2"
}
}
```
--------------------------------------------------------------------------------
/mcp/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
// Try to read port from config file, fallback to default
function getPortFromConfig(): number {
try {
// Determine the global storage path based on platform
let storagePath: string;
const homeDir = os.homedir();
if (process.platform === 'darwin') {
storagePath = path.join(homeDir, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'jasonmcghee.claude-debugs-for-you');
} else if (process.platform === 'win32') {
storagePath = path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'jasonmcghee.claude-debugs-for-you');
} else {
// Linux and others
storagePath = path.join(homeDir, '.config', 'Code', 'User', 'globalStorage', 'jasonmcghee.claude-debugs-for-you');
}
const configPath = path.join(storagePath, 'port-config.json');
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (config && typeof config.port === 'number') {
return config.port;
}
}
} catch (error) {
console.error('Error reading port config:', error);
}
return 4711; // Default port
}
async function makeRequest(payload: any): Promise<any> {
const port = getPortFromConfig();
return new Promise((resolve, reject) => {
const data = JSON.stringify(payload);
const req = http.request({
hostname: 'localhost',
port,
path: '/tcp',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
}, res => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
const response = JSON.parse(body);
if (!response.success) {
reject(new Error(response.error || 'Unknown error'));
} else {
resolve(response.data);
}
} catch (err) {
reject(err);
}
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
const server = new Server(
{
name: "mcp-debug-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
const debugDescription = `Execute a debug plan with breakpoints, launch, continues, and expression
evaluation. ONLY SET BREAKPOINTS BEFORE LAUNCHING OR WHILE PAUSED. Be careful to keep track of where
you are, if paused on a breakpoint. Make sure to find and get the contents of any requested files.
Only use continue when ready to move to the next breakpoint. Launch will bring you to the first
breakpoint. DO NOT USE CONTINUE TO GET TO THE FIRST BREAKPOINT.`;
const listFilesDescription = "List all files in the workspace. Use this to find any requested files.";
const getFileContentDescription = `Get file content with line numbers - you likely need to list files
to understand what files are available. Be careful to use absolute paths.`;
// Zod schemas for the tools
const listFilesInputSchema = {
type: "object",
properties: {
includePatterns: {
type: "array",
items: { type: "string" },
description: "Glob patterns to include (e.g. ['**/*.js'])"
},
excludePatterns: {
type: "array",
items: { type: "string" },
description: "Glob patterns to exclude (e.g. ['node_modules/**'])"
}
}
};
const getFileContentInputSchema = {
type: "object",
properties: {
path: {
type: "string",
description: "Path to the file. IT MUST BE AN ABSOLUTE PATH AND MATCH THE OUTPUT OF listFiles"
}
},
required: ["path"]
};
const debugStepSchema = {
type: "array",
items: {
type: "object",
properties: {
type: {
type: "string",
enum: ["setBreakpoint", "removeBreakpoint", "continue", "evaluate", "launch"],
description: ""
},
file: { type: "string" },
line: { type: "number" },
expression: {
description: "An expression to be evaluated in the stack frame of the current breakpoint",
type: "string"
},
condition: {
description: "If needed, a breakpoint condition may be specified to only stop on a breakpoint for some given condition.",
type: "string"
},
},
required: ["type", "file"]
}
};
const debugInputSchema = {
type: "object",
properties: {
steps: debugStepSchema
},
required: ["steps"]
};
// Main tools array with Zod schemas
const tools = [
{
name: "listFiles",
description: listFilesDescription, // Make sure this variable is defined in your code
inputSchema: listFilesInputSchema,
},
{
name: "getFileContent",
description: getFileContentDescription, // Make sure this variable is defined in your code
inputSchema: getFileContentInputSchema,
},
{
name: "debug",
description: debugDescription, // Make sure this variable is defined in your code
inputSchema: debugInputSchema,
},
];
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const response = await makeRequest({
type: 'callTool',
tool: request.params.name,
arguments: request.params.arguments
});
return {
content: [{
type: "text",
text: Array.isArray(response) ? response.join("\n") : response
}]
};
});
function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function main() {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Debug Server running");
return true;
} catch (error) {
console.error("Error starting server:", error);
return false;
}
}
// Only try up to 10 times
const MAX_RETRIES = 10;
// Wait 500ms before each subsequent check
const TIMEOUT = 500;
// Wait 500ms before first check
const INITIAL_DELAY = 500;
(async function() {
await sleep(INITIAL_DELAY);
for (let i = 0; i < MAX_RETRIES; i++) {
const success = await main();
if (success) {
break;
}
await sleep(TIMEOUT);
}
})();
```
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
```typescript
import * as vscode from 'vscode';
import { DebugServer } from './debug-server';
import * as fs from 'fs';
import * as path from 'path';
export function activate(context: vscode.ExtensionContext) {
// Get the storage path for your extension
const storagePath = context.globalStorageUri.fsPath;
// Ensure the storage directory exists
fs.mkdirSync(storagePath, { recursive: true });
const mcpServerPath = path.join(storagePath, 'mcp-debug.js');
const sourcePath = path.join(context.extensionUri.fsPath, 'mcp', 'build', 'index.js');
try {
fs.copyFileSync(sourcePath, mcpServerPath);
} catch (err: any) {
vscode.window.showErrorMessage(`Failed to setup debug server: ${err.message}`);
return;
}
const config = vscode.workspace.getConfiguration('mcpDebug');
const port = config.get<number>('port') ?? 4711;
// Write port configuration to a file that can be read by the MCP server
const portConfigPath = path.join(storagePath, 'port-config.json');
try {
fs.writeFileSync(portConfigPath, JSON.stringify({ port }));
} catch (err: any) {
vscode.window.showErrorMessage(`Failed to write port configuration: ${err.message}`);
}
const server = new DebugServer(port, portConfigPath);
// Create status bar item
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
statusBarItem.command = 'claude-debugs-for-you.showCommands';
// Update status bar with server state
function updateStatusBar() {
if (server.isRunning) {
statusBarItem.text = "$(check) Claude Debugs For You";
statusBarItem.tooltip = "Claude Debugs For You (Running) - Click to show commands";
} else {
statusBarItem.text = "$(x) Claude Debugs For You";
statusBarItem.tooltip = "Claude Debugs For You (Stopped) - Click to show commands";
}
statusBarItem.show();
}
// Listen for server state changes
server.on('started', updateStatusBar);
server.on('stopped', updateStatusBar);
// Listen for configuration changes
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(async e => {
if (e.affectsConfiguration('mcpDebug.port')) {
// Always reload the latest configuration
const updatedConfig = vscode.workspace.getConfiguration('mcpDebug');
const newPort = updatedConfig.get<number>('port') ?? 4711;
// Update port configuration file
try {
const portConfigPath = path.join(storagePath, 'port-config.json');
fs.writeFileSync(portConfigPath, JSON.stringify({ port: newPort }));
} catch (err: any) {
vscode.window.showErrorMessage(`Failed to write port configuration: ${err.message}`);
}
// Update server's port setting
server.setPort(newPort);
if (server.isRunning) {
// Port changed, restart server with new port
vscode.window.showInformationMessage(`Port changed to ${newPort}. Restarting server...`);
await vscode.commands.executeCommand('vscode-mcp-debug.restart');
}
} else if (e.affectsConfiguration('mcpDebug')) {
updateStatusBar();
}
})
);
// Initial state
updateStatusBar();
async function startServer() {
// Always get the current port from config
const updatedConfig = vscode.workspace.getConfiguration('mcpDebug');
const currentPort = updatedConfig.get<number>('port') ?? 4711;
server.setPort(currentPort);
try {
await server.start();
} catch (err: any) {
// Stop our own server
await server.stop();
// Check if this is likely a port conflict (server already running)
const nodeErr = err as NodeJS.ErrnoException;
if ((nodeErr.code === 'EADDRINUSE') || (nodeErr.message && nodeErr.message.includes('already running'))) {
const response = await vscode.window.showInformationMessage(
`Failed to start debug server. Another server is likely already running in a different VS Code window. Would you like to stop it and start the server in this window?`,
'Yes', 'No', 'Disable Autostart'
);
if (response === 'Yes') {
try {
// First try to stop any existing server
await server.forceStopExistingServer();
// Wait for the port to be released with retry logic
let portAvailable = false;
let retryCount = 0;
const maxRetries = 5;
const currentPort = server.getPort();
while (!portAvailable && retryCount < maxRetries) {
try {
// Check if port is available
const net = require('net');
const testServer = net.createServer();
await new Promise<void>((resolve, reject) => {
testServer.once('error', (err: any) => {
testServer.close();
if (err.code === 'EADDRINUSE') {
reject(new Error('Port still in use'));
} else {
reject(err);
}
});
testServer.once('listening', () => {
testServer.close();
portAvailable = true;
resolve();
});
testServer.listen(currentPort);
});
} catch (err) {
// Port still in use, wait and retry
await new Promise(resolve => setTimeout(resolve, 500));
retryCount++;
}
}
if (!portAvailable) {
throw new Error(`Port ${currentPort} is still in use after ${maxRetries} attempts to release it`);
}
// Now try to start our server
await server.start();
} catch (startErr: any) {
vscode.window.showErrorMessage(`Still failed to start debug server: ${startErr.message}`);
}
} else if (response === 'Disable Autostart') {
// Update autostart configuration to false
await startupConfig.update('autostart', false, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage('Autostart has been disabled');
}
} else {
vscode.window.showErrorMessage(`Failed to start debug server: ${err.message}`);
}
}
}
const startupConfig = vscode.workspace.getConfiguration('mcpDebug');
if (startupConfig.get<boolean>('autostart')) {
void startServer();
}
context.subscriptions.push(
statusBarItem,
vscode.commands.registerCommand('claude-debugs-for-you.showCommands', async () => {
const updatedConfig = vscode.workspace.getConfiguration('mcpDebug');
const currentPort = updatedConfig.get<number>('port') ?? 4711;
const commands = [
// Show either Start or Stop based on server state
server.isRunning
? { label: "Stop Server", command: 'vscode-mcp-debug.stop' }
: { label: "Start Server", command: 'vscode-mcp-debug.restart' },
{ label: `Set Port (currently: ${currentPort})`, command: 'vscode-mcp-debug.setPort' },
{ label: `${updatedConfig.get<boolean>('autostart') ? 'Disable' : 'Enable'} Autostart`, command: 'vscode-mcp-debug.toggleAutostart' },
{ label: "Copy stdio path", command: 'vscode-mcp-debug.copyStdioPath' },
{ label: "Copy SSE address", command: 'vscode-mcp-debug.copySseAddress' }
];
const selected = await vscode.window.showQuickPick(commands, {
placeHolder: 'Select a Claude Debugs For You command'
});
if (selected) {
vscode.commands.executeCommand(selected.command);
}
}),
vscode.commands.registerCommand('vscode-mcp-debug.restart', async () => {
try {
await server.stop();
await startServer();
} catch (err: any) {
vscode.window.showErrorMessage(`Failed to stop debug server: ${err.message}`);
await startServer();
}
}),
vscode.commands.registerCommand('vscode-mcp-debug.stop', () => {
server.stop()
.then(() => {
vscode.window.showInformationMessage('MCP Debug Server stopped');
})
.catch(err => {
vscode.window.showErrorMessage(`Failed to stop debug server: ${err.message}`);
});
}),
vscode.commands.registerCommand('vscode-mcp-debug.copyStdioPath', async () => {
await vscode.env.clipboard.writeText(mcpServerPath);
vscode.window.showInformationMessage(`MCP stdio server path copied to clipboard.`);
}),
vscode.commands.registerCommand('vscode-mcp-debug.copySseAddress', async () => {
// Always get the latest port from config
const updatedConfig = vscode.workspace.getConfiguration('mcpDebug');
const currentPort = updatedConfig.get<number>('port') ?? 4711;
await vscode.env.clipboard.writeText(`http://localhost:${currentPort}/sse`);
vscode.window.showInformationMessage(`MCP sse server address copied to clipboard.`);
}),
vscode.commands.registerCommand('vscode-mcp-debug.setPort', async () => {
// Always get the latest configuration
const updatedConfig = vscode.workspace.getConfiguration('mcpDebug');
const currentPort = updatedConfig.get<number>('port') ?? 4711;
const newPort = await vscode.window.showInputBox({
prompt: 'Enter port number for MCP Debug Server',
placeHolder: 'Port number',
value: currentPort.toString(),
validateInput: (input) => {
const port = parseInt(input);
if (isNaN(port) || port < 1024 || port > 65535) {
return 'Please enter a valid port number (1024-65535)';
}
return null;
}
});
if (newPort) {
const portNum = parseInt(newPort);
await updatedConfig.update('port', portNum, vscode.ConfigurationTarget.Global);
// Update port configuration file
try {
const portConfigPath = path.join(storagePath, 'port-config.json');
fs.writeFileSync(portConfigPath, JSON.stringify({ port: portNum }));
} catch (err: any) {
vscode.window.showErrorMessage(`Failed to write port configuration: ${err.message}`);
}
// Update server's port setting directly
server.setPort(portNum);
if (server.isRunning) {
const restart = await vscode.window.showInformationMessage(
'Port updated. Restart server to apply changes?',
'Yes', 'No'
);
if (restart === 'Yes') {
vscode.commands.executeCommand('vscode-mcp-debug.restart');
}
}
}
}),
vscode.commands.registerCommand('vscode-mcp-debug.toggleAutostart', async () => {
const updatedConfig = vscode.workspace.getConfiguration('mcpDebug');
const currentAutostart = updatedConfig.get<boolean>('autostart') ?? true;
await updatedConfig.update('autostart', !currentAutostart, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage(`Autostart ${!currentAutostart ? 'enabled' : 'disabled'}`);
}),
);
}
export function deactivate() {
// We should already have cleaned up during context disposal, but just in case
}
```
--------------------------------------------------------------------------------
/src/debug-server.ts:
--------------------------------------------------------------------------------
```typescript
import * as net from 'net';
import * as http from 'http';
import * as vscode from 'vscode';
import { EventEmitter } from 'events';
import { z } from 'zod';
interface DebugServerEvents {
on(event: 'started', listener: () => void): this;
on(event: 'stopped', listener: () => void): this;
emit(event: 'started'): boolean;
emit(event: 'stopped'): boolean;
}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
export interface DebugCommand {
command: 'listFiles' | 'getFileContent' | 'debug';
payload: any;
}
export interface DebugStep {
type: 'setBreakpoint' | 'removeBreakpoint' | 'continue' | 'evaluate' | 'launch';
file: string;
line?: number;
expression?: string;
condition?: string;
}
interface ToolRequest {
type: 'listTools' | 'callTool';
tool?: string;
arguments?: any;
}
const debugDescription = `Execute a debug plan with breakpoints, launch, continues, and expression
evaluation. ONLY SET BREAKPOINTS BEFORE LAUNCHING OR WHILE PAUSED. Be careful to keep track of where
you are, if paused on a breakpoint. Make sure to find and get the contents of any requested files.
Only use continue when ready to move to the next breakpoint. Launch will bring you to the first
breakpoint. DO NOT USE CONTINUE TO GET TO THE FIRST BREAKPOINT.`;
const listFilesDescription = "List all files in the workspace. Use this to find any requested files.";
const getFileContentDescription = `Get file content with line numbers - you likely need to list files
to understand what files are available. Be careful to use absolute paths.`;
// Zod schemas for the tools
const listFilesInputSchema = {
includePatterns: z.array(z.string()).describe("Glob patterns to include (e.g. ['**/*.js'])").optional(),
excludePatterns: z.array(z.string()).describe("Glob patterns to exclude (e.g. ['node_modules/**'])").optional(),
};
const getFileContentInputSchema = {
path: z.string().describe("Path to the file. IT MUST BE AN ABSOLUTE PATH AND MATCH THE OUTPUT OF listFiles"),
};
const debugStepSchema = z.object({
type: z.enum(["setBreakpoint", "removeBreakpoint", "continue", "evaluate", "launch"]).describe(""),
file: z.string(),
line: z.number().optional(),
expression: z.string().describe("An expression to be evaluated in the stack frame of the current breakpoint").optional(),
condition: z.string().describe("If needed, a breakpoint condition may be specified to only stop on a breakpoint for some given condition.").optional(),
});
const debugInputSchema = {
steps: z.array(debugStepSchema),
};
// Main tools array with Zod schemas
const tools = [
{
name: "listFiles",
description: listFilesDescription, // Make sure this variable is defined in your code
inputSchema: listFilesInputSchema,
},
{
name: "getFileContent",
description: getFileContentDescription, // Make sure this variable is defined in your code
inputSchema: getFileContentInputSchema,
},
{
name: "debug",
description: debugDescription, // Make sure this variable is defined in your code
inputSchema: debugInputSchema,
},
];
export class DebugServer extends EventEmitter implements DebugServerEvents {
private server: net.Server | null = null;
private port: number = 4711;
private portConfigPath: string | null = null;
private activeTransports: Record<string, SSEServerTransport> = {};
private mcpServer: McpServer;
private _isRunning: boolean = false;
constructor(port?: number, portConfigPath?: string) {
super();
this.port = port || 4711;
this.portConfigPath = portConfigPath || null;
this.mcpServer = new McpServer({
name: "Debug Server",
version: "1.0.0",
});
// Setup MCP tools to use our existing handlers
this.mcpServer.tool("listFiles", listFilesDescription, listFilesInputSchema, async (args: any) => {
const files = await this.handleListFiles(args);
return { content: [{ type: "text", text: JSON.stringify(files) }] };
});
this.mcpServer.tool("getFileContent", getFileContentDescription, getFileContentInputSchema, async (args: any) => {
const content = await this.handleGetFile(args);
return { content: [{ type: "text", text: content }] };
});
this.mcpServer.tool("debug", debugDescription, debugInputSchema, async (args: any) => {
const results = await this.handleDebug(args);
return { content: [{ type: "text", text: results.join('\n') }] };
});
}
get isRunning(): boolean {
return this._isRunning;
}
setPort(port: number): void {
this.port = port || 4711;
// Update port in configuration file if available
if (this.portConfigPath && typeof port === 'number') {
try {
const fs = require('fs');
fs.writeFileSync(this.portConfigPath, JSON.stringify({ port }));
} catch (err) {
console.error('Failed to update port configuration file:', err);
// We'll still use the new port even if saving to file fails
}
}
}
getPort(): number {
return this.port;
}
async forceStopExistingServer(): Promise<void> {
try {
// Send a request to the shutdown endpoint of any existing server
await new Promise<void>((resolve, reject) => {
const req = http.request({
hostname: 'localhost',
port: this.port,
path: '/shutdown',
method: 'POST',
timeout: 3000 // 3 second timeout
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
// Give the server a moment to shut down
setTimeout(resolve, 500);
} else {
reject(new Error(`Unexpected status: ${res.statusCode}`));
}
});
});
req.on('error', (err: NodeJS.ErrnoException) => {
// If we can't connect, there's no server running or it's not ours
if (err.code === 'ECONNREFUSED') {
resolve(); // No server running, so nothing to stop
} else {
reject(err);
}
});
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});
req.end();
});
} catch (err) {
console.error('Error requesting server shutdown:', err);
throw new Error('Failed to stop existing server');
}
}
async start(): Promise<void> {
if (this.server) {
throw new Error('Server is already running');
}
this.server = http.createServer(async (req, res) => {
// Handle CORS
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
res.writeHead(204).end();
return;
}
// Shutdown endpoint - allows another instance to request shutdown of this server
if (req.method === 'POST' && req.url === '/shutdown') {
res.writeHead(200).end('Server shutting down');
this.stop().catch(err => {
res.writeHead(500).end(`Error shutting down: ${err.message}`);
});
return;
}
// Legacy TCP-style endpoint
if (req.method === 'POST' && req.url === '/tcp') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
try {
const request = JSON.parse(body);
let response: any;
if (request.type === 'listTools') {
response = { tools };
} else if (request.type === 'callTool') {
response = await this.handleCommand(request);
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, data: response }));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
}));
}
});
return;
}
// SSE endpoint
if (req.method === 'GET' && req.url === '/sse') {
const transport = new SSEServerTransport('/messages', res);
this.activeTransports[transport.sessionId] = transport;
await this.mcpServer.connect(transport);
res.on('close', () => {
delete this.activeTransports[transport.sessionId];
});
return;
}
// Message endpoint for SSE
if (req.method === 'POST' && req.url?.startsWith('/messages')) {
const url = new URL(req.url, 'http://localhost');
const sessionId = url.searchParams.get('sessionId');
if (!sessionId || !this.activeTransports[sessionId]) {
res.writeHead(404).end('Session not found');
return;
}
await this.activeTransports[sessionId].handlePostMessage(req, res);
return;
}
res.writeHead(404).end();
});
return new Promise((resolve, reject) => {
this.server!.listen(this.port, () => {
this._isRunning = true;
this.emit('started');
resolve();
}).on('error', reject);
});
}
// Helper method to handle tool calls
private async handleCommand(request: ToolRequest): Promise<any> {
switch (request.tool) {
case 'listFiles':
return await this.handleListFiles(request.arguments);
case 'getFileContent':
return await this.handleGetFile(request.arguments);
case 'debug':
return await this.handleDebug(request.arguments);
default:
throw new Error(`Unknown tool: ${request.tool}`);
}
}
private async handleLaunch(payload: {
program: string,
args?: string[]
}): Promise<string> {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
throw new Error('No workspace folder found');
}
// Try to get launch configurations
const launchConfig = vscode.workspace.getConfiguration('launch', workspaceFolder.uri);
const configurations = launchConfig.get<any[]>('configurations');
if (!configurations || configurations.length === 0) {
throw new Error('No debug configurations found in launch.json');
}
// Get the first configuration and update it with the current file
const config = { ...configurations[0] };
// Replace ${file} with actual file path if it exists in the configuration
Object.keys(config).forEach(key => {
if (typeof config[key] === 'string') {
config[key] = config[key].replace('${file}', payload.program);
}
});
// Replace ${workspaceFolder} in environment variables if they exist
if (config.env) {
Object.keys(config.env).forEach(key => {
if (typeof config.env[key] === 'string') {
config.env[key] = config.env[key].replace(
'${workspaceFolder}',
workspaceFolder.uri.fsPath
);
}
});
}
// Check if we're already debugging
let session = vscode.debug.activeDebugSession;
if (!session) {
// Start debugging using the configured launch configuration
await vscode.debug.startDebugging(workspaceFolder, config);
// Wait for session to be available
session = await this.waitForDebugSession();
}
// Check if we're at a breakpoint
try {
const threads = await session.customRequest('threads');
const threadId = threads.threads[0].id;
const stack = await session.customRequest('stackTrace', { threadId });
if (stack.stackFrames && stack.stackFrames.length > 0) {
const topFrame = stack.stackFrames[0];
const currentBreakpoints = vscode.debug.breakpoints.filter(bp => {
if (bp instanceof vscode.SourceBreakpoint) {
return bp.location.uri.toString() === topFrame.source.path &&
bp.location.range.start.line === (topFrame.line - 1);
}
return false;
});
if (currentBreakpoints.length > 0) {
return `Debug session started - Stopped at breakpoint on line ${topFrame.line}`;
}
}
return 'Debug session started';
} catch (err) {
console.error('Error checking breakpoint status:', err);
return 'Debug session started';
}
}
private waitForDebugSession(): Promise<vscode.DebugSession> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for debug session'));
}, 5000);
const checkSession = () => {
const session = vscode.debug.activeDebugSession;
if (session) {
clearTimeout(timeout);
resolve(session);
} else {
setTimeout(checkSession, 100);
}
};
checkSession();
});
}
private async handleListFiles(payload: {
includePatterns?: string[],
excludePatterns?: string[]
}): Promise<string[]> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
throw new Error('No workspace folders found');
}
const includePatterns = payload.includePatterns || ['**/*'];
const excludePatterns = payload.excludePatterns || ['**/node_modules/**', '**/.git/**'];
const files: string[] = [];
for (const folder of workspaceFolders) {
const relativePattern = new vscode.RelativePattern(folder, `{${includePatterns.join(',')}}`);
const foundFiles = await vscode.workspace.findFiles(relativePattern, `{${excludePatterns.join(',')}}`);
files.push(...foundFiles.map(file => file.fsPath));
}
return files;
}
private async handleGetFile(payload: { path: string }): Promise<string> {
const doc = await vscode.workspace.openTextDocument(payload.path);
const lines = doc.getText().split('\n');
return lines.map((line, i) => `${i + 1}: ${line}`).join('\n');
}
private async handleDebug(payload: { steps: DebugStep[] }): Promise<string[]> {
const results: string[] = [];
for (const step of payload.steps) {
switch (step.type) {
case 'setBreakpoint': {
if (!step.line) {
throw new Error('Line number required');
}
if (!step.file) {
throw new Error('File path required');
}
// Open the file and make it active
const document = await vscode.workspace.openTextDocument(step.file);
const editor = await vscode.window.showTextDocument(document);
const bp = new vscode.SourceBreakpoint(
new vscode.Location(
editor.document.uri,
new vscode.Position(step.line - 1, 0)
),
true,
step.condition,
);
await vscode.debug.addBreakpoints([bp]);
results.push(`Set breakpoint at line ${step.line}`);
break;
}
case 'removeBreakpoint': {
if (!step.line) {
throw new Error('Line number required');
}
const bps = vscode.debug.breakpoints.filter(bp => {
if (bp instanceof vscode.SourceBreakpoint) {
return bp.location.range.start.line === step.line! - 1;
}
return false;
});
await vscode.debug.removeBreakpoints(bps);
results.push(`Removed breakpoint at line ${step.line}`);
break;
}
case 'continue': {
const session = vscode.debug.activeDebugSession;
if (!session) {
throw new Error('No active debug session');
}
await session.customRequest('continue');
results.push('Continued execution');
break;
}
case 'evaluate': {
const session = vscode.debug.activeDebugSession;
if (!session) {
throw new Error('No active debug session');
}
const activeStackItem = vscode.debug.activeStackItem;
// Grab the active frameId
let frameId = undefined;
if (activeStackItem instanceof vscode.DebugStackFrame) {
frameId = activeStackItem.frameId;
}
// In case activeStackItem.frameId is falsey
if (!frameId) {
// Get the current stack frame
const frames = await session.customRequest('stackTrace', {
threadId: 1 // You might need to get the actual threadId
});
if (!frames || !frames.stackFrames || frames.stackFrames.length === 0) {
vscode.window.showErrorMessage('No stack frame available');
break;
}
frameId = frames.stackFrames[0].id; // Usually use the top frame
}
try {
const response = await session.customRequest('evaluate', {
expression: step.expression,
frameId: frameId,
context: 'repl'
});
results.push(`Evaluated "${step.expression}": ${response.result}`);
} catch (err: any) {
let errorMessage = '';
let stackTrace = '';
if (err instanceof Error) {
errorMessage = err.message;
if (err.stack) {
stackTrace = `\nStack: ${err.stack}`;
}
} else {
errorMessage = String(err);
}
results.push(`ERROR: Evaluation failed for "${step.expression}": ${errorMessage}${stackTrace}`);
}
break;
}
case 'launch': {
await this.handleLaunch({ program: step.file });
}
}
}
return results;
}
stop(): Promise<void> {
return new Promise((resolve) => {
if (!this.server) {
this._isRunning = false;
this.emit('stopped');
resolve();
return;
}
Object.values(this.activeTransports).forEach(transport => {
transport.close();
});
this.activeTransports = {};
this.server.close(() => {
this.server = null;
this._isRunning = false;
this.emit('stopped');
resolve();
});
});
}
}
```