Skip to content

Latest commit

 

History

History
481 lines (378 loc) · 22.7 KB

File metadata and controls

481 lines (378 loc) · 22.7 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

mcp-debugger is a Model Context Protocol (MCP) server that provides step-through debugging capabilities for AI agents. It acts as a bridge between MCP clients (like Claude) and debug adapters, enabling structured debugging operations through JSON-based tool calls.

The project uses a monorepo architecture with dynamic adapter loading, allowing language-specific debug adapters to be developed and deployed independently.

Monorepo Structure

mcp-debugger/
├── packages/
│   ├── shared/             # Shared interfaces, types, and utilities
│   ├── adapter-python/     # Python debug adapter using debugpy
│   ├── adapter-javascript/ # JavaScript/Node.js adapter using js-debug
│   ├── adapter-rust/       # Rust debug adapter using CodeLLDB
│   ├── adapter-go/         # Go debug adapter using Delve
│   ├── adapter-java/       # Java debug adapter using JDI bridge
│   ├── adapter-dotnet/     # .NET/C# debug adapter using netcoredbg
│   ├── adapter-mock/       # Mock adapter for testing
│   └── mcp-debugger/       # Self-contained CLI bundle (npx distribution)
├── src/
│   ├── adapters/          # Adapter loading and registry system
│   ├── container/         # Dependency injection container
│   ├── proxy/             # DAP proxy system
│   └── session/           # Session management
└── tests/                 # Comprehensive test suite

Package Details

  • @debugmcp/shared: Core interfaces and types used across all packages
  • @debugmcp/adapter-python: Python debugging support via debugpy
  • @debugmcp/adapter-javascript: JavaScript/Node.js debugging support via js-debug
  • @debugmcp/adapter-rust: Rust debugging support via CodeLLDB
  • @debugmcp/adapter-go: Go debugging support via Delve
  • @debugmcp/adapter-java: Java debugging support via JDI bridge
  • @debugmcp/adapter-dotnet: .NET/C# debugging support via netcoredbg
  • @debugmcp/adapter-mock: Mock adapter for testing and development
  • @debugmcp/mcp-debugger: Self-contained CLI bundle for npm distribution (npx-ready)

Key Commands

Building and Development

IMPORTANT: This project uses pnpm, not npm. The workspace:* protocol in dependencies requires pnpm. Run scripts via npm run <script> (which delegates to pnpm) or use pnpm directly.

# Install dependencies (including workspace packages)
pnpm install

# Build all packages and main project
npm run build

# Build specific packages
npm run build:shared
npm run build:adapters       # Build mock + python adapters
npm run build:adapters:all   # Build all adapters including JavaScript
npm run build:packages       # Build all packages in correct order via build-packages.cjs

# Clean build
npm run build:clean

# Development mode with watch
npm run dev

# Start the server (after building)
npm start
# or
node dist/index.js

# Run with specific transport modes
node dist/index.js                          # STDIO mode (default)
node dist/index.js http -p 3001             # Streamable HTTP mode (recommended for remote)
node dist/index.js sse -p 3001              # SSE mode (DEPRECATED — use http instead)

Testing

# Run all tests
npm test

# Run specific test suites
npm run test:unit         # Unit tests only
npm run test:integration  # Integration tests only
npm run test:e2e         # End-to-end tests only

# Run tests with coverage
npm run test:coverage
npm run test:coverage:summary  # Show coverage summary

# Run tests in watch mode
npm run test:watch

# Run a specific test file
npx vitest run tests/unit/session/session-manager.test.ts

# Run smoke tests for quick validation
npm run test:e2e:smoke

Code Quality

# Lint code
npm run lint

# Fix linting issues automatically
npm run lint:fix

# Check for personal paths (pre-commit hook)
npm run check:personal-paths
npm run check:all-personal-paths  # Check all files

Docker

# Build Docker image
npm run docker-build
# or
docker build -t mcp-debugger:local .

# Test container locally
npm run test:e2e:container

# Run container
docker run -v $(pwd):/workspace mcp-debugger:local

GitHub Actions Testing (Act)

# Test GitHub Actions locally using Act
npm run act:check    # Verify Act is installed
npm run act:lint     # Run lint job
npm run act:test     # Run test job (Ubuntu)
npm run act:test:all # Run all test jobs
npm run act:full     # Run full CI workflow

Path Handling Policy 🚨 CRITICAL

