# Directory Structure
```
├── examples
│ ├── crash.c
│ └── USAGE.md
├── install.sh
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# MCP GDB Server
A Model Context Protocol (MCP) server that provides GDB debugging functionality for use with Claude or other AI assistants.
## Features
- Start and manage GDB debugging sessions
- Load programs and core dumps for analysis
- Set breakpoints, step through code, and examine memory
- View call stacks, variables, and registers
- Execute arbitrary GDB commands
## Installation
```bash
# Clone the repository
git clone https://github.com/signal-slot/mcp-gdb.git
cd mcp-gdb
# Install dependencies
npm install
# Build the project
npm run build
```
## Usage
### Using with Claude or other MCP-enabled assistants
1. Configure the MCP settings in the Claude desktop app or browser extension to include this server:
```json
{
"mcpServers": {
"gdb": {
"command": "node",
"args": ["/path/to/mcp-gdb/build/index.js"],
"disabled": false
}
}
}
```
2. Restart Claude or refresh the page.
3. Now you can use the GDB tools in your conversations with Claude.
### Example Commands
Here are some examples of using the GDB MCP server through Claude:
#### Starting a GDB session
```
Use gdb_start to start a new debugging session
```
#### Loading a program
```
Use gdb_load to load /path/to/my/program with the sessionId that was returned from gdb_start
```
#### Setting a breakpoint
```
Use gdb_set_breakpoint to set a breakpoint at main in the active GDB session
```
#### Running the program
```
Use gdb_continue to start execution
```
#### Examining variables
```
Use gdb_print to evaluate the expression "my_variable" in the current context
```
#### Getting a backtrace
```
Use gdb_backtrace to see the current call stack
```
#### Terminating the session
```
Use gdb_terminate to end the debugging session
```
## Supported GDB Commands
- `gdb_start`: Start a new GDB session
- `gdb_load`: Load a program into GDB
- `gdb_command`: Execute an arbitrary GDB command
- `gdb_terminate`: Terminate a GDB session
- `gdb_list_sessions`: List all active GDB sessions
- `gdb_attach`: Attach to a running process
- `gdb_load_core`: Load a core dump file
- `gdb_set_breakpoint`: Set a breakpoint
- `gdb_continue`: Continue program execution
- `gdb_step`: Step program execution
- `gdb_next`: Step over function calls
- `gdb_finish`: Execute until the current function returns
- `gdb_backtrace`: Show call stack
- `gdb_print`: Print value of expression
- `gdb_examine`: Examine memory
- `gdb_info_registers`: Display registers
## License
MIT
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "build",
"declaration": true,
"sourceMap": true,
"resolveJsonModule": true,
"noImplicitAny": false,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build"]
}
```
--------------------------------------------------------------------------------
/examples/crash.c:
--------------------------------------------------------------------------------
```cpp
#include <stdio.h>
#include <stdlib.h>
void function_that_crashes() {
// Access a null pointer to cause a segmentation fault
int *ptr = NULL;
*ptr = 42; // This will cause a crash
}
void function_with_args(int a, int b) {
printf("Arguments: a=%d, b=%d\n", a, b);
if (a > 10) {
function_that_crashes();
}
}
int main(int argc, char **argv) {
printf("Starting the program...\n");
// Print command line arguments
printf("Got %d arguments\n", argc);
for (int i = 0; i < argc; i++) {
printf("Argument %d: %s\n", i, argv[i]);
}
int number = 5;
if (argc > 1) {
number = atoi(argv[1]);
}
printf("Working with number: %d\n", number);
function_with_args(number, number * 2);
printf("Program completed successfully\n");
return 0;
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-gdb",
"version": "0.1.1",
"description": "MCP server for GDB debugging functionality",
"type": "module",
"main": "build/index.js",
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"start": "node build/index.js",
"dev": "tsc-watch --onSuccess \"node build/index.js\"",
"lint": "eslint src/**/*.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"gdb",
"debug",
"mcp"
],
"author": "Tasuku Suzuki",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.10.5",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"eslint": "^8.56.0",
"tsc-watch": "^6.0.4",
"typescript": "^5.3.3"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0"
},
"directories": {
"example": "examples"
},
"repository": {
"type": "git",
"url": "git+https://github.com/signal-slot/mcp-gdb.git"
},
"types": "./build/index.d.ts",
"bugs": {
"url": "https://github.com/signal-slot/mcp-gdb/issues"
},
"homepage": "https://github.com/signal-slot/mcp-gdb#readme"
}
```
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# MCP GDB Server Installer Script
set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
BUILD_DIR="$SCRIPT_DIR/build"
GDB_PATH=$(which gdb 2>/dev/null || echo "")
echo "MCP GDB Server Installer"
echo "========================"
echo
# Check if GDB is installed
if [ -z "$GDB_PATH" ]; then
echo "⚠️ Warning: GDB is not found in your PATH. You need to install GDB before using this MCP server."
echo "On Ubuntu/Debian: sudo apt-get install gdb"
echo "On Fedora/RHEL: sudo dnf install gdb"
echo "On Arch Linux: sudo pacman -S gdb"
echo "On macOS: brew install gdb"
echo
read -p "Do you want to continue with the installation? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Install dependencies
echo "Installing dependencies..."
npm install
# Build the project
echo "Building the MCP GDB server..."
npm run build
# Get the absolute path to the build directory
ABSOLUTE_PATH=$(cd "$BUILD_DIR" && pwd)
# Generate MCP settings configuration snippet
echo
echo "MCP GDB Server has been built successfully!"
echo
echo "To use it with Claude or other MCP-enabled assistants, add the following to your MCP settings configuration:"
echo
echo "{"
echo " \"mcpServers\": {"
echo " \"gdb\": {"
echo " \"command\": \"node\","
echo " \"args\": [\"$ABSOLUTE_PATH/index.js\"],"
echo " \"disabled\": false"
echo " }"
echo " }"
echo "}"
echo
echo "For Claude Desktop, this file is typically located at:"
echo "- Linux: ~/.config/Claude/claude_desktop_config.json"
echo "- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json"
echo "- Windows: %APPDATA%\\Claude\\claude_desktop_config.json"
echo
echo "For Claude in VSCode, this file is typically located at:"
echo "- Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
echo "- macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
echo "- Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json"
echo
echo "After adding the configuration, restart Claude or reload the extension for the changes to take effect."
echo
echo "To test the MCP GDB server, compile and debug the example program:"
echo " cd examples"
echo " gcc -g crash.c -o crash"
echo
echo "Then follow the instructions in examples/USAGE.md to debug the program using Claude."
echo
# Create example directory and compile the example file
if [ -d "$SCRIPT_DIR/examples" ]; then
echo "Would you like to compile the example program now? (y/n) "
read -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
cd "$SCRIPT_DIR/examples"
echo "Compiling example program..."
gcc -g crash.c -o crash
echo "Example program compiled successfully!"
fi
fi
echo "Installation complete!"
```
--------------------------------------------------------------------------------
/examples/USAGE.md:
--------------------------------------------------------------------------------
```markdown
# Using the MCP GDB Server: Example Usage
This guide demonstrates how to use the MCP GDB server to debug the included `crash.c` example.
## Compiling the Example
First, compile the example with debug symbols:
```bash
cd examples
gcc -g crash.c -o crash
```
## Debugging Session Example
Here's a step-by-step guide showing how to debug the crash.c program using the MCP GDB server through Claude.
### 1. Start a GDB Session
Ask Claude to start a new GDB session:
```
I need to debug a program that's crashing. Can you start a new GDB session for me?
```
Claude will use the `gdb_start` tool:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_start</tool_name>
<arguments>
{}
</arguments>
</use_mcp_tool>
```
Claude will receive a response with a session ID (e.g., "1616161616161").
### 2. Load the Program
Now, ask Claude to load the program:
```
Can you load the crash executable I compiled in the examples directory?
```
Claude will use the `gdb_load` tool:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_load</tool_name>
<arguments>
{
"sessionId": "1616161616161",
"program": "/path/to/mcp-gdb/examples/crash"
}
</arguments>
</use_mcp_tool>
```
### 3. Set Breakpoints
Set a breakpoint at the main function:
```
Please set a breakpoint at the main function.
```
Claude will use `gdb_set_breakpoint`:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_set_breakpoint</tool_name>
<arguments>
{
"sessionId": "1616161616161",
"location": "main"
}
</arguments>
</use_mcp_tool>
```
Let's also set a breakpoint at the function that might crash:
```
Can you also set a breakpoint at function_with_args?
```
Claude will set another breakpoint:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_set_breakpoint</tool_name>
<arguments>
{
"sessionId": "1616161616161",
"location": "function_with_args"
}
</arguments>
</use_mcp_tool>
```
### 4. Start the Program
Start the program with an argument that will cause a crash (any number > 10):
```
Please run the program with an argument of 15, which should trigger the crash.
```
Claude will use `gdb_command` to set the arguments and then run the program:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_command</tool_name>
<arguments>
{
"sessionId": "1616161616161",
"command": "set args 15"
}
</arguments>
</use_mcp_tool>
```
Then Claude will start the program:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_continue</tool_name>
<arguments>
{
"sessionId": "1616161616161"
}
</arguments>
</use_mcp_tool>
```
The program will hit the first breakpoint at `main`.
### 5. Step Through the Program
Ask Claude to step through the execution:
```
Please step through the program execution so we can see what's happening.
```
Claude will use `gdb_next` to step through the code:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_next</tool_name>
<arguments>
{
"sessionId": "1616161616161"
}
</arguments>
</use_mcp_tool>
```
Repeat this command to step through the program.
### 6. Examine Variables
At any point, you can ask Claude to print the value of variables:
```
What is the value of the 'number' variable?
```
Claude will use `gdb_print`:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_print</tool_name>
<arguments>
{
"sessionId": "1616161616161",
"expression": "number"
}
</arguments>
</use_mcp_tool>
```
### 7. Continue to Crash
Continue execution until the program crashes:
```
Please continue execution until we hit the crash.
```
Claude will use `gdb_continue`:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_continue</tool_name>
<arguments>
{
"sessionId": "1616161616161"
}
</arguments>
</use_mcp_tool>
```
The program will hit the second breakpoint and then crash when trying to dereference a NULL pointer.
### 8. Analyze the Crash
When the program crashes, examine the backtrace to see where it occurred:
```
Can you show me the backtrace of the crash?
```
Claude will use `gdb_backtrace`:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_backtrace</tool_name>
<arguments>
{
"sessionId": "1616161616161"
}
</arguments>
</use_mcp_tool>
```
The backtrace will show that the crash occurred in `function_that_crashes` when trying to dereference a NULL pointer.
### 9. Terminate the Session
When you're done debugging, terminate the GDB session:
```
Please terminate the GDB session.
```
Claude will use `gdb_terminate`:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_terminate</tool_name>
<arguments>
{
"sessionId": "1616161616161"
}
</arguments>
</use_mcp_tool>
```
## Using with Core Dumps
If the program has already crashed and generated a core dump, you can load it for analysis:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_start</tool_name>
<arguments>
{}
</arguments>
</use_mcp_tool>
```
Then load the program and core dump:
```
<use_mcp_tool>
<server_name>gdb</server_name>
<tool_name>gdb_load_core</tool_name>
<arguments>
{
"sessionId": "1616161616161",
"program": "/path/to/mcp-gdb/examples/crash",
"corePath": "/path/to/core.dump"
}
</arguments>
</use_mcp_tool>
```
Then you can analyze the crash just as if you had caught it in real-time.
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError
} from '@modelcontextprotocol/sdk/types.js';
import { spawn, ChildProcess } from 'child_process';
import * as readline from 'readline';
import * as fs from 'fs';
import * as path from 'path';
// Interface for GDB session
interface GdbSession {
process: ChildProcess;
rl: readline.Interface;
ready: boolean;
id: string;
target?: string;
workingDir?: string;
}
// Map to store active GDB sessions
const activeSessions = new Map<string, GdbSession>();
class GdbServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: 'mcp-gdb-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
// Clean up all active GDB sessions
for (const [id, session] of activeSessions.entries()) {
await this.terminateGdbSession(id);
}
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'gdb_start',
description: 'Start a new GDB session',
inputSchema: {
type: 'object',
properties: {
gdbPath: {
type: 'string',
description: 'Path to the GDB executable (optional, defaults to "gdb")'
},
workingDir: {
type: 'string',
description: 'Working directory for GDB (optional)'
}
}
}
},
{
name: 'gdb_load',
description: 'Load a program into GDB',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
program: {
type: 'string',
description: 'Path to the program to debug'
},
arguments: {
type: 'array',
items: {
type: 'string'
},
description: 'Command-line arguments for the program (optional)'
}
},
required: ['sessionId', 'program']
}
},
{
name: 'gdb_command',
description: 'Execute a GDB command',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
command: {
type: 'string',
description: 'GDB command to execute'
}
},
required: ['sessionId', 'command']
}
},
{
name: 'gdb_terminate',
description: 'Terminate a GDB session',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
}
},
required: ['sessionId']
}
},
{
name: 'gdb_list_sessions',
description: 'List all active GDB sessions',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'gdb_attach',
description: 'Attach to a running process',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
pid: {
type: 'number',
description: 'Process ID to attach to'
}
},
required: ['sessionId', 'pid']
}
},
{
name: 'gdb_load_core',
description: 'Load a core dump file',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
program: {
type: 'string',
description: 'Path to the program executable'
},
corePath: {
type: 'string',
description: 'Path to the core dump file'
}
},
required: ['sessionId', 'program', 'corePath']
}
},
{
name: 'gdb_set_breakpoint',
description: 'Set a breakpoint',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
location: {
type: 'string',
description: 'Breakpoint location (e.g., function name, file:line)'
},
condition: {
type: 'string',
description: 'Breakpoint condition (optional)'
}
},
required: ['sessionId', 'location']
}
},
{
name: 'gdb_continue',
description: 'Continue program execution',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
}
},
required: ['sessionId']
}
},
{
name: 'gdb_step',
description: 'Step program execution',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
instructions: {
type: 'boolean',
description: 'Step by instructions instead of source lines (optional)'
}
},
required: ['sessionId']
}
},
{
name: 'gdb_next',
description: 'Step over function calls',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
instructions: {
type: 'boolean',
description: 'Step by instructions instead of source lines (optional)'
}
},
required: ['sessionId']
}
},
{
name: 'gdb_finish',
description: 'Execute until the current function returns',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
}
},
required: ['sessionId']
}
},
{
name: 'gdb_backtrace',
description: 'Show call stack',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
full: {
type: 'boolean',
description: 'Show variables in each frame (optional)'
},
limit: {
type: 'number',
description: 'Maximum number of frames to show (optional)'
}
},
required: ['sessionId']
}
},
{
name: 'gdb_print',
description: 'Print value of expression',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
expression: {
type: 'string',
description: 'Expression to evaluate'
}
},
required: ['sessionId', 'expression']
}
},
{
name: 'gdb_examine',
description: 'Examine memory',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
expression: {
type: 'string',
description: 'Memory address or expression'
},
format: {
type: 'string',
description: 'Display format (e.g., "x" for hex, "i" for instruction)'
},
count: {
type: 'number',
description: 'Number of units to display'
}
},
required: ['sessionId', 'expression']
}
},
{
name: 'gdb_info_registers',
description: 'Display registers',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'GDB session ID'
},
register: {
type: 'string',
description: 'Specific register to display (optional)'
}
},
required: ['sessionId']
}
}
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
// Route the tool call to the appropriate handler based on the tool name
switch (request.params.name) {
case 'gdb_start':
return await this.handleGdbStart(request.params.arguments);
case 'gdb_load':
return await this.handleGdbLoad(request.params.arguments);
case 'gdb_command':
return await this.handleGdbCommand(request.params.arguments);
case 'gdb_terminate':
return await this.handleGdbTerminate(request.params.arguments);
case 'gdb_list_sessions':
return await this.handleGdbListSessions();
case 'gdb_attach':
return await this.handleGdbAttach(request.params.arguments);
case 'gdb_load_core':
return await this.handleGdbLoadCore(request.params.arguments);
case 'gdb_set_breakpoint':
return await this.handleGdbSetBreakpoint(request.params.arguments);
case 'gdb_continue':
return await this.handleGdbContinue(request.params.arguments);
case 'gdb_step':
return await this.handleGdbStep(request.params.arguments);
case 'gdb_next':
return await this.handleGdbNext(request.params.arguments);
case 'gdb_finish':
return await this.handleGdbFinish(request.params.arguments);
case 'gdb_backtrace':
return await this.handleGdbBacktrace(request.params.arguments);
case 'gdb_print':
return await this.handleGdbPrint(request.params.arguments);
case 'gdb_examine':
return await this.handleGdbExamine(request.params.arguments);
case 'gdb_info_registers':
return await this.handleGdbInfoRegisters(request.params.arguments);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
private async handleGdbStart(args: any) {
const gdbPath = args.gdbPath || 'gdb';
const workingDir = args.workingDir || process.cwd();
// Create a unique session ID
const sessionId = Date.now().toString();
try {
// Start GDB process with MI mode enabled for machine interface
const gdbProcess = spawn(gdbPath, ['--interpreter=mi'], {
cwd: workingDir,
env: process.env,
stdio: ['pipe', 'pipe', 'pipe']
});
// Create readline interface for reading GDB output
const rl = readline.createInterface({
input: gdbProcess.stdout,
terminal: false
});
// Create new GDB session
const session: GdbSession = {
process: gdbProcess,
rl,
ready: false,
id: sessionId,
workingDir
};
// Store session in active sessions map
activeSessions.set(sessionId, session);
// Collect GDB output until ready
let outputBuffer = '';
// Wait for GDB to be ready (when it outputs the initial prompt)
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('GDB start timeout'));
}, 10000); // 10 second timeout
rl.on('line', (line) => {
// Append line to output buffer
outputBuffer += line + '\n';
// Check if GDB is ready (outputs prompt)
if (line.includes('(gdb)') || line.includes('^done')) {
clearTimeout(timeout);
session.ready = true;
resolve();
}
});
gdbProcess.stderr.on('data', (data) => {
outputBuffer += `[stderr] ${data.toString()}\n`;
});
gdbProcess.on('error', (err) => {
clearTimeout(timeout);
reject(err);
});
gdbProcess.on('exit', (code) => {
clearTimeout(timeout);
if (!session.ready) {
reject(new Error(`GDB process exited with code ${code}`));
}
});
});
return {
content: [
{
type: 'text',
text: `GDB session started with ID: ${sessionId}\n\nOutput:\n${outputBuffer}`
}
]
};
} catch (error) {
// Clean up if an error occurs
if (activeSessions.has(sessionId)) {
const session = activeSessions.get(sessionId)!;
session.process.kill();
session.rl.close();
activeSessions.delete(sessionId);
}
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to start GDB: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbLoad(args: any) {
const { sessionId, program, arguments: programArgs = [] } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Normalize path if working directory is set
const normalizedPath = session.workingDir && !path.isAbsolute(program)
? path.resolve(session.workingDir, program)
: program;
// Update session target
session.target = normalizedPath;
// Execute file command to load program
const loadCommand = `file "${normalizedPath}"`;
const loadOutput = await this.executeGdbCommand(session, loadCommand);
// Set program arguments if provided
let argsOutput = '';
if (programArgs.length > 0) {
const argsCommand = `set args ${programArgs.join(' ')}`;
argsOutput = await this.executeGdbCommand(session, argsCommand);
}
return {
content: [
{
type: 'text',
text: `Program loaded: ${normalizedPath}\n\nOutput:\n${loadOutput}${argsOutput ? '\n' + argsOutput : ''}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to load program: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbCommand(args: any) {
const { sessionId, command } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
const output = await this.executeGdbCommand(session, command);
return {
content: [
{
type: 'text',
text: `Command: ${command}\n\nOutput:\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to execute command: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbTerminate(args: any) {
const { sessionId } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
try {
await this.terminateGdbSession(sessionId);
return {
content: [
{
type: 'text',
text: `GDB session terminated: ${sessionId}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to terminate GDB session: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbListSessions() {
const sessions = Array.from(activeSessions.entries()).map(([id, session]) => ({
id,
target: session.target || 'No program loaded',
workingDir: session.workingDir || process.cwd()
}));
return {
content: [
{
type: 'text',
text: `Active GDB Sessions (${sessions.length}):\n\n${JSON.stringify(sessions, null, 2)}`
}
]
};
}
private async handleGdbAttach(args: any) {
const { sessionId, pid } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
const output = await this.executeGdbCommand(session, `attach ${pid}`);
return {
content: [
{
type: 'text',
text: `Attached to process ${pid}\n\nOutput:\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to attach to process: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbLoadCore(args: any) {
const { sessionId, program, corePath } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// First load the program
const fileOutput = await this.executeGdbCommand(session, `file "${program}"`);
// Then load the core file
const coreOutput = await this.executeGdbCommand(session, `core-file "${corePath}"`);
// Get backtrace to show initial state
const backtraceOutput = await this.executeGdbCommand(session, "backtrace");
return {
content: [
{
type: 'text',
text: `Core file loaded: ${corePath}\n\nOutput:\n${fileOutput}\n${coreOutput}\n\nBacktrace:\n${backtraceOutput}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to load core file: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbSetBreakpoint(args: any) {
const { sessionId, location, condition } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Set breakpoint
let command = `break ${location}`;
const output = await this.executeGdbCommand(session, command);
// Set condition if provided
let conditionOutput = '';
if (condition) {
// Extract breakpoint number from output (assumes format like "Breakpoint 1 at...")
const match = output.match(/Breakpoint (\d+)/);
if (match && match[1]) {
const bpNum = match[1];
const conditionCommand = `condition ${bpNum} ${condition}`;
conditionOutput = await this.executeGdbCommand(session, conditionCommand);
}
}
return {
content: [
{
type: 'text',
text: `Breakpoint set at: ${location}${condition ? ` with condition: ${condition}` : ''}\n\nOutput:\n${output}${conditionOutput ? '\n' + conditionOutput : ''}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to set breakpoint: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbContinue(args: any) {
const { sessionId } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
const output = await this.executeGdbCommand(session, "continue");
return {
content: [
{
type: 'text',
text: `Continued execution\n\nOutput:\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to continue execution: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbStep(args: any) {
const { sessionId, instructions = false } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Use stepi for instruction-level stepping, otherwise step
const command = instructions ? "stepi" : "step";
const output = await this.executeGdbCommand(session, command);
return {
content: [
{
type: 'text',
text: `Stepped ${instructions ? 'instruction' : 'line'}\n\nOutput:\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to step: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbNext(args: any) {
const { sessionId, instructions = false } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Use nexti for instruction-level stepping, otherwise next
const command = instructions ? "nexti" : "next";
const output = await this.executeGdbCommand(session, command);
return {
content: [
{
type: 'text',
text: `Stepped over ${instructions ? 'instruction' : 'function call'}\n\nOutput:\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to step over: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbFinish(args: any) {
const { sessionId } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
const output = await this.executeGdbCommand(session, "finish");
return {
content: [
{
type: 'text',
text: `Finished current function\n\nOutput:\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to finish function: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbBacktrace(args: any) {
const { sessionId, full = false, limit } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Build backtrace command with options
let command = full ? "backtrace full" : "backtrace";
if (typeof limit === 'number') {
command += ` ${limit}`;
}
const output = await this.executeGdbCommand(session, command);
return {
content: [
{
type: 'text',
text: `Backtrace${full ? ' (full)' : ''}${limit ? ` (limit: ${limit})` : ''}:\n\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to get backtrace: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbPrint(args: any) {
const { sessionId, expression } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
const output = await this.executeGdbCommand(session, `print ${expression}`);
return {
content: [
{
type: 'text',
text: `Print ${expression}:\n\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to print expression: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbExamine(args: any) {
const { sessionId, expression, format = 'x', count = 1 } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Format examine command: x/[count][format] [expression]
const command = `x/${count}${format} ${expression}`;
const output = await this.executeGdbCommand(session, command);
return {
content: [
{
type: 'text',
text: `Examine ${expression} (format: ${format}, count: ${count}):\n\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to examine memory: ${errorMessage}`
}
],
isError: true
};
}
}
private async handleGdbInfoRegisters(args: any) {
const { sessionId, register } = args;
if (!activeSessions.has(sessionId)) {
return {
content: [
{
type: 'text',
text: `No active GDB session with ID: ${sessionId}`
}
],
isError: true
};
}
const session = activeSessions.get(sessionId)!;
try {
// Build info registers command, optionally with specific register
const command = register ? `info registers ${register}` : `info registers`;
const output = await this.executeGdbCommand(session, command);
return {
content: [
{
type: 'text',
text: `Register info${register ? ` for ${register}` : ''}:\n\n${output}`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Failed to get register info: ${errorMessage}`
}
],
isError: true
};
}
}
/**
* Execute a GDB command and wait for the response
*/
private executeGdbCommand(session: GdbSession, command: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (!session.ready) {
reject(new Error('GDB session is not ready'));
return;
}
// Write command to GDB's stdin
if (session.process.stdin) {
session.process.stdin.write(command + '\n');
} else {
reject(new Error('GDB stdin is not available'));
return;
}
let output = '';
let responseComplete = false;
// Create a one-time event handler for GDB output
const onLine = (line: string) => {
output += line + '\n';
// Check if this line indicates the end of the GDB response
if (line.includes('(gdb)') || line.includes('^done') || line.includes('^error')) {
responseComplete = true;
// If we've received the complete response, resolve the promise
if (responseComplete) {
// Remove the listener to avoid memory leaks
session.rl.removeListener('line', onLine);
resolve(output);
}
}
};
// Add the line handler to the readline interface
session.rl.on('line', onLine);
// Set a timeout to prevent hanging
const timeout = setTimeout(() => {
session.rl.removeListener('line', onLine);
reject(new Error('GDB command timed out'));
}, 10000); // 10 second timeout
// Handle GDB errors
const errorHandler = (data: Buffer) => {
const errorText = data.toString();
output += `[stderr] ${errorText}\n`;
};
// Add error handler
if (session.process.stderr) {
session.process.stderr.once('data', errorHandler);
}
// Clean up event handlers when the timeout expires
timeout.unref();
});
}
/**
* Terminate a GDB session
*/
private async terminateGdbSession(sessionId: string): Promise<void> {
if (!activeSessions.has(sessionId)) {
throw new Error(`No active GDB session with ID: ${sessionId}`);
}
const session = activeSessions.get(sessionId)!;
// Send quit command to GDB
try {
await this.executeGdbCommand(session, 'quit');
} catch (error) {
// Ignore errors from quit command, we'll force kill if needed
}
// Force kill the process if it's still running
if (!session.process.killed) {
session.process.kill();
}
// Close the readline interface
session.rl.close();
// Remove from active sessions
activeSessions.delete(sessionId);
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('GDB MCP server running on stdio');
}
}
// Create and run the server
const server = new GdbServer();
server.run().catch((error) => {
console.error('Failed to start GDB MCP server:', error);
process.exit(1);
});
```