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
42 changes: 38 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -619,8 +619,9 @@ jobs:
mcp-conformance:
# The protocol org's own conformance suite (`@modelcontextprotocol/conformance`, pinned in
# the script) against a local `http` server: the scenarios that apply to a server without
# the suite's reference fixtures (issue #1023). Linux only — on Windows the CLI aborts at
# exit with a libuv assertion even after every check passed. What the suite does not cover,
# the suite's reference fixtures (issue #1023). Linux only — on Windows an affected Node build
# aborts inside process.exit after every check passed (nodejs/node#56645, fixed in 24.20.0
# and 26.7.0), which is a Node bug and not the suite's. What the suite does not cover,
# the session-lifecycle clauses this server answers itself, is pinned by
# tests/integration/cli/http-spec-contract.test.ts in build-and-test.
name: MCP Conformance (official suite)
Expand All @@ -645,9 +646,40 @@ jobs:
- name: Run the applicable conformance scenarios
run: node scripts/mcp-conformance.mjs --verbose

mcp-conformance-2026:
# The same suite's 0.2 line (the one the SDKs run) against the 2026-07-28 revision this
# server now speaks beside 2025-11-25 (issue #1025, step 2): `--requirements 2026-07-28`,
# everything the revision requires of a server, judged by the suite itself against
# scripts/conformance/expected-failures-2026-07-28.yaml (`--expected-failures`) — what
# cannot be tested without the suite's reference fixtures or an optional feature this
# server does not declare. The suite fails on an unlisted failure or warning and on a
# listed entry that passes, so the baseline can only shrink; its exit code is the verdict.
# Linux, where that exit code is always trustworthy (see the script's header).
name: MCP Conformance (2026-07-28)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup pnpm
uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1
- name: Setup Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22.x'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
SKIP_ADAPTER_VENDOR: 'true'
- name: Build project
run: pnpm run build:ci
- name: Run the 2026-07-28 requirements against the baseline, judged by the suite
run: node scripts/mcp-conformance.mjs --era 2026-07-28 --verbose

test-summary:
name: Test Summary
needs: [build-and-test, windows-python-integration, lint, container-tests, cobol-host, dart-host, flutter-host, go-current, mcp-conformance]
needs: [build-and-test, windows-python-integration, lint, container-tests, cobol-host, dart-host, flutter-host, go-current, mcp-conformance, mcp-conformance-2026]
runs-on: ubuntu-latest
if: always()