The project uses a TRUE HANDS-OFF approach to path handling:

  1. Accept all paths as-is - No interpretation of Windows vs Linux paths
  2. Path resolution and existence checking - For immediate LLM UX feedback (SimpleFileChecker)
  3. Container mode: Simple prefix - Only /workspace/ prepend for existence checks
  4. Pass resolved effective paths - src/server.ts validates paths via SimpleFileChecker and passes the resolved effectivePath to SessionManager
  5. No cross-platform logic - Avoids unsolvable edge cases and complexity

Key Files:

  • src/utils/simple-file-checker.ts - Path resolution (via resolvePathForRuntime), relative-path rejection in host mode, and file existence checking
  • src/server.ts - Uses SimpleFileChecker for validation, passes the resolved effectivePath to SessionManager

Rationale: Cross-platform path handling is theoretically impossible due to ambiguous edge cases. The debug adapter and OS know best how to handle paths for their environment.

Architecture Overview

The codebase follows a layered architecture with dependency injection and dynamic adapter loading:

Core Components

  1. MCP Server Layer (src/server.ts, src/index.ts)

    • Entry point for MCP protocol communication
    • Handles tool registration and routing
    • Supports STDIO and Streamable HTTP transport modes (legacy SSE deprecated)
    • Dynamically discovers available language adapters
  2. Adapter System (NEW)

    • AdapterRegistry (src/adapters/adapter-registry.ts): Manages adapter lifecycle
    • AdapterLoader (src/adapters/adapter-loader.ts): Dynamically loads adapters on-demand
    • Language Adapters (packages/adapter-*): Language-specific implementations
    • Supports both pre-registered and dynamically loaded adapters
  3. SessionManager (src/session/)

    • Central orchestrator for debug sessions, implemented as a 4-class inheritance hierarchy:
      • SessionManagerCore (session-manager-core.ts): Lifecycle, state management, event handling, dependency wiring
      • SessionManagerData (session-manager-data.ts): Data retrieval (variables, stack traces, scopes) and adapter policy selection via selectPolicy()
      • SessionManagerOperations (session-manager-operations.ts): Debug operations (start, step, continue, breakpoints, attach/detach)
      • SessionManager (session-manager.ts): Final composition class that extends SessionManagerOperations. Implements handleAutoContinue(sessionId) which auto-continues past entry breakpoints when stopOnEntry=false
    • Coordinates ProxyManager instances (one per session)
    • Handles breakpoint management and queuing
  4. ProxyManager (src/proxy/proxy-manager.ts)

    • Manages communication with debug proxy process
    • Spawns proxy worker in separate process
    • Implements typed event system for DAP events
    • Handles request/response correlation with timeouts
  5. DAP Proxy System (src/proxy/dap-proxy-*.ts, src/proxy/minimal-dap.ts)

    • ProxyRunner (dap-proxy-core.ts): Pure business logic, message processing
    • DapProxyWorker (dap-proxy-worker.ts): Core worker handling debugging operations
    • Adapter Policies: Language-specific behavior via policy pattern (DefaultAdapterPolicy, PythonAdapterPolicy, JsDebugAdapterPolicy, RustAdapterPolicy, GoAdapterPolicy, JavaAdapterPolicy, DotnetAdapterPolicy, MockAdapterPolicy). Note: Java is fully wired to JavaAdapterPolicy in DapProxyWorker.selectAdapterPolicy() (not falling through to DefaultAdapterPolicy).
    • ChildSessionManager (src/proxy/child-session-manager.ts): Manages DAP child sessions within a single proxy process. Currently used by the js-debug adapter (childSessionStrategy: 'launchWithPendingTarget'), which spawns a child debug session for the actual debuggee while the parent session manages the launch orchestration.
    • Implements full Debug Adapter Protocol (DAP) communication

Key Patterns

  • Dependency Injection: All major components use constructor injection via interfaces
  • Factory Pattern: ProxyManagerFactory, SessionStoreFactory, AdapterFactory for testability
  • Dynamic Loading: Language adapters loaded on-demand via ES modules
  • Event-Driven: Extensive EventEmitter usage for async communication
  • Process Isolation: Each debug session runs in separate process for stability
  • Error Boundaries: Centralized error handling with user-friendly messages

Data Flow

