https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/212821
The docs still describe lldb-mcp as a thin stdio-to-socket bridge that auto-launches an LLDB and exposes a single lldb_command tool. It is now a full featured multiplexer hosting its own sessions, with four tools and pid-qualified URIs. Also restructure the documentation around how it is used, with an explanation of the underlying architecture towards the end. >From 3f2becd5f74dbfbc05a5d7b82f02b4211f30356f Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Wed, 29 Jul 2026 09:53:34 -0700 Subject: [PATCH] [lldb][docs] Rewrite the MCP documentation for lldb-mcp The docs still describe lldb-mcp as a thin stdio-to-socket bridge that auto-launches an LLDB and exposes a single lldb_command tool. It is now a full featured multiplexer hosting its own sessions, with four tools and pid-qualified URIs. Also restructure the documentation around how it is used, with an explanation of the underlying architecture towards the end. --- lldb/docs/use/mcp.md | 270 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 214 insertions(+), 56 deletions(-) diff --git a/lldb/docs/use/mcp.md b/lldb/docs/use/mcp.md index 452a6e4d2fabb..2580e9d283ea0 100644 --- a/lldb/docs/use/mcp.md +++ b/lldb/docs/use/mcp.md @@ -8,89 +8,154 @@ memory, step through code. This can range from helping you run a specific command you cannot immediately remember, to a fully agent-driven debugging experience. -## MCP Server +## Getting Started -To start the MCP server in LLDB, use the `protocol-server start` command. -Specify `MCP` as the protocol and provide a URI to listen on. For example, to -start listening for local TCP connections on port `59999`, use the following -command: +LLDB ships with `lldb-mcp`, a binary that speaks MCP over standard input and +output (stdio). Point your MCP client at it, and you are all set. + +Configuration example for [Claude Code](https://modelcontextprotocol.io/quickstart/user): ``` -(lldb) protocol-server start MCP listen://localhost:59999 -MCP server started with connection listeners: connection://[::1]:59999, connection://[127.0.0.1]:59999 +claude mcp add lldb --transport stdio -- /path/to/lldb-mcp ``` -The server will automatically stop when exiting LLDB, or it can be stopped -explicitly with the `protocol-server stop` command. +Configuration example (`mcp.json`) for [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers): +```json +{ + "servers": { + "lldb": { + "type": "stdio", + "command": "/path/to/lldb-mcp" + } + } +} ``` -(lldb) protocol-server stop MCP + +The MCP client launches one `lldb-mcp` process per connection and shuts it down +when it disconnects, taking any session it created with it. + +## Tools + +Tools are a primitive in the Model Context Protocol that enable servers to +expose functionality to clients. `lldb-mcp` exposes four. + +### `session_create` + +Creates a new debug session and returns its URI. This is equivalent to +launching a new instance of `lldb` on the command line. Sessions look like +this: + +``` +lldb-mcp://instance/{pid}/debugger/{id} ``` -The commands will fail if a server is already running or not running -respectively. +The `pid` identifies the process hosting the session and the `id` identifies +the debugger inside it. Pass the whole URI back to the other tools. + +### `command` + +Runs an LLDB command in a debug session and returns its output, the same text +you would see in the LLDB command interpreter. It takes: -## MCP Client +- `command` (required): the command to run, for example `breakpoint set --name main`. +- `debugger` (optional): the URI of the session to run it in. When omitted, the + command runs in the first session `lldb-mcp` created. -MCP uses standard input/output (stdio) for communication between client and -server. The exact configuration depends on the client, but most applications -allow you to specify an MCP server as a binary and arguments. LLDB ships with -`lldb-mcp`, a small helper that bridges stdio to LLDB's MCP server socket. +Commands run one at a time and the result comes back when the command finishes. + +### `sessions_list` + +Lists every debug session reachable from this `lldb-mcp`, one URI per line. +That includes sessions it created itself and sessions in LLDB instances running +elsewhere on the machine (see [Attaching to a Running LLDB](#attaching-to-a-running-lldb)). + +### `session_close` + +Closes a session and frees its resources. It takes a single required `session` +argument, the URI to close. Only sessions that `lldb-mcp` created can be closed +this way. An interactive LLDB that a person is using belongs to that person, so +closing it is refused. + +## A Typical Session + +Creating a session, debugging in it, and cleaning up looks like this: ``` -┌──────────┐ ┌──────────┐ ┌──────────┐ -│ │ │ │ │ │ -│ LLDB ├─────socket────┤ lldb-mcp ├─────stdio─────┤MCP Client│ -│ │ │ │ │ │ -└──────────┘ └──────────┘ └──────────┘ +session_create -> lldb-mcp://instance/4711/debugger/1 +command "target create /tmp/hello" -> Current executable set to '/tmp/hello' (arm64). +command "breakpoint set --name add" -> Breakpoint 1: 4 locations. +command "run" -> Process 4713 stopped + * thread #1, stop reason = breakpoint 1.1 + frame #0: hello`add(a=2, b=3) at hello.c:2 +command "frame variable" -> (int) a = 2 + (int) b = 3 +command "continue" -> Process 4713 exited with status = 0 +session_close -> deleted lldb-mcp://debugger/1 ``` -`lldb-mcp` automatically discovers a running LLDB MCP server, so there is no -need to specify a port. If no server is running, it will launch `lldb` in the -background and connect to it. The `lldb` binary located next to `lldb-mcp` is -used by default; set the `LLDB_EXE_PATH` environment variable to override this. +:::{note} +Sessions start in asynchronous mode, where `run` and `continue` return before +the process actually stops. Commands that need a stopped process then fail with +"Command requires a process which is currently stopped". Run +`script lldb.debugger.SetAsync(False)` once, right after `session_create`, to +get the synchronous behavior shown above. +::: -Configuration example for [Claude Code](https://modelcontextprotocol.io/quickstart/user): +The debuggee's own output does not come back through MCP. Only debugger output +does. Redirect the program's output to a file and read it back if you need it. + +## Attaching to a Running LLDB + +Besides the sessions it creates, `lldb-mcp` can drive LLDB instances you are +already using, so an agent can inspect and steer the exact session you have in +front of you. + +In that LLDB, start an MCP server: ``` -claude mcp add --transport stdio -- lldb-mcp /path/to/lldb-mcp +(lldb) protocol-server start MCP +MCP server started with connection listeners: connection://[::1]:59999, connection://[127.0.0.1]:59999 ``` -Configuration example (`mcp.json`) for [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers): +The server picks a free port on localhost by default. To listen somewhere +specific, pass a URI, either `listen://[host]:port` for TCP or +`accept:///path/to/socket` for a Unix domain socket: -```json -{ - "servers": { - "lldb": { - "type": "stdio", - "command": "/path/to/lldb-mcp" - } - } -} +``` +(lldb) protocol-server start MCP listen://localhost:59999 ``` -## Tools +The server stops when LLDB exits, or explicitly: -Tools are a primitive in the Model Context Protocol that enable servers to -expose functionality to clients. +``` +(lldb) protocol-server stop MCP +``` + +`protocol-server get MCP` reports where a running server is listening. Starting +a server when one is already running, or stopping one that is not, is an error. -LLDB's MCP integration exposes one tool, named `lldb_command` which allows the -model to run the same commands a user would type in the LLDB command -interpreter. It takes two arguments: +Once the server is up, that LLDB's sessions show up in `sessions_list` and +accept `command`, exactly like sessions `lldb-mcp` created. You do not need to +configure a port anywhere: each LLDB with a running MCP server records itself in +`~/.lldb`, and `lldb-mcp` finds it there. -1. The unique debugger ID as a number. -2. The command and its arguments as a string. +:::{note} +Discovery happens once, when `lldb-mcp` starts. An LLDB you launch afterwards is +not picked up until the client reconnects to the MCP server, which usually means +restarting or reloading the MCP server in your client. +::: ## Resources Resources are a primitive in the Model Context Protocol that allow servers to -expose content that can be read by clients. +expose content that can be read by clients. `lldb-mcp` exposes one resource per +debugger and one per target, across every session it can reach. -LLDB's MCP integration exposes a resource for each debugger and target -instance. Debugger resources are accessible using the following URI: +Debugger resources use the following URI: ``` -lldb://debugger/<debugger id> +lldb://instance/<pid>/debugger/<debugger id> ``` Example output: @@ -99,7 +164,7 @@ Example output: { "contents": [ { - "uri": "lldb://debugger/1", + "uri": "lldb://instance/4711/debugger/1", "mimeType": "application/json", "text": "{\"debugger_id\":1,\"name\":\"debugger_1\",\"num_targets\":1}" } @@ -111,7 +176,7 @@ Debuggers can contain one or more targets, which are accessible using the following URI: ``` -lldb://debugger/<debugger id>/target/<target idx> +lldb://instance/<pid>/debugger/<debugger id>/target/<target idx> ``` Example output: @@ -120,9 +185,9 @@ Example output: { "contents": [ { - "uri": "lldb://debugger/1/target/0", + "uri": "lldb://instance/4711/debugger/1/target/0", "mimeType": "application/json", - "text": "{\"arch\":\"arm64-apple-macosx26.0.0\",\"debugger_id\":1,\"dummy\":false,\"path\":\"/bin/count\",\"platform\":\"host\",\"selected\":true,\"target_idx\":0}" + "text": "{\"arch\":\"arm64-apple-macosx26.0.0\",\"debugger_id\":1,\"dummy\":false,\"path\":\"/tmp/hello\",\"platform\":\"host\",\"selected\":true,\"target_idx\":0}" } ] } @@ -133,9 +198,102 @@ stable and may be reused when a target is removed and a new target is added. ## Troubleshooting -The MCP server uses the `Host` log channel. You can enable logging with the -`log enable` command. +**"no debugger found" from `command`.** There is no session to run the command +in. Call `session_create` first, or pass the URI of an existing session. + +**"Command requires a process which is currently stopped".** The session is in +asynchronous mode. Run `script lldb.debugger.SetAsync(False)` in it. + +**"can only close sessions that lldb-mcp created".** `session_close` refuses to +tear down an interactive LLDB. Quit that LLDB yourself. + +**A running LLDB does not show up in `sessions_list`.** Either its MCP server is +not running, which `protocol-server get MCP` will tell you, or it started after +`lldb-mcp` did. Restart the MCP server in your client to rediscover. + +To see the JSON-RPC traffic between your client and `lldb-mcp`, set +`LLDB_MCP_LOG` in the environment. Messages are written to stderr, since stdout +carries the protocol. + +The MCP server inside LLDB logs to the `Host` log channel: ``` (lldb) log enable lldb host ``` + +## Implementation + +This section covers how the pieces fit together, for those working on LLDB +itself. + +`lldb-mcp` is a multiplexer. It presents a single MCP server to the client and +fans out to one or more backends, each an LLDB MCP server reached over a socket +and identified by the pid of the process hosting it. + +``` + ┌──────────┐ + │ LLDB │ + └────┬─────┘ + │ socket + │ +┌──────────┐ ┌─────┴─────┐ ┌──────────┐ +│ in-proc ├────socket────┤ lldb-mcp ├─────stdio────┤MCP Client│ +│ LLDB │ └─────┬─────┘ └──────────┘ +└──────────┘ │ socket + │ + ┌────┴─────┐ + │ LLDB │ + └──────────┘ +``` + +There are two kinds of backend. The **local** backend is an MCP server that +`lldb-mcp` starts inside its own process, through `SBProtocolServer`. The +sessions it hosts are the ones `session_create` makes. **Remote** backends are +the separate LLDB processes discovered through the registry. Both are driven the +same way, over a socket through an `mcp::Client`, which keeps the tool +implementations in one place rather than special-casing the in-process path. + +Requests are dispatched three ways. `initialize` and `tools/list` are answered +by the multiplexer directly. `sessions_list` and `resources/list` fan out to +every live backend and aggregate, keyed by pid so output is deterministic. A +backend that fails or has disconnected is omitted rather than failing the whole +listing. `command`, `resources/read`, and `session_close` are routed to a single +backend by the pid parsed out of the URI. + +Backends only know their own local `lldb-mcp://debugger/{id}` and +`lldb://debugger/{id}` URIs. The multiplexer rewrites them into the +instance-qualified form in both directions, so a client never sees an ambiguous +id and a backend never sees a pid it does not understand. + +`session_create` and `session_close` map onto the `debugger_create` and +`debugger_delete` tools on the local backend. Session ownership is enforced by +comparing the pid in the URI against the local backend's, which is why closing +someone else's session is refused. Running a command in one is not: any +`lldb-mcp` on the machine can drive any discovered session. + +### Discovery + +An LLDB that starts an MCP server writes `~/.lldb/lldb-mcp-<pid>.json`, +recording the pid and the URI to connect to. The entry is written only once the +server is listening, and removed on a clean exit. `lldb-mcp` reads the directory +at startup and connects to each entry, pruning any that fails to connect, since +that means the instance died without cleaning up. `lldb-mcp` registers itself +too, so its managed sessions are visible to other `lldb-mcp` processes. + +### Adding Tools and Resources + +The tool and resource-provider set lives in +`lldb/source/Plugins/Protocol/MCP/` and is installed by +`lldb_private::mcp::PopulateServer`. Sharing one installer keeps every MCP +server consistent, whether it runs in the plugin or is hosted in-process by an +embedder. Adding a tool means subclassing `lldb_protocol::mcp::Tool`, and adding +a resource means subclassing `lldb_protocol::mcp::ResourceProvider`, then +registering it there. + +A tool added this way is exposed by the LLDB MCP server, not automatically by +`lldb-mcp`. Because the multiplexer owns the client-facing surface, it also +needs a case in `HandleToolsList` and `HandleToolsCall`, plus a routing decision +if its arguments carry a URI. + +Note that the protocol version LLDB implements is `2024-11-05`, which has no +structured content. Tools return their output as text. _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
