Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .osoji.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[push]
endpoint = "https://api.osojicode.ai"

[audit]
# Archived investigation docs: near-zero audit value, dominant doc-analysis cost.
exclude = ["docs/archive/**"]
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Structure & Module Organization
- `src/` contains the TypeScript CLI and server core that power the Debug MCP runtime.
- `packages/` hosts workspace modules (`@debugmcp/shared`, `adapter-mock`, `adapter-python`, `adapter-javascript`, `adapter-rust`, `adapter-go`, `adapter-java`, `adapter-dotnet`, `mcp-debugger`) with their own `src/` trees.
- `packages/` hosts workspace modules (`@debugmcp/shared`, `adapter-mock`, `adapter-python`, `adapter-ruby`, `adapter-javascript`, `adapter-rust`, `adapter-go`, `adapter-java`, `adapter-dotnet`, `mcp-debugger`) with their own `src/` trees.
- `tests/` is grouped by scope: `core/`, `adapters/*/`, `e2e/`, plus shared utilities in `tests/test-utils/` and fixtures under `tests/fixtures/`.
- `docs/` covers design notes; `examples/` hosts adapter recipes; `scripts/` stores CI helpers.
- Build artifacts land in `dist/`; recorded assets and Docker helpers live in `assets/` and `docker/`.
Expand Down
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ mcp-debugger is a Model Context Protocol (MCP) server that bridges MCP clients (

## Monorepo Structure

The project uses pnpm workspaces with 9 packages:
The project uses pnpm workspaces with 10 packages:

```
packages/
shared/ Core interfaces and types (IDebugAdapter, IAdapterFactory)
adapter-python/ Python debugging via debugpy
adapter-javascript/ JavaScript/Node.js debugging via js-debug
adapter-ruby/ Ruby debugging via rdbg
adapter-rust/ Rust debugging via CodeLLDB
adapter-go/ Go debugging via Delve
adapter-java/ Java debugging via JDI bridge
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ mcp-debugger/
├── packages/ # Monorepo workspace packages
│ ├── shared/ # Shared interfaces, types, and utilities
│ ├── adapter-python/ # Python debug adapter (debugpy)
│ ├── adapter-ruby/ # Ruby debug adapter (rdbg/debug gem)
│ ├── adapter-javascript/# JavaScript/Node.js adapter (js-debug)
│ ├── adapter-rust/ # Rust adapter (CodeLLDB)
│ ├── adapter-go/ # Go adapter (Delve)
Expand Down Expand Up @@ -312,7 +313,7 @@ mcp-debugger/
- **DAP Proxy**: Handles communication with debug adapters via DAP protocol
- **Adapter Registry**: Dynamically loads and manages language-specific adapters
- **Adapter Policies**: Language-specific behavior via policy pattern
- **MCP Tools**: Implements the 20 MCP protocol tools
- **MCP Tools**: Implements the 21 MCP protocol tools

## 🏃 Running the Demo

Expand Down
10 changes: 5 additions & 5 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface IDebugAdapter extends EventEmitter {
dispose(): Promise<void>;

// Environment validation
validateEnvironment(): Promise<ValidationResult>;
validateEnvironment(executablePath?: string): Promise<ValidationResult>;
resolveExecutablePath(preferredPath?: string): Promise<string>;

// DAP operations
Expand Down Expand Up @@ -61,9 +61,9 @@ Each supported language implements the IDebugAdapter interface:
The **[AdapterRegistry](../../src/adapters/adapter-registry.ts)** manages available adapters through two mechanisms: explicit registration and dynamic loading. Factories can be pre-registered at startup, or loaded on demand via `AdapterLoader` when `enableDynamicLoading` is enabled (or `MCP_CONTAINER=true`):

```typescript
// Explicit registration
registry.register('python', new PythonAdapterFactory());
registry.register('mock', new MockAdapterFactory());
// Explicit registration (register is async and returns Promise<void>)
await registry.register('python', new PythonAdapterFactory());
await registry.register('mock', new MockAdapterFactory());

// Dynamic loading happens automatically when create() is called
// for a language without a pre-registered factory
Expand Down Expand Up @@ -227,7 +227,7 @@ The `stopped` event means PAUSED, not terminated:
```typescript
// ❌ Wrong: Confusing stopped with terminated
if (event.event === 'stopped') {
this.state = AdapterState.TERMINATED; // NO!
this.state = AdapterState.DISCONNECTED; // NO!
}

// ✅ Correct: Stopped = paused for debugging
Expand Down
3 changes: 2 additions & 1 deletion docs/architecture/adapter-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ State
- `getCurrentThreadId(): number | null` — Active thread (if any)

Environment validation
- `validateEnvironment(): Promise<ValidationResult>` — Check runtime prerequisites
- `validateEnvironment(executablePath?: string): Promise<ValidationResult>` — Check runtime prerequisites (optional `executablePath` validates a specific user-configured interpreter instead of an auto-detected one)
- `getRequiredDependencies(): DependencyInfo[]` — Declare dependencies (name/version/required)

Executable management
Expand All @@ -59,6 +59,7 @@ Attach support (optional)
- `supportsDetach?(): boolean` — Whether the adapter supports detaching without terminating the debuggee
- `transformAttachConfig?(config: GenericAttachConfig): LanguageSpecificAttachConfig` — Transforms generic attach config to language-specific format
- `getDefaultAttachConfig?(): Partial<GenericAttachConfig>` — Gets default attach configuration for this language
- `usesDirectConnectForAttach?(): boolean` — when true, attach connects directly to an already-listening DAP server (e.g. rdbg started with `--open`) instead of spawning an adapter process; `ProxyManager` skips `buildAdapterCommand` and the adapter policy returns a `'connect'` spawn config from the attach host/port

Debug configuration
- `transformLaunchConfig(config: GenericLaunchConfig): Promise<LanguageSpecificLaunchConfig>` (async to permit build/compilation steps before launch)
Expand Down
22 changes: 11 additions & 11 deletions docs/architecture/adapter-development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ packages/adapter-<language>/
Naming conventions:
- Package name: `@debugmcp/adapter-<language>`
- Factory class: `<Language>AdapterFactory` (e.g., `GoAdapterFactory`)
- File names: kebab-case (e.g., `go-debug-adapter.ts`, not `GoDebugAdapter.ts`)
- File names: kebab-case is the recommended convention for new adapters (used by 7 of the 8 shipped adapters), e.g. `go-debug-adapter.ts`. The .NET adapter uses PascalCase (`DotnetAdapterFactory.ts`, `DotnetDebugAdapter.ts`) for historical reasons.

---

Expand Down Expand Up @@ -133,7 +133,7 @@ Based on `packages/adapter-go/tsconfig.json`:

### 4. Implement `IAdapterFactory`

The factory creates adapter instances, provides metadata, and validates the environment. Implement the `IAdapterFactory` interface from `@debugmcp/shared` — do not extend a base class.
The factory creates adapter instances, provides metadata, and validates the environment. Either implement `IAdapterFactory` directly (as the Go reference and most adapters do) or extend the abstract `BaseAdapterFactory` (exported from `@debugmcp/shared`), which provides default `getMetadata()` and a default `validate()` (as `JavascriptAdapterFactory` does).

**Interface** (3 required methods):
```typescript
Expand Down Expand Up @@ -273,10 +273,13 @@ export { GoDebugAdapter } from './go-debug-adapter.js';
export { GoAdapterFactory } from './go-adapter-factory.js';
export * from './utils/go-utils.js';

// Optional default export (not used by the dynamic loader, but included by some adapters)
import { GoAdapterFactory as _GoAdapterFactory } from './go-adapter-factory.js';

// Convenience default export — NOT used by the mcp-debugger dynamic loader,
// which imports the named {Language}AdapterFactory export only
export default {
name: 'go',
factory: (await import('./go-adapter-factory.js')).GoAdapterFactory
factory: _GoAdapterFactory
};
```

Expand Down Expand Up @@ -360,13 +363,11 @@ The policy implements the `AdapterPolicy` interface (from `adapter-policy.ts`).

See `packages/shared/src/interfaces/adapter-policy-go.ts` for a minimal, clean policy example.

### Wiring the policy (3 locations)

1. **DAP proxy** — `src/proxy/dap-proxy-worker.ts` → `selectAdapterPolicy()` method: add a new `else if` branch matching your adapter command.
### Wiring the policy (2 locations)

2. **Session manager** — `src/session/session-manager-data.ts` → `selectPolicy()` method: add a new `case` branch for your `DebugLanguage` value.
1. **Policy map (required)** — add a `case` for your `DebugLanguage` in `getPolicyForLanguage()` in `packages/shared/src/interfaces/adapter-policy-map.ts`. This is consumed by both the DAP proxy and session manager. (The `else if` command-shape chain in `dap-proxy-worker.ts` `selectAdapterPolicy()` is a legacy fallback; add a branch there only if you must support sessions that arrive without a language field.)

3. **Export** — Add the policy to `packages/shared/src/index.ts` so both locations can import it.
2. **Export** — Add the policy to `packages/shared/src/index.ts` so `adapter-policy-map.ts` can import it.

---

Expand Down Expand Up @@ -477,8 +478,7 @@ The loader:
- [ ] `DebugLanguage` enum updated in `packages/shared/src/models/index.ts`
- [ ] Adapter policy created in `packages/shared/src/interfaces/adapter-policy-<language>.ts`
- [ ] Policy exported from `packages/shared/src/index.ts`
- [ ] Policy wired into `selectAdapterPolicy()` in `src/proxy/dap-proxy-worker.ts`
- [ ] Policy wired into `selectPolicy()` in `src/session/session-manager-data.ts`
- [ ] Policy `case` added to `getPolicyForLanguage()` in `packages/shared/src/interfaces/adapter-policy-map.ts`
- [ ] Registered in root `package.json` optionalDependencies
- [ ] Added to known adapters list in `src/adapters/adapter-loader.ts`
- [ ] Vitest alias added in `vitest.config.ts`
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/adapter-pattern-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ sequenceDiagram
"name": "create_debug_session",
"inputSchema": {
"properties": {
"language": { "enum": ["python", "javascript", "rust", "go", "java", "dotnet", "mock"] },
"language": { "enum": ["python", "ruby", "javascript", "rust", "go", "java", "dotnet", "mock"] },
"executablePath": { "type": "string" }
}
}
Expand Down
33 changes: 11 additions & 22 deletions docs/architecture/adapter-policy-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ This document explains the AdapterPolicy pattern and how it relates to the main
- **Purpose**: Complete debug adapter implementation
- **Scope**: Handles all DAP protocol communication and language runtime management
- **Location**: `packages/adapter-<language>/`
- **Examples**: `JavascriptDebugAdapter`, `PythonDebugAdapter`, `MockDebugAdapter`, `RustDebugAdapter`, `GoDebugAdapter`
- **Examples**: `JavascriptDebugAdapter`, `PythonDebugAdapter`, `MockDebugAdapter`, `RustDebugAdapter`, `GoDebugAdapter`, `RubyDebugAdapter`, `JavaDebugAdapter`, `DotnetDebugAdapter`

### AdapterPolicy (Supporting Pattern)
- **Purpose**: Language-specific policies for session management
- **Scope**: Lightweight behaviors used by SessionManager and DAP proxy layer
- **Location**: `packages/shared/src/interfaces/adapter-policy-*.ts`
- **All policies**: `DefaultAdapterPolicy`, `PythonAdapterPolicy`, `JsDebugAdapterPolicy`, `RustAdapterPolicy`, `GoAdapterPolicy`, `JavaAdapterPolicy`, `DotnetAdapterPolicy`, `MockAdapterPolicy`
- **All policies**: `DefaultAdapterPolicy`, `PythonAdapterPolicy`, `JsDebugAdapterPolicy`, `RubyAdapterPolicy`, `RustAdapterPolicy`, `GoAdapterPolicy`, `JavaAdapterPolicy`, `DotnetAdapterPolicy`, `MockAdapterPolicy`

## AdapterPolicy Interface

Expand Down Expand Up @@ -136,6 +136,7 @@ Client Request → Server → SessionManager
| `DefaultAdapterPolicy` | `adapter-policy.ts` | `'default'` | `'default'` | No | No |
| `PythonAdapterPolicy` | `adapter-policy-python.ts` | `'python'` | `'debugpy'` | No | No |
| `JsDebugAdapterPolicy` | `adapter-policy-js.ts` | `'js-debug'` | `'pwa-node'` | Yes | Yes |
| `RubyAdapterPolicy` | `adapter-policy-ruby.ts` | `'ruby'` | `'rdbg'` | No | No |
| `RustAdapterPolicy` | `adapter-policy-rust.ts` | `'rust'` | `'lldb'` | No | No |
| `GoAdapterPolicy` | `adapter-policy-go.ts` | `'go'` | `'dlv-dap'` | No | No |
| `JavaAdapterPolicy` | `adapter-policy-java.ts` | `'java'` | `'java'` | No | No |
Expand All @@ -158,10 +159,10 @@ Client Request → Server → SessionManager
- `resolveExecutablePath()` checks `PYTHON_PATH` env var, then defaults to `'python'` (Windows) or `'python3'` (Unix)
- `validateExecutable()` spawns Python to detect Windows Store aliases
- `extractLocalVariables()` filters out `special variables`, `function variables`, dunder variables, and `_pydev*` internals
- `getLocalScopeName()` returns `['Locals']`
- `getLocalScopeName()` returns `['Locals', 'Local']`

**RustAdapterPolicy**:
- `resolveExecutablePath()` prefers an explicitly provided path, then `CARGO_PATH` env var, otherwise returns `undefined` to let downstream adapter discovery decide. Despite the name, this does not resolve the vendored CodeLLDB path; actual adapter spawn path selection happens in `getAdapterSpawnConfig()`.
- `resolveExecutablePath()` prefers an explicitly provided path, otherwise returns `undefined` to let downstream adapter discovery (codelldb-resolver.ts, which checks `CODELLDB_PATH`) decide. Despite the name, this does not resolve the vendored CodeLLDB path; actual adapter spawn path selection happens in `getAdapterSpawnConfig()`.
- `validateExecutable()` checks filesystem existence of the candidate executable, then spawns it with `--version` and verifies stdout contains `codelldb`
- `getAdapterSpawnConfig()` resolves a vendored CodeLLDB binary based on platform/arch
- `getLocalScopeName()` returns `['Local', 'Locals']`
Expand Down Expand Up @@ -203,24 +204,7 @@ Session management classes use a `selectPolicy()` method to get the appropriate
```typescript
export class SessionManagerData extends SessionManagerCore {
protected selectPolicy(language: string | DebugLanguage): AdapterPolicy {
switch (language) {
case DebugLanguage.PYTHON:
return PythonAdapterPolicy;
case DebugLanguage.JAVASCRIPT:
return JsDebugAdapterPolicy;
case DebugLanguage.RUST:
return RustAdapterPolicy;
case DebugLanguage.GO:
return GoAdapterPolicy;
case DebugLanguage.JAVA:
return JavaAdapterPolicy;
case DebugLanguage.DOTNET:
return DotnetAdapterPolicy;
case DebugLanguage.MOCK:
return MockAdapterPolicy;
default:
return DefaultAdapterPolicy;
}
return getPolicyForLanguage(language);
}

async getStackTrace(sessionId: string): Promise<StackFrame[]> {
Expand All @@ -237,6 +221,11 @@ export class SessionManagerData extends SessionManagerCore {
}
```

The language → policy switch itself lives in `getPolicyForLanguage()`
(`packages/shared/src/interfaces/adapter-policy-map.ts`), which is the single
source of truth shared by `SessionManager`, `SessionStore`, and the DAP proxy
worker so the mapping cannot drift between processes.

## Migration from Hardcoded Conditionals

### Before (Hardcoded)
Expand Down
38 changes: 35 additions & 3 deletions docs/architecture/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ Gets the currently active thread ID during debugging.

### Environment Validation Methods

#### `validateEnvironment(): Promise<ValidationResult>`
#### `validateEnvironment(executablePath?: string): Promise<ValidationResult>`
Comprehensive environment check for debugging readiness.

**Parameters**: `executablePath` (optional) — a user-specified interpreter/executable path to validate.

**Returns**:
```typescript
{
Expand Down Expand Up @@ -180,6 +182,11 @@ Gets default attach configuration for this language.

**Returns**: Default attach configuration with language-specific defaults

#### `usesDirectConnectForAttach?(): boolean`
Whether attach connects directly to an already-listening DAP server (e.g. `rdbg` started with `--open`) instead of spawning an adapter process. When `true`, no adapter command is built for attach sessions; the adapter policy returns a `'connect'` spawn config from the attach host/port.

**Returns**: `true` if attach uses direct connection

### Launch Barrier (Optional)

#### `createLaunchBarrier?(command: string, args?: unknown): AdapterLaunchBarrier | undefined`
Expand Down Expand Up @@ -347,17 +354,20 @@ Gets variable scopes for a stack frame.
#### `getVariables(sessionId: string, variablesReference: number): Promise<Variable[]>`
Gets variables in a scope.

#### `evaluateExpression(sessionId: string, expression: string, frameId?: number, context?: string): Promise<EvaluateResult>`
#### `evaluateExpression(sessionId: string, expression: string, frameId?: number, timeoutMs?: number): Promise<EvaluateResult>`
Evaluates an expression in the current context. Returns a structured `EvaluateResult` with `result`, `type`, `variablesReference`, and optional error text.

**Note**: The `context` parameter is accepted by the API but the DAP `evaluate` request is always sent with `context: 'variables'` internally, regardless of the value passed.
**Note**: The DAP `evaluate` `context` is chosen by the adapter policy's `getEvaluateContext()` (defaults to `'variables'`; the Ruby policy uses `'repl'`). There is no client-supplied context parameter.

#### `attachToProcess(sessionId: string, attachConfig: AttachConfig): Promise<DebugResult>`
Attaches the debugger to a running process.

#### `detachFromProcess(sessionId: string, terminateProcess?: boolean): Promise<DebugResult>`
Detaches the debugger from an attached process.

#### `redefineClasses(sessionId: string, classesDir: string, sinceTimestamp?: number, timeoutMs?: number): Promise<RedefineClassesResult>`
Java only. Hot-swaps changed classes into a running JVM via a custom DAP `redefineClasses` request. `sinceTimestamp` (ms) limits the scan to `.class` files modified after that time (0/omitted = all); `timeoutMs` overrides the DAP request timeout. Exposed as the `redefine_classes` MCP tool.

#### `listThreads(sessionId: string): Promise<Array<{ id: number; name: string }>>`
Lists all threads in the debug session.

Expand Down Expand Up @@ -435,6 +445,27 @@ Checks if a language has a registered adapter.
#### `getSupportedLanguages(): string[]`
Lists all registered languages.

#### `unregister(language: string): boolean`
Removes an adapter factory and disposes any active adapters for the language. Returns `false` if no factory was registered.

#### `getAdapterInfo(language: string): AdapterInfo | undefined`
Returns metadata for a registered adapter, or `undefined`.

#### `getAllAdapterInfo(): Map<string, AdapterInfo>`
Returns metadata for all registered adapters.

#### `async listLanguages(): Promise<string[]>`
Lists all known languages from static registration and dynamic discovery (used by server.ts for language advertisement).

#### `async listAvailableAdapters(): Promise<AdapterMetadata[]>`
Lists detailed adapter metadata (known adapters plus install status).

#### `disposeAll(): Promise<void>`
Disposes all created adapters, clears factories, and resets the registry.

#### `getActiveAdapterCount(): number`
Returns the total number of active adapter instances across all languages.

## Event System

### Adapter Events
Expand Down Expand Up @@ -481,6 +512,7 @@ Common sequences:
```typescript
enum DebugLanguage {
PYTHON = 'python',
RUBY = 'ruby',
JAVASCRIPT = 'javascript',
RUST = 'rust',
GO = 'go',
Expand Down
Loading
Loading