Expand All @@ -663,6 +695,7 @@ jobs:
echo "flutter-host: ${{ needs.flutter-host.result }}"
echo "go-current: ${{ needs.go-current.result }}"
echo "mcp-conformance: ${{ needs.mcp-conformance.result }}"
echo "mcp-conformance-2026: ${{ needs.mcp-conformance-2026.result }}"
if [ "${{ needs.build-and-test.result }}" != "success" ] \
|| [ "${{ needs.windows-python-integration.result }}" != "success" ] \
|| [ "${{ needs.lint.result }}" != "success" ] \
Expand All @@ -671,7 +704,8 @@ jobs:
|| [ "${{ needs.dart-host.result }}" != "success" ] \
|| [ "${{ needs.flutter-host.result }}" != "success" ] \
|| [ "${{ needs.go-current.result }}" != "success" ] \
|| [ "${{ needs.mcp-conformance.result }}" != "success" ]; then
|| [ "${{ needs.mcp-conformance.result }}" != "success" ] \
|| [ "${{ needs.mcp-conformance-2026.result }}" != "success" ]; then
echo "❌ One or more required jobs did not succeed"
exit 1
fi
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ The codebase follows a **layered architecture with dependency injection** and **
- Entry point for MCP protocol communication
- `src/server.ts` is the composition root and the shared core (`DebugMcpServer`: dependencies, lifecycle, session validation, breakpoint gating, the public facade methods, the `NotificationHub`); it implements `ToolContext` and passes itself to the handlers. It owns no SDK `Server`: `src/server/protocol-facade.ts` (`createProtocolServer(core, { era, deliver })`) builds one cheap SDK `Server` per connection or request over the core, and `src/server/notification-hub.ts` fans the coalesced `resources/updated` pings and `resources/list_changed` out to every facade's sink (issue #1025)
- `src/server/` holds the tool schemas (`tool-schemas.ts`), argument coercion (`tool-arguments.ts`), the tools/call dispatch wrapper (`tool-dispatch.ts` over `handlers/index.ts`), argument validation (`tool-validation.ts`), result envelopes and the session-error catch helpers (`tool-result.ts`), one handler module per tool family (`handlers/*.ts`) over their shared helpers (`handlers/shared.ts`), and the resource (`output-resources.ts`) and prompt (`prompts.ts`) handlers
- Supports STDIO and Streamable HTTP transport modes. `stdio` is served through the SDK's `serveStdio(factory, { transport })` and speaks both protocol eras (2026-07-28 for a client that probes with `server/discover`, 2025-11-25 and earlier for one that opens with `initialize`); `http` speaks the 2025 era (the 2026 era follows in #1025 step 2, PR 4). One core per process in both: on `http` every MCP session is a facade over the same debug sessions (the id is the capability; one process per agent is the isolation), a debug session outlives the MCP session that created it and ends by its own close, its program ending, or the server exiting. The per-debug-session idle policy is the operator's opt-in (`MCP_DEBUG_SESSION_IDLE_MS`, off by default on every transport; sweep `MCP_DEBUG_SESSION_IDLE_SWEEP_MS`): when set, an abandoned session tree is closed unless a client is attending the process (an open SSE stream), `list_debug_sessions` rows show `idleMs`/`expiresInMs`, and a call naming a session the policy closed answers `closedBy: "idle-policy"`. A session outside the caller's owner scope is hidden from the call's lookups (`src/session/session-scope.ts`), so every tool answers it as an unknown id. The HTTP reapers (`MCP_HTTP_STALE_SESSION_MS`, `MCP_HTTP_STREAM_LOST_SESSION_MS`) reap abandoned MCP sessions only (issue #1025)
- Supports STDIO and Streamable HTTP transport modes, both in both protocol eras (issue #1025): `stdio` is served through the SDK's `serveStdio(factory, { transport })`, which pins a connection to the era its opening message chose (2026-07-28 for a `server/discover` probe, 2025-11-25 and earlier for `initialize`); `http` routes each request with the SDK's `isLegacyRequest` — a POST carrying the 2026 per-request envelope goes to a strict `createMcpHandler(factory, { legacy: 'reject' })` behind `toNodeHandler` (one facade per request, sessionless), everything 2025-shaped to the sessionful app as before. One core per process in both: on `http` every MCP session is a facade over the same debug sessions (the id is the capability; one process per agent is the isolation), a debug session outlives the MCP session that created it and ends by its own close, its program ending, or the server exiting. The per-debug-session idle policy is the operator's opt-in (`MCP_DEBUG_SESSION_IDLE_MS`, off by default on every transport; sweep `MCP_DEBUG_SESSION_IDLE_SWEEP_MS`): when set, an abandoned session tree is closed unless a client is attending the process (a 2025 session's open SSE stream, or a 2026-07-28 client's open `subscriptions/listen` stream), `list_debug_sessions` rows show `idleMs`/`expiresInMs`, and a call naming a session the policy closed answers `closedBy: "idle-policy"`. A session outside the caller's owner scope is hidden from the call's lookups (`src/session/session-scope.ts`), so every tool answers it as an unknown id. The HTTP reapers (`MCP_HTTP_STALE_SESSION_MS`, `MCP_HTTP_STREAM_LOST_SESSION_MS`) reap abandoned MCP sessions only (issue #1025)
- Dynamically discovers available language adapters

2. **Adapter System** (NEW)
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ If your agent runs in a terminal, a pipeline, or a cloud sandbox — or needs to
- 💥 **Crash-state debugging by default** – Launch sessions pause on uncaught exceptions with stack and locals live (`breakOnExceptions`; exception class/message surfaced via `lastStop`)
- 🪞 **Read-only IDE mirror** – `expose_session` opens a loopback, token-gated DAP endpoint so a human's IDE can inspect the agent's live session without taking control
- ☸️ **Kubernetes debugging** – port-forward attach for interpreted runtimes, `kubectl debug --target` + attach-by-PID for native processes ([recipe](docs/kubernetes.md), [turnkey manifests](examples/kubernetes/))
- 🔌 **STDIO and Streamable HTTP transports** – Works with any MCP client. `stdio` speaks both protocol eras — the current 2026-07-28 revision (a probing client such as Claude Code's v2 runtime lands on it) and 2025-11-25 and earlier (a client that opens with `initialize`); `http` speaks 2025-11-25 and earlier, with 2026-07-28 next
- 🔌 **STDIO and Streamable HTTP transports** – Works with any MCP client. Both speak both protocol eras: the current 2026-07-28 revision (a probing client such as Claude Code's v2 runtime lands on it; sessionless on `http`) and 2025-11-25 and earlier (a client that opens with `initialize`)
- 📦 **Zero-runtime dependencies** – Self-contained bundles via esbuild + tsup
- ⚡ **npx ready** – Run directly with `npx @debugmcp/mcp-debugger` - no installation needed
- 🐳 **Docker and npm packages** – Deploy anywhere
Expand Down Expand Up @@ -224,6 +224,12 @@ and point the client at it:
}
```

The endpoint speaks both eras of the protocol: a client that probes with `server/discover` (Claude
Code's v2 runtime does) gets the current 2026-07-28 revision, sessionless — every request stands
alone, resource notifications flow over `subscriptions/listen` streams — and a client that opens
with `initialize` gets the 2025 sessionful era, `Mcp-Session-Id` and all, exactly as before.
`GET /health` counts the requests classified per era under `requests` (refused ones included).

One `http` process is one debugger: every MCP client of it sees the same debug sessions, and a
session is addressable by whoever holds its id (the handle is the capability — run one process
per agent if agents must not see each other's work). A debug session outlives the MCP session
Expand All @@ -233,8 +239,12 @@ on a timer by default, so a quiet agent never loses a paused program. A server t
long-lived, or that attaches to something that must not stay paused, can turn the idle policy on:
with `MCP_DEBUG_SESSION_IDLE_MS` set, a session tree that no tool call has named for that long is
closed (a launched program is ended, an attached one released), unless a client is attending the
process — while any client holds its SSE stream open, nothing expires. `list_debug_sessions`
shows each session's idle time, so a forgotten one is easy to find and close. Abandoned MCP
process — while any client holds a stream open (a 2025 session's SSE stream, a 2026-07-28
client's `subscriptions/listen` stream), nothing expires. A 2026 client that only calls tools is
not attending: with the policy on, its sessions live by tool activity (and an in-flight
`wait_for_stop`) alone, so choose a window longer than such a client's pauses.
`list_debug_sessions` shows each session's idle time, so a forgotten one is easy to find and
close. Abandoned MCP
sessions themselves are reaped separately (`MCP_HTTP_STREAM_LOST_SESSION_MS`,
`MCP_HTTP_STALE_SESSION_MS`). `GET /health` lists the MCP sessions, the debug sessions with
their idle time, and whether anyone is attending.
Expand Down
1 change: 1 addition & 0 deletions changelog.d/1025-conformance-2026.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**A second MCP conformance lane for the 2026-07-28 revision, judged by the suite itself.** `node scripts/mcp-conformance.mjs --era 2026-07-28` runs the protocol org's conformance suite on its 0.2 line (`@modelcontextprotocol/conformance@0.2.0-alpha.12`, the line the SDKs run) with `--requirements 2026-07-28` — everything the revision requires of a server — against a local `http` server, and hands the suite its own baseline with `--expected-failures`: the suite fails the run on a failure or warning that is not listed and on a listed entry that passes, and its exit code is the lane's verdict. `scripts/conformance/expected-failures-2026-07-28.yaml` lists what cannot be tested against this server — 18 checks that only drive the suite's reference fixtures (the "everything server" tools, resources and prompts), and 13 that need an optional feature this server does not declare (`completions`, input-required results) — each a single check with the fixture or feature named beside it, never a whole scenario, so every scenario's other checks (the schema validation of each message this server sends) stay enforced; none is a protocol divergence, and by the suite's own policy a missing prerequisite is a failure, not a skip (upstream discussion: modelcontextprotocol/conformance#516). CI runs it as `mcp-conformance-2026`, required in the test summary beside the 2025-11-25 lane, which is unchanged. Running the suite found two real gaps, both fixed in both eras: `resources/templates/list` answered "Method not found" (a server advertising the resources capability must answer it; it now answers an empty list), and the error for an unknown resource carried no `data` (it now names the requested `uri`, SEP-2164).
1 change: 1 addition & 0 deletions changelog.d/1025-protocol-2026-http.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**`http` speaks the current MCP protocol revision, 2026-07-28, beside 2025-11-25 and earlier — on the same `/mcp` endpoint.** A request carrying the 2026 per-request envelope (`_meta` with `io.modelcontextprotocol/protocolVersion`) is served by the SDK's sessionless handler: `server/discover` answers the supported versions and capabilities, every request stands alone with no `Mcp-Session-Id`, results carry `resultType` and the cache fields, resource notifications flow over `subscriptions/listen` streams, and the 2026 header rules apply (`MCP-Protocol-Version` and `Mcp-Method` on every POST, `Mcp-Name` on the methods that name a tool, prompt or resource; a missing or mismatched one is refused with `-32020`, an unsupported claimed revision with `-32022` naming the supported ones). A 2025-shaped request — `initialize`, a request carrying `Mcp-Session-Id` and no envelope, a GET or DELETE, a claim-less POST — lands on the sessionful app exactly as before, status codes included, so every 2025 client keeps working. An open `subscriptions/listen` stream counts the process as attended for the idle policy, like a 2025 session's SSE stream; a 2026 client that only calls tools is not attending, so with the idle policy turned on its sessions live by tool activity alone. A probing client such as Claude Code's v2 runtime therefore lands on 2026-07-28 against `http` too. `GET /health` reports `listenStreams` and the requests classified per era (`requests.legacy` / `requests.modern`). Browser clients: CORS now allows `Mcp-Method`, `Mcp-Name` and echoes a preflight's requested headers. In the log, each 2026-07-28 client is named once (`[MCP Server] Client identified`), a request the handler refuses is a warning rather than an error, and a body too large to classify answers 413 like any other oversized body. Step 2 of #1025 completes the protocol move on both transports.
2 changes: 1 addition & 1 deletion docs/architecture/system-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ graph TB
- **Purpose**: Entry point for MCP protocol communication
- **Key Files**:
- `src/server.ts` - `DebugMcpServer`, the composition root and the shared core: dependencies, lifecycle, session validation, breakpoint gating, the public facade methods and the `NotificationHub`; implements `ToolContext`. It owns no SDK `Server`
- `src/server/protocol-facade.ts` - `createProtocolServer(core, { era, deliver })`: one cheap SDK `Server` per connection or request over the core (the eight MCP handlers, the error and initialized hooks, one notification sink that leaves with the facade); `src/server/notification-hub.ts` - the process-wide fan-out of coalesced `resources/updated` pings and `resources/list_changed` to every facade's sink (issue #1025)
- `src/server/protocol-facade.ts` - `createProtocolServer(core, { era, deliver })`: one cheap SDK `Server` per connection or request over the core (the nine MCP handlers, the error and initialized hooks, one notification sink that leaves with the facade); `src/server/notification-hub.ts` - the process-wide fan-out of coalesced `resources/updated` pings and `resources/list_changed` to every facade's sink (issue #1025)
- `src/server/` - tool schemas (`tool-schemas.ts`), argument coercion (`tool-arguments.ts`), the tools/call dispatch wrapper (`tool-dispatch.ts` over `handlers/index.ts`), argument validation (`tool-validation.ts`), result envelopes and the session-error catch helpers (`tool-result.ts`), one handler module per tool family (`handlers/*.ts`) over their shared helpers (`handlers/shared.ts`), and the resource (`output-resources.ts`) and prompt (`prompts.ts`) handlers
- `src/index.ts` - the `dist/index.js` entry: the prologue (`src/cli/entry-prologue.ts`, console silencing and argv cleanup, evaluated before anything else), then `main()` from `src/main.ts` — the side-effect-free module the npm bundle's `cli-entry.ts` imports directly (#732); the four subcommands are wired in `src/cli/setup.ts`: `stdio` (default), `http` (Streamable HTTP, recommended, `-p/--port`, default 3001), `doctor` (toolchain self-check), `check-rust-binary`
- `src/server/output-resources.ts` - the MCP resource handlers: `debug://sessions/{id}/output` (subscribable plain-text transcript of captured debuggee output, coalesced `resources/updated` notifications) and `debug://sessions/{id}/proxy-log` (a sanitized, bounded tail of the session's proxy log — 64 KiB / 80 lines, on-demand snapshot, not subscribable, and listed only once the session has a log directory)
Expand Down
Loading
Loading