MCP Client → MCP Server → SessionManager → ProxyManager → ProxyWorker → Language Adapter → Debug Runtime
                ↓
         AdapterRegistry → AdapterLoader → Dynamic Import of @debugmcp/adapter-*

Dynamic Adapter Loading

The system supports dynamic adapter loading through:

  1. AdapterLoader: Attempts to load adapters by package name (@debugmcp/adapter-{language})
  2. Fallback Paths: Checks multiple locations (node_modules, packages directory)
  3. Registry Integration: Auto-registers dynamically loaded adapters
  4. Container Mode: Pre-loads known adapters in Docker environments

State Management

Sessions use SessionState as the primary state model, stored directly on each ManagedSession and checked throughout the codebase:

  • SessionState: CREATEDINITIALIZINGREADYRUNNINGPAUSEDSTOPPED | ERROR

A dual-state overlay (SessionLifecycleState + ExecutionState) is derived from SessionState via mapLegacyState() in _updateSessionState():

  • SessionLifecycleState: CREATEDACTIVETERMINATED (coarse lifecycle)
  • ExecutionState: INITIALIZINGRUNNINGPAUSEDTERMINATED | ERROR (fine-grained execution)

SessionState is the actively used model; the dual-state fields are kept in sync as a secondary representation.

Important Files and Directories

Core System

  • src/server.ts - Main MCP server implementation
  • src/session/ - Session management (4-class hierarchy: session-manager-core.tssession-manager-data.tssession-manager-operations.tssession-manager.ts)
  • src/proxy/proxy-manager.ts - Proxy process management
  • src/proxy/dap-proxy-worker.ts - Debug adapter protocol implementation

Adapter System

  • src/adapters/adapter-registry.ts - Adapter lifecycle management
  • src/adapters/adapter-loader.ts - Dynamic adapter loading (7 known adapters)
  • packages/shared/ - Shared interfaces and types
  • packages/adapter-python/ - Python debug adapter (debugpy)
  • packages/adapter-javascript/ - JavaScript/Node.js debug adapter (js-debug)
  • packages/adapter-rust/ - Rust debug adapter (CodeLLDB)
  • packages/adapter-go/ - Go debug adapter (Delve)
  • packages/adapter-java/ - Java debug adapter (JDI bridge)
  • packages/adapter-dotnet/ - .NET/C# debug adapter (netcoredbg)
  • packages/adapter-mock/ - Mock adapter for testing

Distribution

  • packages/mcp-debugger/ - Self-contained CLI bundle for npm/npx distribution

Supporting Infrastructure

  • src/container/dependencies.ts - Dependency injection container
  • src/utils/error-messages.ts - Centralized error messages
  • tests/ - Comprehensive test suite (unit, integration, e2e)
  • examples/ - Example scripts for debugging
  • docs/architecture/ - Detailed architecture documentation

Development Guidelines

  1. TypeScript Strict Mode: All code must pass TypeScript strict mode checks
  2. Monorepo Management: Use pnpm workspaces for package management (pnpm install, not npm install)
  3. Build Order: Packages must build in order: shared → adapters → main server. This is managed by scripts/build-packages.cjs
  4. Test Coverage: Maintain >90% test coverage
  5. Error Handling: Use centralized error messages from error-messages.ts
  6. Logging: Use Winston logger with appropriate log levels
  7. Async Operations: All DAP operations are async with timeouts
  8. Process Cleanup: Always ensure proper cleanup of spawned processes
  9. Adapter Development: New language adapters should implement IAdapterFactory from @debugmcp/shared

Testing Approach

The project uses Vitest with three test levels:

  • Unit Tests: Test components in isolation with mocks
  • Integration Tests: Test component interactions
  • E2E Tests: Test full debugging workflows with real debug adapters

Common Debugging Scenarios

When debugging issues:

  1. Enable debug logging: DEBUG=debug-mcp:* node dist/index.js
  2. Check proxy process output in logs
  3. Verify language-specific requirements (e.g., python -m debugpy --version)
  4. Use dryRunSpawn: true in start_debugging tool arguments to test configuration without starting a real debug session

Adding New Language Adapters

To add support for a new language:

  1. Create Package: Add new package under packages/adapter-{language}/
  2. Implement Interfaces: Implement IAdapterFactory and IDebugAdapter from @debugmcp/shared
  3. Export Factory: Export a factory class named {Language}AdapterFactory
  4. Register in root package.json: Add "@debugmcp/adapter-{language}": "workspace:*" to optionalDependencies
  5. Add Vitest alias: Add { find: '@debugmcp/adapter-{language}', replacement: path.resolve(__dirname, './packages/adapter-{language}/src/index.ts') } to resolve.alias in vitest.config.ts
  6. Update adapter count: Update hardcoded adapter counts in tests (adapter-loader.test.ts, models.test.ts)
  7. Add Tests: Include unit and integration tests in the package
  8. Run pnpm install: To link the new workspace package

Example structure:

packages/adapter-{language}/
├── src/
│   ├── index.ts         # Export {Language}AdapterFactory
│   ├── {language}-debug-adapter.ts  # Implement IDebugAdapter
│   └── {language}-adapter-factory.ts  # Implement IAdapterFactory
├── tests/
├── package.json         # Must include "type": "module" and workspace:* dep on @debugmcp/shared
├── tsconfig.json
└── vitest.config.ts     # Optional if tests run from root

Language-Specific Requirements

Python

  • Python 3.7+ must be installed
  • debugpy must be installed: pip install debugpy
  • The system will auto-detect Python path or use PYTHON_PATH env var

JavaScript/Node.js

  • Node.js 22+ must be installed
  • Uses bundled js-debug adapter (VSCode's debugger)
  • Supports JavaScript and TypeScript debugging
  • Auto-detects TypeScript configuration

Rust

  • Rust toolchain must be installed (rustc, cargo)
  • Uses vendored CodeLLDB debug adapter (auto-downloaded during pnpm install)
  • Supports both MSVC and GNU toolchains on Windows

Java

  • JDK 21+ must be installed (java and javac on PATH, or JAVA_HOME set)
  • Uses JDI bridge (JdiDapServer.java) — a single Java file compiled on first use via javac
  • Must compile target code with javac -g for variable inspection (includes LocalVariableTable)
  • Launch mode: The adapter derives mainClass from the program field in the launch config and transparently forwards classpath, sourcePath, cwd, env, and args — JDI bridge spawns the JVM and connects via JDI
  • Attach mode: Connect to JVM with JDWP agent (-agentlib:jdwp=...). JDI bridge handles deferred breakpoints natively via ClassPrepareRequest
  • Zero external dependencies — JDI (com.sun.jdi.*) ships with every JDK
  • See docs/java/README.md for architecture details

Go

  • Go 1.18+ must be installed
  • Delve debugger must be installed: go install github.com/go-delve/delve/cmd/dlv@latest
  • Uses Delve's native DAP protocol support

.NET/C#

  • .NET 6+ SDK must be installed
  • netcoredbg must be installed: download from Samsung releases or build from source
  • Set NETCOREDBG_PATH environment variable to the netcoredbg executable, or add its directory to PATH
  • PDB symbols must be in Portable format (compile with /debug:portable or the adapter's auto Pdb2Pdb conversion)
  • Uses TCP-to-stdio bridge on all platforms (works around a netcoredbg --server mode bug originally discovered on Windows)
  • See docs/dotnet/README.md for architecture details and debugging guide

Mock (Testing)

  • No external requirements
  • Used for testing the debug infrastructure

MCP Integration with Claude Code CLI

Installation for Claude Code

Choose the installation method that best fits your use case:

Option 1: NPX (No Installation Required)

# Best for: Trying out mcp-debugger
/home/ubuntu/.claude/local/claude mcp add-json mcp-debugger \
  '{"type":"stdio","command":"npx","args":["@debugmcp/mcp-debugger","stdio"]}'

Option 2: Global NPM Install

# Best for: Regular use across projects
npm install -g @debugmcp/mcp-debugger
/home/ubuntu/.claude/local/claude mcp add-json mcp-debugger \
  '{"type":"stdio","command":"mcp-debugger","args":["stdio"]}'

Option 3: Docker

# Best for: Isolation and consistency
/home/ubuntu/.claude/local/claude mcp add-json mcp-debugger \
  '{"type":"stdio","command":"docker","args":["run","-i","--rm","-v","${PWD}:/workspace","debugmcp/mcp-debugger:latest","stdio"]}'

Option 4: Build from Source

# Best for: One-off use from a local clone
pnpm install && npm run build
/home/ubuntu/.claude/local/claude mcp add-json mcp-debugger \
  '{"type":"stdio","command":"node","args":["/home/ubuntu/mcp-debugger/dist/index.js","stdio"]}'

Note: The stdio argument is critical - it tells the server to suppress all console output which would otherwise corrupt the JSON-RPC protocol communication.

Option 5: Dev Proxy (Current Setup — Recommended for Development)

# Best for: Active development of mcp-debugger itself
pnpm install && npm run build
claude mcp add-json mcp-debugger \
  '{"type":"stdio","command":"node","args":["tools/dev-proxy/dev-proxy.mjs"]}'

The dev proxy (tools/dev-proxy/dev-proxy.mjs) is a lightweight MCP proxy that sits between Claude Code and mcp-debugger. It maintains a stable stdio connection to Claude Code while managing the backend as a restartable Streamable HTTP child process (default; legacy SSE and stdio modes are also supported via DEV_PROXY_BACKEND_TRANSPORT). This means you can rebuild and restart mcp-debugger without restarting Claude Code (which would lose conversation context).

When to use: Whenever you are actively developing mcp-debugger — making code changes, adding adapters, or installing new toolchains (Go, etc.) that need to be picked up by the running server.

How it works: After code changes, call the dev_rebuild_and_restart tool. The proxy kills the backend, runs npm run build, spawns a fresh process, and reconnects — all transparently. If the backend crashes, dev tools remain available to bring it back.

Configuration (env vars, all optional):

  • DEV_PROXY_PORT — Backend HTTP port (default: 3001; used by http and sse modes)
  • DEV_PROXY_BUILD_CMD — Build command (default: npm run build)
  • DEV_PROXY_ROOT — Project root (default: auto-detected)
  • DEV_PROXY_BACKEND_TRANSPORThttp (default), sse (legacy/deprecated), or stdio

Verify Installation

After adding the MCP server:

  1. Check connection status:

    /home/ubuntu/.claude/local/claude mcp list
    # Should show: mcp-debugger ... - ✓ Connected
  2. Restart Claude Code for the changes to take effect

Configuration Details

  • Location: Configuration saved to /home/ubuntu/.claude.json under the project's mcpServers section
  • Server Type: STDIO (local server)
  • Command: node /home/ubuntu/mcp-debugger/dist/index.js stdio (stdio argument is required!)
  • Status Check: After restart, type /mcp in Claude Code to see connected servers

Available Tools After Integration

Once connected, the following 21 MCP tools become available:

  • create_debug_session - Start a new debug session
  • list_debug_sessions - List active debug sessions
  • list_supported_languages - Show available language adapters
  • set_breakpoint - Set breakpoints in code
  • start_debugging - Begin debugging a script
  • attach_to_process - Attach debugger to a running process
  • detach_from_process - Detach debugger from a process
  • close_debug_session - Clean up sessions
  • step_over, step_into, step_out - Step through code
  • continue_execution - Continue running
  • pause_execution - Pause a running program
  • list_threads - List all threads in a debug session
  • get_variables - Inspect variables in scope
  • get_local_variables - Get local variables in current frame
  • get_stack_trace - Inspect call stack
  • get_scopes - Get variable scopes for a stack frame
  • evaluate_expression - Evaluate expressions in debug context
  • get_source_context - Get source code around current position
  • redefine_classes - Hot-swap changed Java classes into a running JVM (Java only)

Dev proxy only (these 3 tools are injected by the dev proxy process itself, not by the main mcp-debugger server):

  • dev_restart_debugger - Restart the backend (pass rebuild: true to build first)
  • dev_rebuild_and_restart - Run npm run build then restart the backend
  • dev_server_status - Check backend state, PID, uptime, tool count

Troubleshooting MCP Connection

  • If server shows "Failed to connect":
    • Ensure the stdio argument is included in the configuration
    • The server silences console output at startup to protect stdio/IPC transports, but misconfigured builds or third-party code may still produce output
    • Use the add-json command shown above to properly configure with the stdio argument
    • Note: The stdio command is the default subcommand (isDefault: true in CLI setup), so explicitly passing stdio is optional but recommended for clarity
  • Test the server manually:
    echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"roots":{},"sampling":{}},"clientInfo":{"name":"test","version":"1.0.0"}},"id":1}' | node dist/index.js stdio
    # Should return clean JSON without any log messages
  • Verify Python and debugpy are installed: python3 -m debugpy --version
  • Check logs if needed: Set DEBUG=debug-mcp:* environment variable (only for troubleshooting, not for normal operation)