#!/usr/bin/env node
/**
* VSCode-integrated MCP server for debugging tools
* This server communicates with the VSCode extension via commands
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
CallToolRequest,
} from '@modelcontextprotocol/sdk/types.js';
import { execSync } from 'child_process';
class VSCodeDebuggerMcpServer {
private server: Server;
constructor() {
this.server = new Server({
name: 'vscode-debugger-mcp',
version: '1.0.0',
capabilities: {
tools: {},
},
});
this.setupToolHandlers();
}
private setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'add_breakpoint',
description: 'Add a breakpoint to a specific file and line number',
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Absolute path to the file where breakpoint should be added'
},
lineNumber: {
type: 'number',
description: 'Line number where breakpoint should be added (1-based)'
},
condition: {
type: 'string',
description: 'Optional condition for the breakpoint'
}
},
required: ['filePath', 'lineNumber']
}
},
{
name: 'remove_breakpoint',
description: 'Remove a breakpoint from a specific file and line number',
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Absolute path to the file where breakpoint should be removed'
},
lineNumber: {
type: 'number',
description: 'Line number where breakpoint should be removed (1-based)'
}
},
required: ['filePath', 'lineNumber']
}
},
{
name: 'start_debugging',
description: 'Start debugging with a specific configuration',
inputSchema: {
type: 'object',
properties: {
configurationName: {
type: 'string',
description: 'Name of the debug configuration to use'
},
workspaceFolderPath: {
type: 'string',
description: 'Path to the workspace folder'
}
},
required: []
}
},
{
name: 'stop_debugging',
description: 'Stop the current debugging session',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Optional session ID to stop specific session'
}
},
required: []
}
}
]
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'add_breakpoint':
return await this.addBreakpoint(args);
case 'remove_breakpoint':
return await this.removeBreakpoint(args);
case 'start_debugging':
return await this.startDebugging(args);
case 'stop_debugging':
return await this.stopDebugging(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error executing ${name}: ${error instanceof Error ? error.message : String(error)}`
}
]
};
}
});
}
private async executeVSCodeCommand(command: string, args: any): Promise<any> {
try {
// Create a simple file-based communication mechanism
const fs = require('fs');
const os = require('os');
const path = require('path');
const tempDir = os.tmpdir();
const requestFile = path.join(tempDir, `vscode-debugger-mcp-request-${Date.now()}.json`);
const responseFile = path.join(tempDir, `vscode-debugger-mcp-response-${Date.now()}.json`);
// Write the command request
const request = {
command,
args,
responseFile
};
fs.writeFileSync(requestFile, JSON.stringify(request));
// For now, just return success - the actual implementation would need
// a more sophisticated communication mechanism like named pipes or sockets
return { success: true };
} catch (error) {
throw new Error(`Failed to execute VSCode command: ${error instanceof Error ? error.message : String(error)}`);
}
}
private async addBreakpoint(args: any) {
const { filePath, lineNumber, condition } = args;
try {
await this.executeVSCodeCommand('addBreakpoint', {
filePath,
lineNumber,
condition
});
return {
content: [
{
type: 'text',
text: `Successfully added breakpoint at ${filePath}:${lineNumber}${condition ? ` with condition: ${condition}` : ''}`
}
]
};
} catch (error) {
throw new Error(`Failed to add breakpoint: ${error instanceof Error ? error.message : String(error)}`);
}
}
private async removeBreakpoint(args: any) {
const { filePath, lineNumber } = args;
try {
await this.executeVSCodeCommand('removeBreakpoint', {
filePath,
lineNumber
});
return {
content: [
{
type: 'text',
text: `Successfully removed breakpoint at ${filePath}:${lineNumber}`
}
]
};
} catch (error) {
throw new Error(`Failed to remove breakpoint: ${error instanceof Error ? error.message : String(error)}`);
}
}
private async startDebugging(args: any) {
const { configurationName, workspaceFolderPath } = args;
try {
await this.executeVSCodeCommand('startDebugging', {
configurationName,
workspaceFolderPath
});
return {
content: [
{
type: 'text',
text: `Successfully started debugging${configurationName ? ` with configuration: ${configurationName}` : ''}`
}
]
};
} catch (error) {
throw new Error(`Failed to start debugging: ${error instanceof Error ? error.message : String(error)}`);
}
}
private async stopDebugging(args: any) {
const { sessionId } = args;
try {
await this.executeVSCodeCommand('stopDebugging', {
sessionId
});
return {
content: [
{
type: 'text',
text: sessionId
? `Successfully stopped debugging session: ${sessionId}`
: 'Successfully stopped active debugging session'
}
]
};
} catch (error) {
throw new Error(`Failed to stop debugging: ${error instanceof Error ? error.message : String(error)}`);
}
}
public async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('VSCode Debugger MCP Server started'); // Use stderr for logging
}
}
// Start the server when run as a standalone script
if (require.main === module) {
const server = new VSCodeDebuggerMcpServer();
server.start().catch((error) => {
console.error('Failed to start MCP server:', error);
process.exit(1);
});
}
export { VSCodeDebuggerMcpServer };