Editor Integration
Dapper does not ship a one-click IDE extension. Instead, you keep the debugger
extension you already use and insert Dapper between the editor and the debug
adapter it launches. Once the adapter runs through dapper proxy, the CLI and
MCP agents can attach to the very same live session.
editor DAP client -> dapper proxy -> debug adapter -> debuggee
^
|
CLI / MCP agent
The wrapper pattern
Most editors launch their debug adapter as a child process and speak DAP to it
over stdin/stdout. The trick is to replace that adapter command with a thin
wrapper that execs dapper proxy process <real-adapter>:
- Dapper speaks DAP over stdio to the editor (no
--client-port). - Dapper spawns the real adapter and speaks DAP to it.
- Dapper's own logs go to stderr and a log file (
DAPPER_LOG_PATH, or a temp path), so stdout stays clean for the DAP stream.
How you point the editor at the wrapper depends on the extension: some expose a
setting for the adapter path, others let you override it per launch in
launch.json. Below are step-by-step setups for the two official VS Code
extensions.
What You Need
- A working
dapperbinary on yourPATH(see Installation). - The path to the real debug adapter the extension normally launches
(
lldb-dap, ordebugpy). - Permission to edit your VS Code
settings.jsonand/orlaunch.json.
dapper not found?VS Code launched from the GUI may not inherit your shell's PATH. If the editor
reports that the adapter failed to start, use the absolute path to dapper
in the scripts below (find it with which dapper).
VS Code: LLDB DAP extension
For the official llvm-vs-code-extensions.lldb-dap
extension, the adapter is launched from the path in the lldb-dap.executable-path
setting (or auto-detected). Point that setting at a wrapper script.
1. Create the wrapper script
Save this as, for example, ~/.dapper/lldb-dap-proxy.sh:
#!/usr/bin/env bash
# Run the real lldb-dap behind a Dapper proxy so the CLI and MCP agents
# can share this VS Code debug session.
set -euo pipefail
# The real lldb-dap binary. On macOS, xcrun resolves Xcode's copy;
# otherwise fall back to the first lldb-dap on PATH. Replace with an
# absolute path if neither applies.
REAL_LLDB_DAP="$(xcrun -f lldb-dap 2>/dev/null || command -v lldb-dap || true)"
if [[ -z "$REAL_LLDB_DAP" ]]; then
echo "lldb-dap not found; set REAL_LLDB_DAP to an absolute path." >&2
exit 1
fi
# Use an absolute path here if `dapper` is not on VS Code's PATH.
DAPPER_BIN="${DAPPER_BIN:-dapper}"
# Keep Dapper's logs off stdout (which carries the DAP stream).
export DAPPER_LOG_PATH="${TMPDIR:-/tmp}/dapper-lldb.log"
# `"$@"` forwards any extra args from `lldb-dap.arguments`.
# `exec` hands stdio and signals straight to the proxy.
exec "$DAPPER_BIN" proxy \
--scope-id="vscode-lldb" \
process "$REAL_LLDB_DAP" "$@"
Make it executable:
chmod +x ~/.dapper/lldb-dap-proxy.sh
2. Point the extension at the wrapper
In VS Code settings.json (User or Workspace), set the adapter path to your
script and keep server mode off:
{
"lldb-dap.executable-path": "/Users/you/.dapper/lldb-dap-proxy.sh",
"lldb-dap.serverMode": false
}
You can also set this from the Settings UI — search for "lldb-dap: Executable Path".
serverMode offWith lldb-dap.serverMode enabled (or a debugAdapterPort in your launch
config), the extension connects to lldb-dap over a TCP port and bypasses the
wrapped executable, so the proxy would not be in the path. Leave both unset.
3. Debug as usual
Your existing lldb-dap launch configurations work unchanged — the program,
args, and other fields still flow through to lldb-dap over DAP:
{
"type": "lldb-dap",
"request": "launch",
"name": "Debug (via Dapper)",
"program": "${workspaceFolder}/build/my_program"
}
Per-project alternative. Instead of the global setting, you can override the
adapter for a single launch with debugAdapterExecutable (do not also set
debugAdapterPort):
{
"type": "lldb-dap",
"request": "launch",
"name": "Debug (via Dapper)",
"program": "${workspaceFolder}/build/my_program",
"debugAdapterExecutable": "/Users/you/.dapper/lldb-dap-proxy.sh"
}
VS Code: Python Debugger (debugpy) extension
The official ms-python.debugpy
extension has no adapter-path setting. It always launches its adapter as
python <adapter-path> [--log-dir ...], and any interpreter override is
validated as a real Python install. That means a bash wrapper can't stand in for
the adapter — but a small Python shim can. Point the launch config's
debugAdapterPath at that shim, and it re-execs the proxy.
1. Install debugpy in your interpreter
The shim launches the adapter as python -m debugpy.adapter, so debugpy must
be importable by the interpreter you debug with:
python -m pip install debugpy
2. Create the Python adapter shim
Save this as, for example, ~/.dapper/debugpy-adapter-proxy.py:
#!/usr/bin/env python3
"""Run the debugpy DAP adapter behind a Dapper proxy.
The Python Debugger extension launches its adapter as:
<python> <this script> [--log-dir DIR]
so `sys.executable` is exactly the interpreter the extension selected.
We re-exec that as `dapper proxy process <python> -m debugpy.adapter`,
forwarding any extra args (e.g. --log-dir) through to the adapter.
"""
import os
import sys
# Fail early with a clear message if debugpy isn't available.
try:
import debugpy # noqa: F401
except ImportError:
sys.exit(
f"debugpy is not installed for {sys.executable}. "
f"Run: {sys.executable} -m pip install debugpy"
)
# Use an absolute path here if `dapper` is not on VS Code's PATH.
dapper_bin = os.environ.get("DAPPER_BIN", "dapper")
# Keep Dapper's logs off stdout (which carries the DAP stream).
os.environ.setdefault(
"DAPPER_LOG_PATH",
os.path.join(os.environ.get("TMPDIR", "/tmp"), "dapper-python.log"),
)
argv = [
dapper_bin, "proxy",
"--scope-id", "vscode-python",
"process",
sys.executable, "-m", "debugpy.adapter",
*sys.argv[1:],
]
try:
os.execvp(dapper_bin, argv)
except FileNotFoundError:
sys.exit(
f"Could not find '{dapper_bin}' on PATH. "
f"Set DAPPER_BIN to its absolute path."
)
3. Point launch.json at the shim
Add debugAdapterPath (a fully-qualified path) to your Python launch config:
{
"version": "0.2.0",
"configurations": [
{
"type": "debugpy",
"request": "launch",
"name": "Python: Current File (via Dapper)",
"program": "${file}",
"console": "integratedTerminal",
"debugAdapterPath": "/Users/you/.dapper/debugpy-adapter-proxy.py"
}
]
}
The debuggee still launches with your normal interpreter selection — only the adapter is routed through Dapper. Press F5 and the proxy starts automatically; there is no separate server to launch.
Unlike lldb-dap, the Python Debugger always runs its adapter through Python
(python <debugAdapterPath>) and checks that the adapter interpreter is a valid
Python install. A bash script would be handed to Python and rejected, so the
override has to be a Python file.
Verify the session is shared
Once the editor has started a debug session through Dapper, confirm Dapper can see it from another terminal:
dapper debug sessions
When exactly one session is active, the CLI and MCP server discover it automatically. With several active sessions, pin one deterministically with its control port:
dapper debug --control-port=47823 threads
dapper mcp --control-port=47823
The --scope-id baked into each wrapper (vscode-lldb, vscode-python) groups
sessions and lets agents auto-pair, but --control-port is the most precise
when multiple sessions are live. See Sessions for the
full targeting model and dapper proxy for other proxy
modes (TCP, Unix-domain socket, headless from-config).