Skip to content

Add start_debugging_with_config tool for inline-config launches#99

Open
ksa-real wants to merge 2 commits into
microsoft:mainfrom
ksa-real:feat/start-debugging-with-config
Open

Add start_debugging_with_config tool for inline-config launches#99
ksa-real wants to merge 2 commits into
microsoft:mainfrom
ksa-real:feat/start-debugging-with-config

Conversation

@ksa-real

Copy link
Copy Markdown
Contributor

Summary

Adds a language-agnostic start_debugging_with_config tool that lets an agent start a debug session from a raw, caller-supplied DebugConfigurationno launch.json entry required.

The existing start_debugging tool launches a named configuration that must already exist in the workspace's launch.json. That's a hard blocker for the common agent workflow of "debug this arbitrary program/command right now," and it forces users to hand-edit launch.json for one-off sessions.

This tool forwards the config straight to vscode.debug.startDebugging, keeping the extension a thin shim while the caller owns all toolchain/runtime specifics:

  • TypeScript: { "type":"node", "request":"launch", "program":"<abs .ts>", "runtimeExecutable":"tsx" }
  • Python: { "type":"python", "request":"launch", "program":"<abs .py>" }
  • Attach: { "type":"node", "request":"attach", "port":9229 } (e.g. a node --inspect-brk process)

Adapter-specific fields (args, env, cwd, console, port, stopOnEntry, …) are passed through unchanged. Minimal validation only: type and request are required; name defaults when omitted.

Tests

startDebuggingMatrix.test.ts covers:

  • config forwarded verbatim (no fields injected or stripped)
  • clean run returns terminated
  • missing name is defaulted rather than failing
  • missing type and invalid request are rejected
  • a failed launch is surfaced

Notes

ksa-real added 2 commits June 24, 2026 10:26
Expose a language-agnostic launcher that forwards a raw, caller-supplied
DebugConfiguration straight to vscode.debug.startDebugging — no launch.json
entry required. The caller owns all toolchain/runtime choices (e.g.
runtimeExecutable:"tsx" for TypeScript, debugpy for Python, request:"attach"
to a node --inspect-brk process); the extension stays a thin shim. Adds
minimal validation (type and request required, name defaulted).
Cover config-forwarded-verbatim (no fields injected or stripped), clean-run
"terminated", missing-name defaulting, missing-type and invalid-request
validation, and failed-launch surfacing.
@ksa-real

ksa-real commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Concern: the loopback server is unauthenticated

Not specific to this PR, but it broadens the blast radius so worth raising here.

Loopback binding + Host/Origin checks block remote and browser (DNS-rebinding) access, but nothing authenticates the endpoint itself — the trust boundary is just "can open 127.0.0.1:3001." So any local process (or any user on a shared host) can drive the full tool surface without credentials, and that surface is code execution by design: start_debugging_with_config launches an arbitrary runtimeExecutable/command, start_debugging runs workspace code, evaluate_expression evals in the debuggee. Net effect: unauthenticated local RCE in the user's context — and fully network-exposed if debugmcp.bindHost is ever set non-loopback (only a console warning guards that).

Mitigation (standard path, not bespoke). The server must stay in the extension host (it needs vscode.debug.*), so a stdio transport isn't an option here — even McpStdioServerDefinition spawns a separate child process with no access to the debugger. But the documented HTTP-auth model fits: require a per-session bearer token.

  • VS Code / Copilot: supply it via McpHttpServerDefinition + resolveMcpServerDefinition, where the editor injects the Authorization header (docs).
  • Cursor / Codex / Claude (no provider API): write the same token into the MCP config the extension already generates.

@ksa-real ksa-real left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up to my note above: there's now a working implementation of the per-install bearer-token approach on feat/mcp-auth-token.

What it does:

  • The HTTP server requires a per-install token (randomUUID, persisted in SecretStorage). Any request with a missing/invalid token gets 401 (constant-time compare; token accepted via Authorization: Bearer header or ?token= query param).
  • The token is delivered to the editor's built-in MCP client automatically, so no manual config:
    • VS Code / Copilot: McpHttpServerDefinition + resolveMcpServerDefinition (editor injects the Authorization header).
    • Cursor: vscode.cursor.mcp.registerServer, with the token carried in the URL query to work around Cursor's known dropped-headers bug (header is still sent for when the upstream fix lands).
    • Other agents: the same token is written into the MCP config files the extension already generates.
  • debugmcp.bindHost warning updated to reflect that the server is token-gated but still widens the attack surface off loopback; added tests for the 401 path and token extraction.

Verified end-to-end in Cursor (registers via the extension API, unauthenticated/wrong-token requests return 401, valid token drives the full tool surface). Happy to open a PR if there's interest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant