# Directory Structure
```
├── examples
│ ├── crash.c
│ └── USAGE.md
├── install.sh
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP GDB Server
2 |
3 | A Model Context Protocol (MCP) server that provides GDB debugging functionality for use with Claude or other AI assistants.
4 |
5 | ## Features
6 |
7 | - Start and manage GDB debugging sessions
8 | - Load programs and core dumps for analysis
9 | - Set breakpoints, step through code, and examine memory
10 | - View call stacks, variables, and registers
11 | - Execute arbitrary GDB commands
12 |
13 | ## Installation
14 |
15 | ```bash
16 | # Clone the repository
17 | git clone https://github.com/signal-slot/mcp-gdb.git
18 | cd mcp-gdb
19 |
20 | # Install dependencies
21 | npm install
22 |
23 | # Build the project
24 | npm run build
25 | ```
26 |
27 | ## Usage
28 |
29 | ### Using with Claude or other MCP-enabled assistants
30 |
31 | 1. Configure the MCP settings in the Claude desktop app or browser extension to include this server:
32 |
33 | ```json
34 | {
35 | "mcpServers": {
36 | "gdb": {
37 | "command": "node",
38 | "args": ["/path/to/mcp-gdb/build/index.js"],
39 | "disabled": false
40 | }
41 | }
42 | }
43 | ```
44 |
45 | 2. Restart Claude or refresh the page.
46 |
47 | 3. Now you can use the GDB tools in your conversations with Claude.
48 |
49 | ### Example Commands
50 |
51 | Here are some examples of using the GDB MCP server through Claude:
52 |
53 | #### Starting a GDB session
54 | ```
55 | Use gdb_start to start a new debugging session
56 | ```
57 |
58 | #### Loading a program
59 | ```
60 | Use gdb_load to load /path/to/my/program with the sessionId that was returned from gdb_start
61 | ```
62 |
63 | #### Setting a breakpoint
64 | ```
65 | Use gdb_set_breakpoint to set a breakpoint at main in the active GDB session
66 | ```
67 |
68 | #### Running the program
69 | ```
70 | Use gdb_continue to start execution
71 | ```
72 |
73 | #### Examining variables
74 | ```
75 | Use gdb_print to evaluate the expression "my_variable" in the current context
76 | ```
77 |
78 | #### Getting a backtrace
79 | ```
80 | Use gdb_backtrace to see the current call stack
81 | ```
82 |
83 | #### Terminating the session
84 | ```
85 | Use gdb_terminate to end the debugging session
86 | ```
87 |
88 | ## Supported GDB Commands
89 |
90 | - `gdb_start`: Start a new GDB session
91 | - `gdb_load`: Load a program into GDB
92 | - `gdb_command`: Execute an arbitrary GDB command
93 | - `gdb_terminate`: Terminate a GDB session
94 | - `gdb_list_sessions`: List all active GDB sessions
95 | - `gdb_attach`: Attach to a running process
96 | - `gdb_load_core`: Load a core dump file
97 | - `gdb_set_breakpoint`: Set a breakpoint
98 | - `gdb_continue`: Continue program execution
99 | - `gdb_step`: Step program execution
100 | - `gdb_next`: Step over function calls
101 | - `gdb_finish`: Execute until the current function returns
102 | - `gdb_backtrace`: Show call stack
103 | - `gdb_print`: Print value of expression
104 | - `gdb_examine`: Examine memory
105 | - `gdb_info_registers`: Display registers
106 |
107 | ## License
108 |
109 | MIT
110 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "build",
11 | "declaration": true,
12 | "sourceMap": true,
13 | "resolveJsonModule": true,
14 | "noImplicitAny": false,
15 | "allowSyntheticDefaultImports": true
16 | },
17 | "include": ["src/**/*"],
18 | "exclude": ["node_modules", "build"]
19 | }
20 |
```
--------------------------------------------------------------------------------
/examples/crash.c:
--------------------------------------------------------------------------------
```cpp
1 | #include <stdio.h>
2 | #include <stdlib.h>
3 |
4 | void function_that_crashes() {
5 | // Access a null pointer to cause a segmentation fault
6 | int *ptr = NULL;
7 | *ptr = 42; // This will cause a crash
8 | }
9 |
10 | void function_with_args(int a, int b) {
11 | printf("Arguments: a=%d, b=%d\n", a, b);
12 | if (a > 10) {
13 | function_that_crashes();
14 | }
15 | }
16 |
17 | int main(int argc, char **argv) {
18 | printf("Starting the program...\n");
19 |
20 | // Print command line arguments
21 | printf("Got %d arguments\n", argc);
22 | for (int i = 0; i < argc; i++) {
23 | printf("Argument %d: %s\n", i, argv[i]);
24 | }
25 |
26 | int number = 5;
27 |
28 | if (argc > 1) {
29 | number = atoi(argv[1]);
30 | }
31 |
32 | printf("Working with number: %d\n", number);
33 |
34 | function_with_args(number, number * 2);
35 |
36 | printf("Program completed successfully\n");
37 | return 0;
38 | }
39 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-gdb",
3 | "version": "0.1.1",
4 | "description": "MCP server for GDB debugging functionality",
5 | "type": "module",
6 | "main": "build/index.js",
7 | "scripts": {
8 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
9 | "start": "node build/index.js",
10 | "dev": "tsc-watch --onSuccess \"node build/index.js\"",
11 | "lint": "eslint src/**/*.ts",
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "keywords": [
15 | "gdb",
16 | "debug",
17 | "mcp"
18 | ],
19 | "author": "Tasuku Suzuki",
20 | "license": "MIT",
21 | "devDependencies": {
22 | "@types/node": "^20.10.5",
23 | "@typescript-eslint/eslint-plugin": "^6.15.0",
24 | "@typescript-eslint/parser": "^6.15.0",
25 | "eslint": "^8.56.0",
26 | "tsc-watch": "^6.0.4",
27 | "typescript": "^5.3.3"
28 | },
29 | "dependencies": {
30 | "@modelcontextprotocol/sdk": "^1.7.0"
31 | },
32 | "directories": {
33 | "example": "examples"
34 | },
35 | "repository": {
36 | "type": "git",
37 | "url": "git+https://github.com/signal-slot/mcp-gdb.git"
38 | },
39 | "types": "./build/index.d.ts",
40 | "bugs": {
41 | "url": "https://github.com/signal-slot/mcp-gdb/issues"
42 | },
43 | "homepage": "https://github.com/signal-slot/mcp-gdb#readme"
44 | }
45 |
```
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 |
3 | # MCP GDB Server Installer Script
4 |
5 | set -e
6 |
7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
8 | BUILD_DIR="$SCRIPT_DIR/build"
9 | GDB_PATH=$(which gdb 2>/dev/null || echo "")
10 |
11 | echo "MCP GDB Server Installer"
12 | echo "========================"
13 | echo
14 |
15 | # Check if GDB is installed
16 | if [ -z "$GDB_PATH" ]; then
17 | echo "⚠️ Warning: GDB is not found in your PATH. You need to install GDB before using this MCP server."
18 | echo "On Ubuntu/Debian: sudo apt-get install gdb"
19 | echo "On Fedora/RHEL: sudo dnf install gdb"
20 | echo "On Arch Linux: sudo pacman -S gdb"
21 | echo "On macOS: brew install gdb"
22 | echo
23 | read -p "Do you want to continue with the installation? (y/n) " -n 1 -r
24 | echo
25 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then
26 | exit 1
27 | fi
28 | fi
29 |
30 | # Install dependencies
31 | echo "Installing dependencies..."
32 | npm install
33 |
34 | # Build the project
35 | echo "Building the MCP GDB server..."
36 | npm run build
37 |
38 | # Get the absolute path to the build directory
39 | ABSOLUTE_PATH=$(cd "$BUILD_DIR" && pwd)
40 |
41 | # Generate MCP settings configuration snippet
42 | echo
43 | echo "MCP GDB Server has been built successfully!"
44 | echo
45 | echo "To use it with Claude or other MCP-enabled assistants, add the following to your MCP settings configuration:"
46 | echo
47 | echo "{"
48 | echo " \"mcpServers\": {"
49 | echo " \"gdb\": {"
50 | echo " \"command\": \"node\","
51 | echo " \"args\": [\"$ABSOLUTE_PATH/index.js\"],"
52 | echo " \"disabled\": false"
53 | echo " }"
54 | echo " }"
55 | echo "}"
56 | echo
57 | echo "For Claude Desktop, this file is typically located at:"
58 | echo "- Linux: ~/.config/Claude/claude_desktop_config.json"
59 | echo "- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json"
60 | echo "- Windows: %APPDATA%\\Claude\\claude_desktop_config.json"
61 | echo
62 | echo "For Claude in VSCode, this file is typically located at:"
63 | echo "- Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
64 | echo "- macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
65 | echo "- Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json"
66 | echo
67 | echo "After adding the configuration, restart Claude or reload the extension for the changes to take effect."
68 | echo
69 | echo "To test the MCP GDB server, compile and debug the example program:"
70 | echo " cd examples"
71 | echo " gcc -g crash.c -o crash"
72 | echo
73 | echo "Then follow the instructions in examples/USAGE.md to debug the program using Claude."
74 | echo
75 |
76 | # Create example directory and compile the example file
77 | if [ -d "$SCRIPT_DIR/examples" ]; then
78 | echo "Would you like to compile the example program now? (y/n) "
79 | read -n 1 -r
80 | echo
81 | if [[ $REPLY =~ ^[Yy]$ ]]; then
82 | cd "$SCRIPT_DIR/examples"
83 | echo "Compiling example program..."
84 | gcc -g crash.c -o crash
85 | echo "Example program compiled successfully!"
86 | fi
87 | fi
88 |
89 | echo "Installation complete!"
90 |
```
--------------------------------------------------------------------------------
/examples/USAGE.md:
--------------------------------------------------------------------------------
```markdown
1 | # Using the MCP GDB Server: Example Usage
2 |
3 | This guide demonstrates how to use the MCP GDB server to debug the included `crash.c` example.
4 |
5 | ## Compiling the Example
6 |
7 | First, compile the example with debug symbols:
8 |
9 | ```bash
10 | cd examples
11 | gcc -g crash.c -o crash
12 | ```
13 |
14 | ## Debugging Session Example
15 |
16 | Here's a step-by-step guide showing how to debug the crash.c program using the MCP GDB server through Claude.
17 |
18 | ### 1. Start a GDB Session
19 |
20 | Ask Claude to start a new GDB session:
21 |
22 | ```
23 | I need to debug a program that's crashing. Can you start a new GDB session for me?
24 | ```
25 |
26 | Claude will use the `gdb_start` tool:
27 |
28 | ```
29 | <use_mcp_tool>
30 | <server_name>gdb</server_name>
31 | <tool_name>gdb_start</tool_name>
32 | <arguments>
33 | {}
34 | </arguments>
35 | </use_mcp_tool>
36 | ```
37 |
38 | Claude will receive a response with a session ID (e.g., "1616161616161").
39 |
40 | ### 2. Load the Program
41 |
42 | Now, ask Claude to load the program:
43 |
44 | ```
45 | Can you load the crash executable I compiled in the examples directory?
46 | ```
47 |
48 | Claude will use the `gdb_load` tool:
49 |
50 | ```
51 | <use_mcp_tool>
52 | <server_name>gdb</server_name>
53 | <tool_name>gdb_load</tool_name>
54 | <arguments>
55 | {
56 | "sessionId": "1616161616161",
57 | "program": "/path/to/mcp-gdb/examples/crash"
58 | }
59 | </arguments>
60 | </use_mcp_tool>
61 | ```
62 |
63 | ### 3. Set Breakpoints
64 |
65 | Set a breakpoint at the main function:
66 |
67 | ```
68 | Please set a breakpoint at the main function.
69 | ```
70 |
71 | Claude will use `gdb_set_breakpoint`:
72 |
73 | ```
74 | <use_mcp_tool>
75 | <server_name>gdb</server_name>
76 | <tool_name>gdb_set_breakpoint</tool_name>
77 | <arguments>
78 | {
79 | "sessionId": "1616161616161",
80 | "location": "main"
81 | }
82 | </arguments>
83 | </use_mcp_tool>
84 | ```
85 |
86 | Let's also set a breakpoint at the function that might crash:
87 |
88 | ```
89 | Can you also set a breakpoint at function_with_args?
90 | ```
91 |
92 | Claude will set another breakpoint:
93 |
94 | ```
95 | <use_mcp_tool>
96 | <server_name>gdb</server_name>
97 | <tool_name>gdb_set_breakpoint</tool_name>
98 | <arguments>
99 | {
100 | "sessionId": "1616161616161",
101 | "location": "function_with_args"
102 | }
103 | </arguments>
104 | </use_mcp_tool>
105 | ```
106 |
107 | ### 4. Start the Program
108 |
109 | Start the program with an argument that will cause a crash (any number > 10):
110 |
111 | ```
112 | Please run the program with an argument of 15, which should trigger the crash.
113 | ```
114 |
115 | Claude will use `gdb_command` to set the arguments and then run the program:
116 |
117 | ```
118 | <use_mcp_tool>
119 | <server_name>gdb</server_name>
120 | <tool_name>gdb_command</tool_name>
121 | <arguments>
122 | {
123 | "sessionId": "1616161616161",
124 | "command": "set args 15"
125 | }
126 | </arguments>
127 | </use_mcp_tool>
128 | ```
129 |
130 | Then Claude will start the program:
131 |
132 | ```
133 | <use_mcp_tool>
134 | <server_name>gdb</server_name>
135 | <tool_name>gdb_continue</tool_name>
136 | <arguments>
137 | {
138 | "sessionId": "1616161616161"
139 | }
140 | </arguments>
141 | </use_mcp_tool>
142 | ```
143 |
144 | The program will hit the first breakpoint at `main`.
145 |
146 | ### 5. Step Through the Program
147 |
148 | Ask Claude to step through the execution:
149 |
150 | ```
151 | Please step through the program execution so we can see what's happening.
152 | ```
153 |
154 | Claude will use `gdb_next` to step through the code:
155 |
156 | ```
157 | <use_mcp_tool>
158 | <server_name>gdb</server_name>
159 | <tool_name>gdb_next</tool_name>
160 | <arguments>
161 | {
162 | "sessionId": "1616161616161"
163 | }
164 | </arguments>
165 | </use_mcp_tool>
166 | ```
167 |
168 | Repeat this command to step through the program.
169 |
170 | ### 6. Examine Variables
171 |
172 | At any point, you can ask Claude to print the value of variables:
173 |
174 | ```
175 | What is the value of the 'number' variable?
176 | ```
177 |
178 | Claude will use `gdb_print`:
179 |
180 | ```
181 | <use_mcp_tool>
182 | <server_name>gdb</server_name>
183 | <tool_name>gdb_print</tool_name>
184 | <arguments>
185 | {
186 | "sessionId": "1616161616161",
187 | "expression": "number"
188 | }
189 | </arguments>
190 | </use_mcp_tool>
191 | ```
192 |
193 | ### 7. Continue to Crash
194 |
195 | Continue execution until the program crashes:
196 |
197 | ```
198 | Please continue execution until we hit the crash.
199 | ```
200 |
201 | Claude will use `gdb_continue`:
202 |
203 | ```
204 | <use_mcp_tool>
205 | <server_name>gdb</server_name>
206 | <tool_name>gdb_continue</tool_name>
207 | <arguments>
208 | {
209 | "sessionId": "1616161616161"
210 | }
211 | </arguments>
212 | </use_mcp_tool>
213 | ```
214 |
215 | The program will hit the second breakpoint and then crash when trying to dereference a NULL pointer.
216 |
217 | ### 8. Analyze the Crash
218 |
219 | When the program crashes, examine the backtrace to see where it occurred:
220 |
221 | ```
222 | Can you show me the backtrace of the crash?
223 | ```
224 |
225 | Claude will use `gdb_backtrace`:
226 |
227 | ```
228 | <use_mcp_tool>
229 | <server_name>gdb</server_name>
230 | <tool_name>gdb_backtrace</tool_name>
231 | <arguments>
232 | {
233 | "sessionId": "1616161616161"
234 | }
235 | </arguments>
236 | </use_mcp_tool>
237 | ```
238 |
239 | The backtrace will show that the crash occurred in `function_that_crashes` when trying to dereference a NULL pointer.
240 |
241 | ### 9. Terminate the Session
242 |
243 | When you're done debugging, terminate the GDB session:
244 |
245 | ```
246 | Please terminate the GDB session.
247 | ```
248 |
249 | Claude will use `gdb_terminate`:
250 |
251 | ```
252 | <use_mcp_tool>
253 | <server_name>gdb</server_name>
254 | <tool_name>gdb_terminate</tool_name>
255 | <arguments>
256 | {
257 | "sessionId": "1616161616161"
258 | }
259 | </arguments>
260 | </use_mcp_tool>
261 | ```
262 |
263 | ## Using with Core Dumps
264 |
265 | If the program has already crashed and generated a core dump, you can load it for analysis:
266 |
267 | ```
268 | <use_mcp_tool>
269 | <server_name>gdb</server_name>
270 | <tool_name>gdb_start</tool_name>
271 | <arguments>
272 | {}
273 | </arguments>
274 | </use_mcp_tool>
275 | ```
276 |
277 | Then load the program and core dump:
278 |
279 | ```
280 | <use_mcp_tool>
281 | <server_name>gdb</server_name>
282 | <tool_name>gdb_load_core</tool_name>
283 | <arguments>
284 | {
285 | "sessionId": "1616161616161",
286 | "program": "/path/to/mcp-gdb/examples/crash",
287 | "corePath": "/path/to/core.dump"
288 | }
289 | </arguments>
290 | </use_mcp_tool>
291 | ```
292 |
293 | Then you can analyze the crash just as if you had caught it in real-time.
294 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import {
5 | CallToolRequestSchema,
6 | ErrorCode,
7 | ListToolsRequestSchema,
8 | McpError
9 | } from '@modelcontextprotocol/sdk/types.js';
10 | import { spawn, ChildProcess } from 'child_process';
11 | import * as readline from 'readline';
12 | import * as fs from 'fs';
13 | import * as path from 'path';
14 |
15 | // Interface for GDB session
16 | interface GdbSession {
17 | process: ChildProcess;
18 | rl: readline.Interface;
19 | ready: boolean;
20 | id: string;
21 | target?: string;
22 | workingDir?: string;
23 | }
24 |
25 | // Map to store active GDB sessions
26 | const activeSessions = new Map<string, GdbSession>();
27 |
28 | class GdbServer {
29 | private server: Server;
30 |
31 | constructor() {
32 | this.server = new Server(
33 | {
34 | name: 'mcp-gdb-server',
35 | version: '0.1.0',
36 | },
37 | {
38 | capabilities: {
39 | tools: {},
40 | },
41 | }
42 | );
43 |
44 | this.setupToolHandlers();
45 |
46 | // Error handling
47 | this.server.onerror = (error) => console.error('[MCP Error]', error);
48 | process.on('SIGINT', async () => {
49 | // Clean up all active GDB sessions
50 | for (const [id, session] of activeSessions.entries()) {
51 | await this.terminateGdbSession(id);
52 | }
53 | await this.server.close();
54 | process.exit(0);
55 | });
56 | }
57 |
58 | private setupToolHandlers() {
59 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
60 | tools: [
61 | {
62 | name: 'gdb_start',
63 | description: 'Start a new GDB session',
64 | inputSchema: {
65 | type: 'object',
66 | properties: {
67 | gdbPath: {
68 | type: 'string',
69 | description: 'Path to the GDB executable (optional, defaults to "gdb")'
70 | },
71 | workingDir: {
72 | type: 'string',
73 | description: 'Working directory for GDB (optional)'
74 | }
75 | }
76 | }
77 | },
78 | {
79 | name: 'gdb_load',
80 | description: 'Load a program into GDB',
81 | inputSchema: {
82 | type: 'object',
83 | properties: {
84 | sessionId: {
85 | type: 'string',
86 | description: 'GDB session ID'
87 | },
88 | program: {
89 | type: 'string',
90 | description: 'Path to the program to debug'
91 | },
92 | arguments: {
93 | type: 'array',
94 | items: {
95 | type: 'string'
96 | },
97 | description: 'Command-line arguments for the program (optional)'
98 | }
99 | },
100 | required: ['sessionId', 'program']
101 | }
102 | },
103 | {
104 | name: 'gdb_command',
105 | description: 'Execute a GDB command',
106 | inputSchema: {
107 | type: 'object',
108 | properties: {
109 | sessionId: {
110 | type: 'string',
111 | description: 'GDB session ID'
112 | },
113 | command: {
114 | type: 'string',
115 | description: 'GDB command to execute'
116 | }
117 | },
118 | required: ['sessionId', 'command']
119 | }
120 | },
121 | {
122 | name: 'gdb_terminate',
123 | description: 'Terminate a GDB session',
124 | inputSchema: {
125 | type: 'object',
126 | properties: {
127 | sessionId: {
128 | type: 'string',
129 | description: 'GDB session ID'
130 | }
131 | },
132 | required: ['sessionId']
133 | }
134 | },
135 | {
136 | name: 'gdb_list_sessions',
137 | description: 'List all active GDB sessions',
138 | inputSchema: {
139 | type: 'object',
140 | properties: {}
141 | }
142 | },
143 | {
144 | name: 'gdb_attach',
145 | description: 'Attach to a running process',
146 | inputSchema: {
147 | type: 'object',
148 | properties: {
149 | sessionId: {
150 | type: 'string',
151 | description: 'GDB session ID'
152 | },
153 | pid: {
154 | type: 'number',
155 | description: 'Process ID to attach to'
156 | }
157 | },
158 | required: ['sessionId', 'pid']
159 | }
160 | },
161 | {
162 | name: 'gdb_load_core',
163 | description: 'Load a core dump file',
164 | inputSchema: {
165 | type: 'object',
166 | properties: {
167 | sessionId: {
168 | type: 'string',
169 | description: 'GDB session ID'
170 | },
171 | program: {
172 | type: 'string',
173 | description: 'Path to the program executable'
174 | },
175 | corePath: {
176 | type: 'string',
177 | description: 'Path to the core dump file'
178 | }
179 | },
180 | required: ['sessionId', 'program', 'corePath']
181 | }
182 | },
183 | {
184 | name: 'gdb_set_breakpoint',
185 | description: 'Set a breakpoint',
186 | inputSchema: {
187 | type: 'object',
188 | properties: {
189 | sessionId: {
190 | type: 'string',
191 | description: 'GDB session ID'
192 | },
193 | location: {
194 | type: 'string',
195 | description: 'Breakpoint location (e.g., function name, file:line)'
196 | },
197 | condition: {
198 | type: 'string',
199 | description: 'Breakpoint condition (optional)'
200 | }
201 | },
202 | required: ['sessionId', 'location']
203 | }
204 | },
205 | {
206 | name: 'gdb_continue',
207 | description: 'Continue program execution',
208 | inputSchema: {
209 | type: 'object',
210 | properties: {
211 | sessionId: {
212 | type: 'string',
213 | description: 'GDB session ID'
214 | }
215 | },
216 | required: ['sessionId']
217 | }
218 | },
219 | {
220 | name: 'gdb_step',
221 | description: 'Step program execution',
222 | inputSchema: {
223 | type: 'object',
224 | properties: {
225 | sessionId: {
226 | type: 'string',
227 | description: 'GDB session ID'
228 | },
229 | instructions: {
230 | type: 'boolean',
231 | description: 'Step by instructions instead of source lines (optional)'
232 | }
233 | },
234 | required: ['sessionId']
235 | }
236 | },
237 | {
238 | name: 'gdb_next',
239 | description: 'Step over function calls',
240 | inputSchema: {
241 | type: 'object',
242 | properties: {
243 | sessionId: {
244 | type: 'string',
245 | description: 'GDB session ID'
246 | },
247 | instructions: {
248 | type: 'boolean',
249 | description: 'Step by instructions instead of source lines (optional)'
250 | }
251 | },
252 | required: ['sessionId']
253 | }
254 | },
255 | {
256 | name: 'gdb_finish',
257 | description: 'Execute until the current function returns',
258 | inputSchema: {
259 | type: 'object',
260 | properties: {
261 | sessionId: {
262 | type: 'string',
263 | description: 'GDB session ID'
264 | }
265 | },
266 | required: ['sessionId']
267 | }
268 | },
269 | {
270 | name: 'gdb_backtrace',
271 | description: 'Show call stack',
272 | inputSchema: {
273 | type: 'object',
274 | properties: {
275 | sessionId: {
276 | type: 'string',
277 | description: 'GDB session ID'
278 | },
279 | full: {
280 | type: 'boolean',
281 | description: 'Show variables in each frame (optional)'
282 | },
283 | limit: {
284 | type: 'number',
285 | description: 'Maximum number of frames to show (optional)'
286 | }
287 | },
288 | required: ['sessionId']
289 | }
290 | },
291 | {
292 | name: 'gdb_print',
293 | description: 'Print value of expression',
294 | inputSchema: {
295 | type: 'object',
296 | properties: {
297 | sessionId: {
298 | type: 'string',
299 | description: 'GDB session ID'
300 | },
301 | expression: {
302 | type: 'string',
303 | description: 'Expression to evaluate'
304 | }
305 | },
306 | required: ['sessionId', 'expression']
307 | }
308 | },
309 | {
310 | name: 'gdb_examine',
311 | description: 'Examine memory',
312 | inputSchema: {
313 | type: 'object',
314 | properties: {
315 | sessionId: {
316 | type: 'string',
317 | description: 'GDB session ID'
318 | },
319 | expression: {
320 | type: 'string',
321 | description: 'Memory address or expression'
322 | },
323 | format: {
324 | type: 'string',
325 | description: 'Display format (e.g., "x" for hex, "i" for instruction)'
326 | },
327 | count: {
328 | type: 'number',
329 | description: 'Number of units to display'
330 | }
331 | },
332 | required: ['sessionId', 'expression']
333 | }
334 | },
335 | {
336 | name: 'gdb_info_registers',
337 | description: 'Display registers',
338 | inputSchema: {
339 | type: 'object',
340 | properties: {
341 | sessionId: {
342 | type: 'string',
343 | description: 'GDB session ID'
344 | },
345 | register: {
346 | type: 'string',
347 | description: 'Specific register to display (optional)'
348 | }
349 | },
350 | required: ['sessionId']
351 | }
352 | }
353 | ],
354 | }));
355 |
356 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
357 | // Route the tool call to the appropriate handler based on the tool name
358 | switch (request.params.name) {
359 | case 'gdb_start':
360 | return await this.handleGdbStart(request.params.arguments);
361 | case 'gdb_load':
362 | return await this.handleGdbLoad(request.params.arguments);
363 | case 'gdb_command':
364 | return await this.handleGdbCommand(request.params.arguments);
365 | case 'gdb_terminate':
366 | return await this.handleGdbTerminate(request.params.arguments);
367 | case 'gdb_list_sessions':
368 | return await this.handleGdbListSessions();
369 | case 'gdb_attach':
370 | return await this.handleGdbAttach(request.params.arguments);
371 | case 'gdb_load_core':
372 | return await this.handleGdbLoadCore(request.params.arguments);
373 | case 'gdb_set_breakpoint':
374 | return await this.handleGdbSetBreakpoint(request.params.arguments);
375 | case 'gdb_continue':
376 | return await this.handleGdbContinue(request.params.arguments);
377 | case 'gdb_step':
378 | return await this.handleGdbStep(request.params.arguments);
379 | case 'gdb_next':
380 | return await this.handleGdbNext(request.params.arguments);
381 | case 'gdb_finish':
382 | return await this.handleGdbFinish(request.params.arguments);
383 | case 'gdb_backtrace':
384 | return await this.handleGdbBacktrace(request.params.arguments);
385 | case 'gdb_print':
386 | return await this.handleGdbPrint(request.params.arguments);
387 | case 'gdb_examine':
388 | return await this.handleGdbExamine(request.params.arguments);
389 | case 'gdb_info_registers':
390 | return await this.handleGdbInfoRegisters(request.params.arguments);
391 | default:
392 | throw new McpError(
393 | ErrorCode.MethodNotFound,
394 | `Unknown tool: ${request.params.name}`
395 | );
396 | }
397 | });
398 | }
399 |
400 | private async handleGdbStart(args: any) {
401 | const gdbPath = args.gdbPath || 'gdb';
402 | const workingDir = args.workingDir || process.cwd();
403 |
404 | // Create a unique session ID
405 | const sessionId = Date.now().toString();
406 |
407 | try {
408 | // Start GDB process with MI mode enabled for machine interface
409 | const gdbProcess = spawn(gdbPath, ['--interpreter=mi'], {
410 | cwd: workingDir,
411 | env: process.env,
412 | stdio: ['pipe', 'pipe', 'pipe']
413 | });
414 |
415 | // Create readline interface for reading GDB output
416 | const rl = readline.createInterface({
417 | input: gdbProcess.stdout,
418 | terminal: false
419 | });
420 |
421 | // Create new GDB session
422 | const session: GdbSession = {
423 | process: gdbProcess,
424 | rl,
425 | ready: false,
426 | id: sessionId,
427 | workingDir
428 | };
429 |
430 | // Store session in active sessions map
431 | activeSessions.set(sessionId, session);
432 |
433 | // Collect GDB output until ready
434 | let outputBuffer = '';
435 |
436 | // Wait for GDB to be ready (when it outputs the initial prompt)
437 | await new Promise<void>((resolve, reject) => {
438 | const timeout = setTimeout(() => {
439 | reject(new Error('GDB start timeout'));
440 | }, 10000); // 10 second timeout
441 |
442 | rl.on('line', (line) => {
443 | // Append line to output buffer
444 | outputBuffer += line + '\n';
445 |
446 | // Check if GDB is ready (outputs prompt)
447 | if (line.includes('(gdb)') || line.includes('^done')) {
448 | clearTimeout(timeout);
449 | session.ready = true;
450 | resolve();
451 | }
452 | });
453 |
454 | gdbProcess.stderr.on('data', (data) => {
455 | outputBuffer += `[stderr] ${data.toString()}\n`;
456 | });
457 |
458 | gdbProcess.on('error', (err) => {
459 | clearTimeout(timeout);
460 | reject(err);
461 | });
462 |
463 | gdbProcess.on('exit', (code) => {
464 | clearTimeout(timeout);
465 | if (!session.ready) {
466 | reject(new Error(`GDB process exited with code ${code}`));
467 | }
468 | });
469 | });
470 |
471 | return {
472 | content: [
473 | {
474 | type: 'text',
475 | text: `GDB session started with ID: ${sessionId}\n\nOutput:\n${outputBuffer}`
476 | }
477 | ]
478 | };
479 | } catch (error) {
480 | // Clean up if an error occurs
481 | if (activeSessions.has(sessionId)) {
482 | const session = activeSessions.get(sessionId)!;
483 | session.process.kill();
484 | session.rl.close();
485 | activeSessions.delete(sessionId);
486 | }
487 |
488 | const errorMessage = error instanceof Error ? error.message : String(error);
489 | return {
490 | content: [
491 | {
492 | type: 'text',
493 | text: `Failed to start GDB: ${errorMessage}`
494 | }
495 | ],
496 | isError: true
497 | };
498 | }
499 | }
500 |
501 | private async handleGdbLoad(args: any) {
502 | const { sessionId, program, arguments: programArgs = [] } = args;
503 |
504 | if (!activeSessions.has(sessionId)) {
505 | return {
506 | content: [
507 | {
508 | type: 'text',
509 | text: `No active GDB session with ID: ${sessionId}`
510 | }
511 | ],
512 | isError: true
513 | };
514 | }
515 |
516 | const session = activeSessions.get(sessionId)!;
517 |
518 | try {
519 | // Normalize path if working directory is set
520 | const normalizedPath = session.workingDir && !path.isAbsolute(program)
521 | ? path.resolve(session.workingDir, program)
522 | : program;
523 |
524 | // Update session target
525 | session.target = normalizedPath;
526 |
527 | // Execute file command to load program
528 | const loadCommand = `file "${normalizedPath}"`;
529 | const loadOutput = await this.executeGdbCommand(session, loadCommand);
530 |
531 | // Set program arguments if provided
532 | let argsOutput = '';
533 | if (programArgs.length > 0) {
534 | const argsCommand = `set args ${programArgs.join(' ')}`;
535 | argsOutput = await this.executeGdbCommand(session, argsCommand);
536 | }
537 |
538 | return {
539 | content: [
540 | {
541 | type: 'text',
542 | text: `Program loaded: ${normalizedPath}\n\nOutput:\n${loadOutput}${argsOutput ? '\n' + argsOutput : ''}`
543 | }
544 | ]
545 | };
546 | } catch (error) {
547 | const errorMessage = error instanceof Error ? error.message : String(error);
548 | return {
549 | content: [
550 | {
551 | type: 'text',
552 | text: `Failed to load program: ${errorMessage}`
553 | }
554 | ],
555 | isError: true
556 | };
557 | }
558 | }
559 |
560 | private async handleGdbCommand(args: any) {
561 | const { sessionId, command } = args;
562 |
563 | if (!activeSessions.has(sessionId)) {
564 | return {
565 | content: [
566 | {
567 | type: 'text',
568 | text: `No active GDB session with ID: ${sessionId}`
569 | }
570 | ],
571 | isError: true
572 | };
573 | }
574 |
575 | const session = activeSessions.get(sessionId)!;
576 |
577 | try {
578 | const output = await this.executeGdbCommand(session, command);
579 |
580 | return {
581 | content: [
582 | {
583 | type: 'text',
584 | text: `Command: ${command}\n\nOutput:\n${output}`
585 | }
586 | ]
587 | };
588 | } catch (error) {
589 | const errorMessage = error instanceof Error ? error.message : String(error);
590 | return {
591 | content: [
592 | {
593 | type: 'text',
594 | text: `Failed to execute command: ${errorMessage}`
595 | }
596 | ],
597 | isError: true
598 | };
599 | }
600 | }
601 |
602 | private async handleGdbTerminate(args: any) {
603 | const { sessionId } = args;
604 |
605 | if (!activeSessions.has(sessionId)) {
606 | return {
607 | content: [
608 | {
609 | type: 'text',
610 | text: `No active GDB session with ID: ${sessionId}`
611 | }
612 | ],
613 | isError: true
614 | };
615 | }
616 |
617 | try {
618 | await this.terminateGdbSession(sessionId);
619 |
620 | return {
621 | content: [
622 | {
623 | type: 'text',
624 | text: `GDB session terminated: ${sessionId}`
625 | }
626 | ]
627 | };
628 | } catch (error) {
629 | const errorMessage = error instanceof Error ? error.message : String(error);
630 | return {
631 | content: [
632 | {
633 | type: 'text',
634 | text: `Failed to terminate GDB session: ${errorMessage}`
635 | }
636 | ],
637 | isError: true
638 | };
639 | }
640 | }
641 |
642 | private async handleGdbListSessions() {
643 | const sessions = Array.from(activeSessions.entries()).map(([id, session]) => ({
644 | id,
645 | target: session.target || 'No program loaded',
646 | workingDir: session.workingDir || process.cwd()
647 | }));
648 |
649 | return {
650 | content: [
651 | {
652 | type: 'text',
653 | text: `Active GDB Sessions (${sessions.length}):\n\n${JSON.stringify(sessions, null, 2)}`
654 | }
655 | ]
656 | };
657 | }
658 |
659 | private async handleGdbAttach(args: any) {
660 | const { sessionId, pid } = args;
661 |
662 | if (!activeSessions.has(sessionId)) {
663 | return {
664 | content: [
665 | {
666 | type: 'text',
667 | text: `No active GDB session with ID: ${sessionId}`
668 | }
669 | ],
670 | isError: true
671 | };
672 | }
673 |
674 | const session = activeSessions.get(sessionId)!;
675 |
676 | try {
677 | const output = await this.executeGdbCommand(session, `attach ${pid}`);
678 |
679 | return {
680 | content: [
681 | {
682 | type: 'text',
683 | text: `Attached to process ${pid}\n\nOutput:\n${output}`
684 | }
685 | ]
686 | };
687 | } catch (error) {
688 | const errorMessage = error instanceof Error ? error.message : String(error);
689 | return {
690 | content: [
691 | {
692 | type: 'text',
693 | text: `Failed to attach to process: ${errorMessage}`
694 | }
695 | ],
696 | isError: true
697 | };
698 | }
699 | }
700 |
701 | private async handleGdbLoadCore(args: any) {
702 | const { sessionId, program, corePath } = args;
703 |
704 | if (!activeSessions.has(sessionId)) {
705 | return {
706 | content: [
707 | {
708 | type: 'text',
709 | text: `No active GDB session with ID: ${sessionId}`
710 | }
711 | ],
712 | isError: true
713 | };
714 | }
715 |
716 | const session = activeSessions.get(sessionId)!;
717 |
718 | try {
719 | // First load the program
720 | const fileOutput = await this.executeGdbCommand(session, `file "${program}"`);
721 |
722 | // Then load the core file
723 | const coreOutput = await this.executeGdbCommand(session, `core-file "${corePath}"`);
724 |
725 | // Get backtrace to show initial state
726 | const backtraceOutput = await this.executeGdbCommand(session, "backtrace");
727 |
728 | return {
729 | content: [
730 | {
731 | type: 'text',
732 | text: `Core file loaded: ${corePath}\n\nOutput:\n${fileOutput}\n${coreOutput}\n\nBacktrace:\n${backtraceOutput}`
733 | }
734 | ]
735 | };
736 | } catch (error) {
737 | const errorMessage = error instanceof Error ? error.message : String(error);
738 | return {
739 | content: [
740 | {
741 | type: 'text',
742 | text: `Failed to load core file: ${errorMessage}`
743 | }
744 | ],
745 | isError: true
746 | };
747 | }
748 | }
749 |
750 | private async handleGdbSetBreakpoint(args: any) {
751 | const { sessionId, location, condition } = args;
752 |
753 | if (!activeSessions.has(sessionId)) {
754 | return {
755 | content: [
756 | {
757 | type: 'text',
758 | text: `No active GDB session with ID: ${sessionId}`
759 | }
760 | ],
761 | isError: true
762 | };
763 | }
764 |
765 | const session = activeSessions.get(sessionId)!;
766 |
767 | try {
768 | // Set breakpoint
769 | let command = `break ${location}`;
770 | const output = await this.executeGdbCommand(session, command);
771 |
772 | // Set condition if provided
773 | let conditionOutput = '';
774 | if (condition) {
775 | // Extract breakpoint number from output (assumes format like "Breakpoint 1 at...")
776 | const match = output.match(/Breakpoint (\d+)/);
777 | if (match && match[1]) {
778 | const bpNum = match[1];
779 | const conditionCommand = `condition ${bpNum} ${condition}`;
780 | conditionOutput = await this.executeGdbCommand(session, conditionCommand);
781 | }
782 | }
783 |
784 | return {
785 | content: [
786 | {
787 | type: 'text',
788 | text: `Breakpoint set at: ${location}${condition ? ` with condition: ${condition}` : ''}\n\nOutput:\n${output}${conditionOutput ? '\n' + conditionOutput : ''}`
789 | }
790 | ]
791 | };
792 | } catch (error) {
793 | const errorMessage = error instanceof Error ? error.message : String(error);
794 | return {
795 | content: [
796 | {
797 | type: 'text',
798 | text: `Failed to set breakpoint: ${errorMessage}`
799 | }
800 | ],
801 | isError: true
802 | };
803 | }
804 | }
805 |
806 | private async handleGdbContinue(args: any) {
807 | const { sessionId } = args;
808 |
809 | if (!activeSessions.has(sessionId)) {
810 | return {
811 | content: [
812 | {
813 | type: 'text',
814 | text: `No active GDB session with ID: ${sessionId}`
815 | }
816 | ],
817 | isError: true
818 | };
819 | }
820 |
821 | const session = activeSessions.get(sessionId)!;
822 |
823 | try {
824 | const output = await this.executeGdbCommand(session, "continue");
825 |
826 | return {
827 | content: [
828 | {
829 | type: 'text',
830 | text: `Continued execution\n\nOutput:\n${output}`
831 | }
832 | ]
833 | };
834 | } catch (error) {
835 | const errorMessage = error instanceof Error ? error.message : String(error);
836 | return {
837 | content: [
838 | {
839 | type: 'text',
840 | text: `Failed to continue execution: ${errorMessage}`
841 | }
842 | ],
843 | isError: true
844 | };
845 | }
846 | }
847 |
848 | private async handleGdbStep(args: any) {
849 | const { sessionId, instructions = false } = args;
850 |
851 | if (!activeSessions.has(sessionId)) {
852 | return {
853 | content: [
854 | {
855 | type: 'text',
856 | text: `No active GDB session with ID: ${sessionId}`
857 | }
858 | ],
859 | isError: true
860 | };
861 | }
862 |
863 | const session = activeSessions.get(sessionId)!;
864 |
865 | try {
866 | // Use stepi for instruction-level stepping, otherwise step
867 | const command = instructions ? "stepi" : "step";
868 | const output = await this.executeGdbCommand(session, command);
869 |
870 | return {
871 | content: [
872 | {
873 | type: 'text',
874 | text: `Stepped ${instructions ? 'instruction' : 'line'}\n\nOutput:\n${output}`
875 | }
876 | ]
877 | };
878 | } catch (error) {
879 | const errorMessage = error instanceof Error ? error.message : String(error);
880 | return {
881 | content: [
882 | {
883 | type: 'text',
884 | text: `Failed to step: ${errorMessage}`
885 | }
886 | ],
887 | isError: true
888 | };
889 | }
890 | }
891 |
892 | private async handleGdbNext(args: any) {
893 | const { sessionId, instructions = false } = args;
894 |
895 | if (!activeSessions.has(sessionId)) {
896 | return {
897 | content: [
898 | {
899 | type: 'text',
900 | text: `No active GDB session with ID: ${sessionId}`
901 | }
902 | ],
903 | isError: true
904 | };
905 | }
906 |
907 | const session = activeSessions.get(sessionId)!;
908 |
909 | try {
910 | // Use nexti for instruction-level stepping, otherwise next
911 | const command = instructions ? "nexti" : "next";
912 | const output = await this.executeGdbCommand(session, command);
913 |
914 | return {
915 | content: [
916 | {
917 | type: 'text',
918 | text: `Stepped over ${instructions ? 'instruction' : 'function call'}\n\nOutput:\n${output}`
919 | }
920 | ]
921 | };
922 | } catch (error) {
923 | const errorMessage = error instanceof Error ? error.message : String(error);
924 | return {
925 | content: [
926 | {
927 | type: 'text',
928 | text: `Failed to step over: ${errorMessage}`
929 | }
930 | ],
931 | isError: true
932 | };
933 | }
934 | }
935 |
936 | private async handleGdbFinish(args: any) {
937 | const { sessionId } = args;
938 |
939 | if (!activeSessions.has(sessionId)) {
940 | return {
941 | content: [
942 | {
943 | type: 'text',
944 | text: `No active GDB session with ID: ${sessionId}`
945 | }
946 | ],
947 | isError: true
948 | };
949 | }
950 |
951 | const session = activeSessions.get(sessionId)!;
952 |
953 | try {
954 | const output = await this.executeGdbCommand(session, "finish");
955 |
956 | return {
957 | content: [
958 | {
959 | type: 'text',
960 | text: `Finished current function\n\nOutput:\n${output}`
961 | }
962 | ]
963 | };
964 | } catch (error) {
965 | const errorMessage = error instanceof Error ? error.message : String(error);
966 | return {
967 | content: [
968 | {
969 | type: 'text',
970 | text: `Failed to finish function: ${errorMessage}`
971 | }
972 | ],
973 | isError: true
974 | };
975 | }
976 | }
977 |
978 | private async handleGdbBacktrace(args: any) {
979 | const { sessionId, full = false, limit } = args;
980 |
981 | if (!activeSessions.has(sessionId)) {
982 | return {
983 | content: [
984 | {
985 | type: 'text',
986 | text: `No active GDB session with ID: ${sessionId}`
987 | }
988 | ],
989 | isError: true
990 | };
991 | }
992 |
993 | const session = activeSessions.get(sessionId)!;
994 |
995 | try {
996 | // Build backtrace command with options
997 | let command = full ? "backtrace full" : "backtrace";
998 | if (typeof limit === 'number') {
999 | command += ` ${limit}`;
1000 | }
1001 |
1002 | const output = await this.executeGdbCommand(session, command);
1003 |
1004 | return {
1005 | content: [
1006 | {
1007 | type: 'text',
1008 | text: `Backtrace${full ? ' (full)' : ''}${limit ? ` (limit: ${limit})` : ''}:\n\n${output}`
1009 | }
1010 | ]
1011 | };
1012 | } catch (error) {
1013 | const errorMessage = error instanceof Error ? error.message : String(error);
1014 | return {
1015 | content: [
1016 | {
1017 | type: 'text',
1018 | text: `Failed to get backtrace: ${errorMessage}`
1019 | }
1020 | ],
1021 | isError: true
1022 | };
1023 | }
1024 | }
1025 |
1026 | private async handleGdbPrint(args: any) {
1027 | const { sessionId, expression } = args;
1028 |
1029 | if (!activeSessions.has(sessionId)) {
1030 | return {
1031 | content: [
1032 | {
1033 | type: 'text',
1034 | text: `No active GDB session with ID: ${sessionId}`
1035 | }
1036 | ],
1037 | isError: true
1038 | };
1039 | }
1040 |
1041 | const session = activeSessions.get(sessionId)!;
1042 |
1043 | try {
1044 | const output = await this.executeGdbCommand(session, `print ${expression}`);
1045 |
1046 | return {
1047 | content: [
1048 | {
1049 | type: 'text',
1050 | text: `Print ${expression}:\n\n${output}`
1051 | }
1052 | ]
1053 | };
1054 | } catch (error) {
1055 | const errorMessage = error instanceof Error ? error.message : String(error);
1056 | return {
1057 | content: [
1058 | {
1059 | type: 'text',
1060 | text: `Failed to print expression: ${errorMessage}`
1061 | }
1062 | ],
1063 | isError: true
1064 | };
1065 | }
1066 | }
1067 |
1068 | private async handleGdbExamine(args: any) {
1069 | const { sessionId, expression, format = 'x', count = 1 } = args;
1070 |
1071 | if (!activeSessions.has(sessionId)) {
1072 | return {
1073 | content: [
1074 | {
1075 | type: 'text',
1076 | text: `No active GDB session with ID: ${sessionId}`
1077 | }
1078 | ],
1079 | isError: true
1080 | };
1081 | }
1082 |
1083 | const session = activeSessions.get(sessionId)!;
1084 |
1085 | try {
1086 | // Format examine command: x/[count][format] [expression]
1087 | const command = `x/${count}${format} ${expression}`;
1088 | const output = await this.executeGdbCommand(session, command);
1089 |
1090 | return {
1091 | content: [
1092 | {
1093 | type: 'text',
1094 | text: `Examine ${expression} (format: ${format}, count: ${count}):\n\n${output}`
1095 | }
1096 | ]
1097 | };
1098 | } catch (error) {
1099 | const errorMessage = error instanceof Error ? error.message : String(error);
1100 | return {
1101 | content: [
1102 | {
1103 | type: 'text',
1104 | text: `Failed to examine memory: ${errorMessage}`
1105 | }
1106 | ],
1107 | isError: true
1108 | };
1109 | }
1110 | }
1111 |
1112 | private async handleGdbInfoRegisters(args: any) {
1113 | const { sessionId, register } = args;
1114 |
1115 | if (!activeSessions.has(sessionId)) {
1116 | return {
1117 | content: [
1118 | {
1119 | type: 'text',
1120 | text: `No active GDB session with ID: ${sessionId}`
1121 | }
1122 | ],
1123 | isError: true
1124 | };
1125 | }
1126 |
1127 | const session = activeSessions.get(sessionId)!;
1128 |
1129 | try {
1130 | // Build info registers command, optionally with specific register
1131 | const command = register ? `info registers ${register}` : `info registers`;
1132 | const output = await this.executeGdbCommand(session, command);
1133 |
1134 | return {
1135 | content: [
1136 | {
1137 | type: 'text',
1138 | text: `Register info${register ? ` for ${register}` : ''}:\n\n${output}`
1139 | }
1140 | ]
1141 | };
1142 | } catch (error) {
1143 | const errorMessage = error instanceof Error ? error.message : String(error);
1144 | return {
1145 | content: [
1146 | {
1147 | type: 'text',
1148 | text: `Failed to get register info: ${errorMessage}`
1149 | }
1150 | ],
1151 | isError: true
1152 | };
1153 | }
1154 | }
1155 |
1156 | /**
1157 | * Execute a GDB command and wait for the response
1158 | */
1159 | private executeGdbCommand(session: GdbSession, command: string): Promise<string> {
1160 | return new Promise<string>((resolve, reject) => {
1161 | if (!session.ready) {
1162 | reject(new Error('GDB session is not ready'));
1163 | return;
1164 | }
1165 |
1166 | // Write command to GDB's stdin
1167 | if (session.process.stdin) {
1168 | session.process.stdin.write(command + '\n');
1169 | } else {
1170 | reject(new Error('GDB stdin is not available'));
1171 | return;
1172 | }
1173 |
1174 | let output = '';
1175 | let responseComplete = false;
1176 |
1177 | // Create a one-time event handler for GDB output
1178 | const onLine = (line: string) => {
1179 | output += line + '\n';
1180 |
1181 | // Check if this line indicates the end of the GDB response
1182 | if (line.includes('(gdb)') || line.includes('^done') || line.includes('^error')) {
1183 | responseComplete = true;
1184 |
1185 | // If we've received the complete response, resolve the promise
1186 | if (responseComplete) {
1187 | // Remove the listener to avoid memory leaks
1188 | session.rl.removeListener('line', onLine);
1189 | resolve(output);
1190 | }
1191 | }
1192 | };
1193 |
1194 | // Add the line handler to the readline interface
1195 | session.rl.on('line', onLine);
1196 |
1197 | // Set a timeout to prevent hanging
1198 | const timeout = setTimeout(() => {
1199 | session.rl.removeListener('line', onLine);
1200 | reject(new Error('GDB command timed out'));
1201 | }, 10000); // 10 second timeout
1202 |
1203 | // Handle GDB errors
1204 | const errorHandler = (data: Buffer) => {
1205 | const errorText = data.toString();
1206 | output += `[stderr] ${errorText}\n`;
1207 | };
1208 |
1209 | // Add error handler
1210 | if (session.process.stderr) {
1211 | session.process.stderr.once('data', errorHandler);
1212 | }
1213 |
1214 | // Clean up event handlers when the timeout expires
1215 | timeout.unref();
1216 | });
1217 | }
1218 |
1219 | /**
1220 | * Terminate a GDB session
1221 | */
1222 | private async terminateGdbSession(sessionId: string): Promise<void> {
1223 | if (!activeSessions.has(sessionId)) {
1224 | throw new Error(`No active GDB session with ID: ${sessionId}`);
1225 | }
1226 |
1227 | const session = activeSessions.get(sessionId)!;
1228 |
1229 | // Send quit command to GDB
1230 | try {
1231 | await this.executeGdbCommand(session, 'quit');
1232 | } catch (error) {
1233 | // Ignore errors from quit command, we'll force kill if needed
1234 | }
1235 |
1236 | // Force kill the process if it's still running
1237 | if (!session.process.killed) {
1238 | session.process.kill();
1239 | }
1240 |
1241 | // Close the readline interface
1242 | session.rl.close();
1243 |
1244 | // Remove from active sessions
1245 | activeSessions.delete(sessionId);
1246 | }
1247 |
1248 | async run() {
1249 | const transport = new StdioServerTransport();
1250 | await this.server.connect(transport);
1251 | console.error('GDB MCP server running on stdio');
1252 | }
1253 | }
1254 |
1255 | // Create and run the server
1256 | const server = new GdbServer();
1257 | server.run().catch((error) => {
1258 | console.error('Failed to start GDB MCP server:', error);
1259 | process.exit(1);
1260 | });
1261 |
```