From d56238e693c29b3ffc6715c2daac4f2d430a3176 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 11:17:01 +0200 Subject: [PATCH 01/20] add mcp in debugger session --- .github/workflows/pr-verify.yml | 2 + .vscode/launch.json | 16 + CHANGELOG.md | 13 + DevelopersReadme.md | 7 + HELP.MD | 129 ++ apps/erlangbridge/src/mcp/mcp_audit.erl | 19 + apps/erlangbridge/src/mcp/mcp_encoder.erl | 144 +++ apps/erlangbridge/src/mcp/mcp_policy.erl | 496 ++++++++ apps/erlangbridge/src/mcp/mcp_runtime.erl | 1079 +++++++++++++++++ apps/erlangbridge/src/mcp/mcp_server.erl | 504 ++++++++ apps/erlangbridge/src/mcp/mcp_store.erl | 318 +++++ apps/erlangbridge/src/mcp/mcp_sup.erl | 129 ++ apps/erlangbridge/src/mcp/mcp_tools.erl | 231 ++++ apps/erlangbridge/src/vscode_connection.erl | 90 +- .../test/mcp/mcp_bridge_SUITE.erl | 282 +++++ .../mcp_attach_target.erl | 5 + .../test/mcp/mcp_capture_logger.erl | 8 + .../test/mcp/mcp_encoder_SUITE.erl | 117 ++ .../erlangbridge/test/mcp/mcp_fixture_sup.erl | 67 + .../test/mcp/mcp_fixture_worker.erl | 38 + .../test/mcp/mcp_policy_SUITE.erl | 251 ++++ .../test/mcp/mcp_server_SUITE.erl | 1011 +++++++++++++++ .../erlangbridge/test/mcp/mcp_store_SUITE.erl | 131 ++ images/vscode-erlang-mcp-running.png | Bin 0 -> 675072 bytes lib/ErlangConfigurationProvider.ts | 31 +- lib/ErlangShellDebugger.ts | 50 +- lib/GenericShell.ts | 7 +- lib/erlangDebugConnection.ts | 26 +- lib/erlangDebugSession.ts | 103 +- lib/erlangSettings.ts | 16 + lib/extension.ts | 165 ++- mcp-tasks.md | 623 ++++++++++ mcp_security.md | 58 + package.json | 36 + rebar.config | 2 + test/test-suite/mcp/mcp_configuration.test.ts | 93 ++ test/test-suite/mcp/mcp_debug_adapter.test.ts | 263 ++++ 37 files changed, 6546 insertions(+), 14 deletions(-) create mode 100644 apps/erlangbridge/src/mcp/mcp_audit.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_encoder.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_policy.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_runtime.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_server.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_store.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_sup.erl create mode 100644 apps/erlangbridge/src/mcp/mcp_tools.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_bridge_SUITE.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_bridge_SUITE_data/mcp_attach_target.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_capture_logger.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_fixture_sup.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_fixture_worker.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_server_SUITE.erl create mode 100644 apps/erlangbridge/test/mcp/mcp_store_SUITE.erl create mode 100644 images/vscode-erlang-mcp-running.png create mode 100644 mcp-tasks.md create mode 100644 mcp_security.md create mode 100644 test/test-suite/mcp/mcp_configuration.test.ts create mode 100644 test/test-suite/mcp/mcp_debug_adapter.test.ts diff --git a/.github/workflows/pr-verify.yml b/.github/workflows/pr-verify.yml index 01c45033..2d6a3048 100644 --- a/.github/workflows/pr-verify.yml +++ b/.github/workflows/pr-verify.yml @@ -55,3 +55,5 @@ jobs: - name: Erlang Common Test suites run: ./rebar3 ct + - name: Erlang Common Test suites (MCP inspector) + run: ./rebar3 ct --dir apps/erlangbridge/test/mcp diff --git a/.vscode/launch.json b/.vscode/launch.json index 1296b590..73238995 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -47,6 +47,22 @@ "outFiles": ["${workspaceFolder}/out/**/*.js"], "preLaunchTask": "npm: compile" }, + { + // Only the extension under development + what is installed in the dedicated + // extensions dir (your normal extensions are not loaded). Install Claude Code there once: + // code --extensions-dir ~/.vscode-erlang-dev/extensions --install-extension anthropic.claude-code + "name": "Launch Extension (sample-mcp, Claude only)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "${workspaceFolder}/../../erlang/sample-mcp", + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/out"], + "preLaunchTask": "npm: compile" + }, { "name": "Launch Tests", "type": "extensionHost", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0e154f..2e89a112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change log +## Unreleased + +* MCP topology inspector for debug sessions (opt-in) + - New settings `erlang.mcp.enabled` (default `false`), `erlang.mcp.host` (loopback only) and `erlang.mcp.port` (`0` = any free port). When enabled, a debug session (launch or attach, not *Run without debugging*) starts a read-only, token-authenticated [MCP](https://modelcontextprotocol.io) server inside the debugged Erlang node, so that an AI agent can map its OTP topology: `runtime_summary`, `application_overview`, `supervision_tree`, `registered_processes`, `process_info`, `ets_tables` (approved tables, metadata only) and `debug_session`. Results are bounded, paginated, with typed edges, evidence, confidence and explicit coverage limitations + - VS Code registers the live endpoint automatically (MCP server list) and **Erlang MCP: Show Connection Details** helps configure other clients. The session token never appears in logs, DAP events or settings + - An optional top-level `{mcp, [...]}` term in the project's `rebar.config` restricts tools, ETS tables and limits. Nothing changes when the setting is off. See [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) and [mcp_security.md](./mcp_security.md) + - Agent-sized answers: graph tools default to `detail: "summary"` (compact entities and edges, 100 items per page, modules listed per application only); `detail: "full"` returns the complete JSON (e.g. to save it to a file), `pageSize` tunes the pages + - **Output > Erlang MCP** journals every MCP request (tool, status, inspector time, size, pages, delay since the previous request) and summarizes the session, to see how an agent uses the inspector; arguments, results and token are never written + - Optional user setting `erlang.mcp.authToken` (development convenience): a fixed token so that a static client such as Claude Code (`claude mcp add`) is configured once; default empty = random token per session + - The verbose debug log no longer prints the Erlang cookie passed to an attach helper + +--- + ## Version 1.2.5 (September 27, 2026) * [358](https://github.com/pgourlain/vscode_erlang/issues/358) : Launch (Run / Debug) broken on Linux and macOS since 1.2.4 diff --git a/DevelopersReadme.md b/DevelopersReadme.md index aec01725..b7861a6d 100644 --- a/DevelopersReadme.md +++ b/DevelopersReadme.md @@ -26,8 +26,15 @@ then ```bash ./rebar3 ct +./rebar3 ct --dir apps/erlangbridge/test/mcp # MCP inspector suites (policy, server, debugger bridge) +npm test # includes out/test/test-suite/mcp/* (real erl target) ``` +The embedded MCP inspector lives in `apps/erlangbridge/src/mcp/` (`mcp_*.erl`, all Erlang) and is +only compiled to `_build/default/lib/ebin` by the debug adapter and started on request +(`vscode_connection`: `mcp_start`, `mcp_renew`, `mcp_stop`); the LSP node never loads it. Its +tests/fixtures are in `apps/erlangbridge/test/mcp/` and `test/test-suite/mcp/`. + ## build package - vsce package : 'vscode:prepublish' is executed diff --git a/HELP.MD b/HELP.MD index c35cb65a..03dc9d8e 100644 --- a/HELP.MD +++ b/HELP.MD @@ -121,6 +121,135 @@ Restart the language server after changing it. the same machine, with the `debugger` and `inets` applications available - see [Attach to a running node](./README.md#attach-to-a-running-node). +## MCP topology inspector (AI agents, debug sessions only) + +An opt-in, **read-only** [MCP](https://modelcontextprotocol.io) server that lets an AI agent +discover and explain the OTP topology (applications, supervisors, processes, modules, approved +ETS metadata) of the Erlang node you are debugging. It is a structural map, not a trace of +messages or business flows. Security model and residual risks: [mcp_security.md](./mcp_security.md). + +**Turn it on** (folder-scoped, default `false`) and start a *debug session* (launch or attach): + +```json +{ + "erlang.mcp.enabled": true, + "erlang.mcp.host": "127.0.0.1", + "erlang.mcp.port": 0 +} +``` + +- It starts only inside the debugged node, only for a real debug session (never in a normal + editor session, with *Run without debugging*, or from rebar/eunit) and stops with the session. + Settings are read per debug folder when the session starts; changing them does not affect a + running session. +- `host` must be loopback (`127.0.0.1`, `::1`, ...); `0.0.0.0`, LAN and public addresses are + rejected. `port: 0` (default) lets the OS choose. +- Any MCP problem (bad policy, busy port, untrusted workspace) is reported and disables **only** the + inspector; debugging continues. There is no "required" option. +- Same machine only: the client must reach the node's loopback; no port forwarding is supported. + Attach needs the `crypto` application in the target in addition to `debugger` and `inets`. + +**Connect a client** + +- *VS Code* (1.101+, MCP provider API): the running session appears as an MCP server named + `Erlang debug: ` in the MCP server list (*MCP: List Servers*); the per-session token is + handed to VS Code automatically and never printed. It disappears when the session ends. +- *Claude Code (and other static clients)*: Claude Code does not read VS Code's MCP list. For a + one-time setup, set the **user** setting `erlang.mcp.authToken` to a fixed development token + (16-128 chars `A-Za-z0-9._~-`, not read from workspace settings) and a fixed `erlang.mcp.port`, then once: + `claude mcp add --transport http erlang-debug http://127.0.0.1:/mcp --header "Authorization: Bearer "`. + The server is only reachable during a debug session. A fixed token is a convenience that trades + away the per-session credential: anyone knowing it who can reach the loopback port can read the + inspector's metadata. Leave it empty (default) for a fresh random token per session. +- *Any other client*: run **Erlang MCP: Show Connection Details** (select the session if several) + and choose *Copy URL* / *Copy Authorization header* (explicit action; nothing is copied + automatically). Set a fixed `erlang.mcp.port` if the client needs a stable URL: the token + still changes at every session and must be supplied as `Authorization: Bearer `. If the + fixed port is busy MCP is disabled for the session (no silent rebinding). +- Protocol: MCP `2025-06-18`, Streamable HTTP with JSON responses (no SSE, no prompts, no resources). + +**Project policy (optional)** - a literal top-level term in the *target project's* `rebar.config` +(nearest file from the debug `cwd`, never above the workspace folder; no `rebar.config.script`, +no profiles). It controls tools, tables and *lower* limits only; activation, host, port and +credentials come only from the VS Code settings. The values below are the defaults and the hard +ceilings: + +```erlang +{mcp, [ + {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, + <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, + <<"debug_session">>]}, + {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>]; private tables never + {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, + {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, + {max_concurrency, 2}, {request_timeout_ms, 3000}, {collection_ttl_ms, 30000}, + {max_collection_bytes, 1048576}, {max_collections, 2}]} +]}. +``` + +A malformed or duplicated term, an `mcp` term only inside a profile, or keys such as `enabled`, +`host`, `port`, `required` or a token disable MCP for that session with an explanation. + +**Tools** (all read-only, strict input schemas, discoverable through `tools/list`) + +| Tool | Purpose | +| --- | --- | +| `runtime_summary` | OTP/ERTS versions, node (host redacted by default), uptime, schedulers, process count, memory, session id | +| `application_overview` | started applications, versions, declared dependencies, root supervisor ids, optional module names/behaviours; `application`, `includeModules`, `cursor` | +| `supervision_tree` | typed `supervises` edges from an application/supervisor id or a named supervisor; child role, modules, restart/shutdown; `id`/`supervisor`, `maxDepth`, `includeModules`, `cursor` | +| `registered_processes` | locally registered names with status/queue length and membership (`confirmed`/`inferred`/`unknown`); `application`, `cursor` | +| `process_info` | allowlisted fields of one process (by `id`, `name` or `pid`), links/monitors as ids; never dictionary, stack, mailbox or state | +| `ets_tables` | metadata of the approved named tables (protection, type, owner, size, memory in words and bytes) - never keys or values | +| `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line) | + +Graph tools also take `detail` and `pageSize`. `detail: "summary"` (default) is meant for agents: +same entities, ids and edges, without rarely needed fields (pids, descriptions, per-edge +`observedAt`, coverage limitations after the first page), 100 items per page, and module lists only +for one selected application. `detail: "full"` returns every field in the largest pages (policy +`max_items`): ask the agent for it when you want the complete JSON, e.g. to save it to a file. + +Graph tools return a versioned envelope: `schemaVersion`, `sessionId`, `collectionId`, +`startedAt`, `finishedAt`, `scope` (with `limitations`), `entities`, `relationships`, +`complete`, `truncated`, `omissions`, `nextCursor`. Entity kinds: `application`, `process`, +`module`, `ets_table` (a supervisor is a process with `role: supervisor`). Edge types: +`depends_on`, `supervises`, `owns_table`, `linked_to`, `monitors`, `belongs_to`, `uses_module`, each +with `evidence`, `observedAt` and `confidence` (`confirmed`/`inferred`/`unknown`). Links and +monitors never imply supervision or message traffic. IDs are opaque, valid only within the session +and change when a process restarts or a table is recreated. A collection is an observation +interval, not an atomic snapshot: absence from a partial collection is not proof of termination. +Units: timestamps are UTC ISO-8601, `memory` of a process is bytes, of a table words +(`memoryBytes` is derived), `uptimeMs` is milliseconds. + +`omissions` (`policy_denied`, `disappeared`, `timeout`, `unavailable_while_paused`, `limit_reached`, +`unknown_membership`) explain what is missing and whether it is resumable; cursors are bound to +the session, the collection, the tool and the arguments, and return `cursor_expired` after the +retention time - a traversal is never silently restarted. Partial example (a supervisor blocked at +a breakpoint or busy): + +```json +{ "complete": false, "truncated": false, "nextCursor": null, + "omissions": [ { "reason": "timeout", "count": 1, "scope": "p_51ad...", + "resumable": false, "message": "the supervisor did not answer in time; its children are missing from this map" } ], + "relationships": [ { "type": "supervises", "from": "p_0a1...", "to": "p_51ad...", "confidence": "confirmed", + "evidence": "supervisor:which_children/1", "observedAt": "2026-01-01T10:00:00.000Z" } ] } +``` + +**Agent workflow**: `runtime_summary` + `application_overview` -> pick an application, follow its +root ids into `supervision_tree` (expand subtrees with the ids in `limit_reached` omissions) -> +enrich with `process_info`, `registered_processes`, `ets_tables`, `debug_session` -> join by ids, +keep confirmed edges apart from inferences, and report the omissions. Module names can then be +correlated with the workspace/LSP tools; the inspector never returns source or code paths. + +**Request journal**: every MCP request of the session is written to the **Output > Erlang MCP** +channel - request number, tool, status, time spent in the inspector, response size, entities / +relationships, page and cursor use, and the delay since the previous request (time spent by the +agent) - with a summary when the session ends. Arguments, results and the token are never written. + +**Breakpoints**: inspection never resumes, suspends or alters application processes. A process +stopped at a breakpoint still answers `process_info`; a supervisor that cannot answer in time +yields a `timeout` omission (never a guess that it is paused). The target stops the inspector +within ~10 seconds if the debug adapter stops renewing its lease. + ## Changing the debugger mode has no effect `erlang.debuggerRunMode` is read at extension startup: restart VS Code after diff --git a/apps/erlangbridge/src/mcp/mcp_audit.erl b/apps/erlangbridge/src/mcp/mcp_audit.erl new file mode 100644 index 00000000..477c9db6 --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_audit.erl @@ -0,0 +1,19 @@ +%% Structured lifecycle/security events of the MCP inspector. +%% Only event names and bounded, non-sensitive fields are logged: never the +%% token, request parameters or returned runtime data. +-module(mcp_audit). + +-export([event/2]). + +-define(ALLOWED_FIELDS, [tool, status, duration_ms, reason, method, code, port, host_ok, count]). + +event(Kind, Fields) when is_atom(Kind), is_map(Fields) -> + Safe = maps:with(?ALLOWED_FIELDS, Fields), + try + case erlang:function_exported(logger, info, 2) of + true -> logger:info("mcp: ~p ~p", [Kind, Safe]); + false -> error_logger:info_msg("mcp: ~p ~p~n", [Kind, Safe]) + end + catch _:_ -> ok + end, + ok. diff --git a/apps/erlangbridge/src/mcp/mcp_encoder.erl b/apps/erlangbridge/src/mcp/mcp_encoder.erl new file mode 100644 index 00000000..7be2b47a --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_encoder.erl @@ -0,0 +1,144 @@ +%% Bounded conversion of Erlang terms to JSON-encodable terms (binary keys, +%% binaries, numbers, booleans, null, lists, maps). Never creates atoms, never +%% exposes an executable handle: funs, ports, references and pids become text. +-module(mcp_encoder). + +-export([term/2, text/2, safe_binary/2, hex/1, iso8601/1, pid_text/1]). + +-define(SECRET_WORDS, ["password", "passwd", "secret", "token", "cookie", + "credential", "apikey", "api_key", "private_key", "auth"]). + +%% Limits: #{max_depth, max_items, max_binary_bytes} +term(Term, Limits) -> + enc(Term, 0, Limits). + +%% Short printable text of any term, bounded. +text(Term, Limits) -> + Max = maps:get(max_binary_bytes, Limits, 4096), + Depth = maps:get(max_depth, Limits, 16), + Str = try lists:flatten(io_lib:format("~0P", [Term, Depth])) + catch _:_ -> "" + end, + safe_binary(unicode:characters_to_binary(Str, unicode, utf8), Max). + +enc(_, Depth, #{max_depth := Max}) when Depth > Max -> + <<"">>; +enc(T, _, _) when is_integer(T) -> T; +enc(T, _, _) when is_float(T) -> T; +enc(true, _, _) -> true; +enc(false, _, _) -> false; +enc(undefined, _, _) -> null; +enc(T, _, _) when is_atom(T) -> + safe_binary(atom_to_binary(T, utf8), 255); +enc(T, _, #{max_binary_bytes := Max}) when is_binary(T) -> + binary(T, Max); +enc(T, _, _) when is_pid(T) -> pid_text(T); +enc(T, _, _) when is_port(T) -> #{<<"port">> => list_bin(erlang:port_to_list(T))}; +enc(T, _, _) when is_reference(T) -> #{<<"ref">> => list_bin(erlang:ref_to_list(T))}; +enc(T, _, _) when is_function(T) -> + %% Descriptive only. The fun itself is never reachable from the result. + case erlang:fun_info(T, module) of + {module, M} -> #{<<"fun">> => safe_binary(atom_to_binary(M, utf8), 255)}; + _ -> #{<<"fun">> => <<"unknown">>} + end; +enc(T, Depth, Limits) when is_tuple(T) -> + #{<<"tuple">> => enc_list(tuple_to_list(T), Depth + 1, Limits)}; +enc(T, Depth, Limits) when is_map(T) -> + Max = maps:get(max_items, Limits), + Pairs = lists:sublist(lists:sort(maps:to_list(T)), Max), + #{<<"map">> => [#{<<"key">> => enc(K, Depth + 1, Limits), + <<"value">> => enc_value(K, V, Depth + 1, Limits)} || {K, V} <- Pairs], + <<"size">> => map_size(T)}; +enc([], _, _) -> []; +enc(T, Depth, Limits) when is_list(T) -> + case printable(T) of + true -> safe_binary(unicode:characters_to_binary(T, unicode, utf8), maps:get(max_binary_bytes, Limits)); + false -> enc_list(T, Depth + 1, Limits) + end; +enc(_, _, _) -> + <<"">>. + +enc_value(Key, Value, Depth, Limits) -> + case secret_key(Key) of + true -> <<"">>; + false -> enc(Value, Depth, Limits) + end. + +secret_key(Key) when is_atom(Key) -> secret_key(atom_to_list(Key)); +secret_key(Key) when is_binary(Key) -> + secret_key(binary_to_list(Key)); +secret_key(Key) when is_list(Key) -> + try + Lower = string:to_lower(Key), + lists:any(fun(W) -> string:str(Lower, W) > 0 end, ?SECRET_WORDS) + catch _:_ -> false + end; +secret_key(_) -> false. + +%% Proper or improper list, capped by max_items. +enc_list(L, Depth, #{max_items := Max} = Limits) -> + {Items, Rest} = take(L, Max, []), + Encoded = [enc(I, Depth, Limits) || I <- Items], + case Rest of + [] -> Encoded; + R when is_list(R) -> Encoded ++ [#{<<"truncated">> => true}]; + Tail -> Encoded ++ [#{<<"improperTail">> => enc(Tail, Depth, Limits)}] + end. + +take([H | T], N, Acc) when N > 0 -> take(T, N - 1, [H | Acc]); +take(Rest, _, Acc) -> {lists:reverse(Acc), Rest}. + +printable(L) -> + try io_lib:printable_unicode_list(L) catch _:_ -> false end. + +binary(B, Max) -> + case is_utf8(B) andalso byte_size(B) =< Max of + true -> B; + false -> + Size = byte_size(B), + Head = binary:part(B, 0, min(Size, Max)), + #{<<"binary">> => #{<<"size">> => Size, + <<"encoding">> => <<"base64">>, + <<"data">> => base64:encode(Head), + <<"truncated">> => Size > Max}} + end. + +%% A valid UTF-8 binary cut at Max bytes, on a character boundary. +safe_binary(B, Max) when is_binary(B) -> + case is_utf8(B) of + true when byte_size(B) =< Max -> B; + true -> cut_utf8(B, Max); + false -> <<"">> + end. + +cut_utf8(B, Max) -> + Head = binary:part(B, 0, Max), + case unicode:characters_to_binary(Head, utf8, utf8) of + R when is_binary(R) -> R; + {incomplete, Good, _} -> Good; + _ -> <<>> + end. + +is_utf8(B) -> + case unicode:characters_to_binary(B, utf8, utf8) of + R when is_binary(R) -> true; + _ -> false + end. + +list_bin(L) -> list_to_binary(L). + +pid_text(Pid) -> list_to_binary(pid_to_list(Pid)). + +hex(Bin) -> + << <<(hex_digit(N div 16)), (hex_digit(N rem 16))>> || <> <= Bin >>. + +hex_digit(N) when N < 10 -> $0 + N; +hex_digit(N) -> $a + N - 10. + +%% Unix milliseconds -> "2026-01-01T00:00:00.000Z" +iso8601(Ms) -> + Secs = Ms div 1000, + {{Y, Mo, D}, {H, Mi, S}} = calendar:gregorian_seconds_to_datetime( + Secs + calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}})), + iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~3..0wZ", + [Y, Mo, D, H, Mi, S, Ms rem 1000])). diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl new file mode 100644 index 00000000..9172bf6a --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -0,0 +1,496 @@ +%% Configuration and policy of the embedded MCP inspector. +%% +%% Two sources, with disjoint ownership: +%% * VS Code settings (erlang.mcp.enabled/host/port): activation and binding; +%% * the optional top-level `{mcp, [...]}` term of the target project's +%% rebar.config: tool/table allowlists and *lower* resource limits. +%% Neither can relax the built-in ceilings. +%% +%% The project file is read by a bounded, non-evaluating reader (no +%% rebar.config.script, no profile merging, no includes). It interns the atoms +%% of the file, so it must only run in a short-lived helper VM (see cli/0) - +%% never in the long-lived LSP or target VM. +%% +%% Normalized configuration (internal contract, atom keys) : +%% #{host => "127.0.0.1", port => 0, +%% allowed_tools => [<<"runtime_summary">>, ...], +%% allowed_ets_tables => [<<"orders_index">>], +%% limits => #{max_items => 500, ...}} +%% and its JSON form (camelCase binary keys) which crosses to the target. +-module(mcp_policy). + +-include_lib("kernel/include/file.hrl"). + +-export([all_tools/0, defaults/0, ceilings/0, limit/2, tool_allowed/2]). +-export([resolve/1, read_project_policy/2, normalize_project_term/1]). +-export([validate_host/1, validate_config/1, to_json_map/1, from_json_map/1, validate_token/1]). +-export([cli/0]). + +-define(MAX_PROJECT_FILE_BYTES, 262144). +-define(MAX_PROJECT_TOKENS, 20000). +-define(PROJECT_FILE, "rebar.config"). + +-define(TOOLS, [<<"runtime_summary">>, <<"application_overview">>, + <<"supervision_tree">>, <<"process_info">>, + <<"registered_processes">>, <<"ets_tables">>, + <<"debug_session">>]). + +%% {internal key, project key, JSON key, default = ceiling} +-define(LIMITS, + [{max_request_bytes, max_request_bytes, <<"maxRequestBytes">>, 16384}, + {max_result_bytes, max_result_bytes, <<"maxResultBytes">>, 262144}, + {max_items, max_items, <<"maxItems">>, 500}, + {max_depth, max_depth, <<"maxDepth">>, 16}, + {max_binary_bytes, max_binary_bytes, <<"maxBinaryBytes">>, 4096}, + {max_traversal_depth, max_traversal_depth, <<"maxTraversalDepth">>, 8}, + {max_concurrency, max_concurrency, <<"maxConcurrency">>, 2}, + {request_timeout_ms, request_timeout_ms, <<"requestTimeoutMs">>, 3000}, + {collection_ttl_ms, collection_ttl_ms, <<"collectionTtlMs">>, 30000}, + {max_collection_bytes, max_collection_bytes, <<"maxCollectionBytes">>, 1048576}, + {max_collections, max_collections, <<"maxCollections">>, 2}]). + +%% Keys a project term must never carry (owned by VS Code / the runtime). +-define(FORBIDDEN_PROJECT_KEYS, + [enabled, required, host, port, transport, auth_token, token, + credentials, cookie, authorization, bearer]). + +all_tools() -> ?TOOLS. + +ceilings() -> + maps:from_list([{K, D} || {K, _, _, D} <- ?LIMITS]). + +defaults() -> + #{host => "127.0.0.1", + port => 0, + allowed_tools => ?TOOLS, + allowed_ets_tables => [], + limits => ceilings()}. + +limit(Key, #{limits := Limits}) -> maps:get(Key, Limits). + +tool_allowed(Tool, #{allowed_tools := Tools}) -> lists:member(Tool, Tools). + +%%------------------------------------------------------------------------------ +%% Host validation: loopback literals or names resolving only to loopback. +%%------------------------------------------------------------------------------ + +%% -> {ok, LiteralAddressString} | {error, Message} +validate_host(Host) when is_binary(Host) -> + validate_host(binary_to_list(Host)); +validate_host(Host) when is_list(Host), length(Host) > 0, length(Host) =< 255 -> + case inet:parse_strict_address(Host) of + {ok, Ip} -> check_loopback(Ip, Host); + {error, _} -> resolve_loopback(Host) + end; +validate_host(_) -> + {error, "erlang.mcp.host must be a non-empty loopback address"}. + +check_loopback(Ip, Text) -> + case is_loopback(Ip) of + true -> {ok, inet:ntoa(Ip)}; + false -> + {error, lists:flatten(io_lib:format( + "erlang.mcp.host '~ts' is not a loopback address: only 127.0.0.0/8 and ::1 are accepted", [Text]))} + end. + +resolve_loopback(Name) -> + Ips = lists:usort([Ip || Fam <- [inet, inet6], + {ok, Ip} <- [safe_getaddr(Name, Fam)]]), + case Ips of + [] -> + {error, lists:flatten(io_lib:format("erlang.mcp.host '~ts' cannot be resolved", [Name]))}; + _ -> + case lists:all(fun is_loopback/1, Ips) of + true -> {ok, inet:ntoa(hd(prefer_v4(Ips)))}; + false -> + {error, lists:flatten(io_lib:format( + "erlang.mcp.host '~ts' resolves to a non-loopback address", [Name]))} + end + end. + +prefer_v4(Ips) -> + {V4, V6} = lists:partition(fun(Ip) -> tuple_size(Ip) =:= 4 end, Ips), + V4 ++ V6. + +safe_getaddr(Name, Family) -> + try inet:getaddr(Name, Family) catch _:_ -> {error, einval} end. + +is_loopback({127, _, _, _}) -> true; +is_loopback({0, 0, 0, 0, 0, 0, 0, 1}) -> true; +is_loopback(_) -> false. + +%%------------------------------------------------------------------------------ +%% Settings + project policy -> normalized configuration +%%------------------------------------------------------------------------------ + +%% Input (binary-keyed map, decoded JSON) : +%% <<"enabled">>, <<"host">>, <<"port">>, <<"cwd">>, <<"root">>, <<"trusted">> +%% -> disabled | {ok, Config} | {error, Message} +resolve(Input) when is_map(Input) -> + case maps:get(<<"enabled">>, Input, false) of + true -> resolve_enabled(Input); + _ -> disabled + end. + +resolve_enabled(Input) -> + case maps:get(<<"trusted">>, Input, false) of + true -> + Host = maps:get(<<"host">>, Input, <<"127.0.0.1">>), + Port = maps:get(<<"port">>, Input, 0), + case {validate_host(Host), valid_port(Port)} of + {{error, Msg}, _} -> {error, Msg}; + {_, false} -> {error, "erlang.mcp.port must be an integer between 0 and 65535"}; + {{ok, Literal}, true} -> + resolve_project(Input, Literal, Port) + end; + _ -> + {error, "MCP is disabled: the workspace is not trusted"} + end. + +resolve_project(Input, Host, Port) -> + Cwd = to_list(maps:get(<<"cwd">>, Input, <<>>)), + Root = to_list(maps:get(<<"root">>, Input, <<>>)), + case read_project_policy(Cwd, Root) of + {error, Msg} -> {error, Msg}; + {ok, none} -> finish(Host, Port, #{}); + {ok, Project} -> finish(Host, Port, Project) + end. + +finish(Host, Port, Project) -> + Base = (defaults())#{host => Host, port => Port}, + Merged = maps:merge(Base, maps:without([limits], Project)), + Limits = maps:merge(maps:get(limits, Base), maps:get(limits, Project, #{})), + Config = Merged#{limits => Limits}, + case validate_config(Config) of + ok -> {ok, Config}; + {error, Msg} -> {error, Msg} + end. + +valid_port(P) -> is_integer(P) andalso P >= 0 andalso P =< 65535. + +to_list(B) when is_binary(B) -> binary_to_list(B); +to_list(L) when is_list(L) -> L. + +%%------------------------------------------------------------------------------ +%% Project policy reader +%%------------------------------------------------------------------------------ + +%% Nearest rebar.config from Cwd up to Root (inclusive), never above Root. +%% -> {ok, none} | {ok, #{allowed_tools|allowed_ets_tables|limits => ...}} | {error, Msg} +read_project_policy(Cwd, Root) -> + case find_project_file(Cwd, Root) of + {error, _} = E -> E; + none -> {ok, none}; + {ok, File} -> + case read_terms(File) of + {ok, Terms} -> project_policy(Terms); + {error, Msg} -> {error, Msg} + end + end. + +find_project_file(Cwd, Root) -> + C = normalize_dir(Cwd), + R = normalize_dir(Root), + case C =/= [] andalso R =/= [] andalso within(C, R) of + true -> walk_up(C, R); + false -> {error, "MCP is disabled: the debug cwd is outside the selected workspace folder"} + end. + +walk_up(Dir, Root) -> + File = filename:join(Dir, ?PROJECT_FILE), + case filelib:is_regular(File) of + true -> {ok, File}; + false when Dir =:= Root -> none; + false -> + Parent = filename:dirname(Dir), + case Parent =:= Dir of + true -> none; + false -> walk_up(Parent, Root) + end + end. + +normalize_dir([]) -> []; +normalize_dir(Dir) -> + Abs = filename:absname(Dir), + Parts = [P || P <- filename:split(Abs), P =/= "."], + Resolved = lists:foldl( + fun("..", [_ | Acc]) -> Acc; + ("..", []) -> []; + (P, Acc) -> [P | Acc] + end, [], Parts), + filename:join(lists:reverse(Resolved)). + +%% Path-component prefix check (not a string prefix: /a/bc is not within /a/b). +within(Dir, Root) -> + D = filename:split(Dir), + R = filename:split(Root), + lists:prefix(R, D). + +read_terms(File) -> + case file:read_file_info(File) of + {ok, #file_info{size = Size}} when Size > ?MAX_PROJECT_FILE_BYTES -> + {error, "rebar.config is too large to read the MCP policy (limit 256 KiB)"}; + {ok, _} -> + case file:read_file(File) of + {ok, Bin} -> scan_terms(Bin); + {error, _} -> {error, "rebar.config cannot be read"} + end; + {error, _} -> + {error, "rebar.config cannot be read"} + end. + +scan_terms(Bin) -> + case unicode:characters_to_list(Bin) of + Str when is_list(Str) -> + try + collect_terms(Str, 1, 0, []) + catch + throw:{malformed, Msg} -> {error, Msg} + end; + _ -> + {error, "rebar.config is not valid text (UTF-8 expected)"} + end. + +collect_terms(Str, Line, Count, Acc) -> + case erl_scan:tokens([], Str, Line) of + {done, {ok, Tokens, EndLine}, Rest} -> + Count1 = Count + length(Tokens), + Count1 > ?MAX_PROJECT_TOKENS andalso throw({malformed, "rebar.config is too complex to read the MCP policy"}), + Term = case erl_parse:parse_term(Tokens) of + {ok, T} -> T; + {error, _} -> throw({malformed, "rebar.config is malformed: it must contain only Erlang terms ending with '.'"}) + end, + collect_terms(Rest, EndLine, Count1, [Term | Acc]); + {done, {eof, _}, _} -> + {ok, lists:reverse(Acc)}; + {done, {error, _, _}, _} -> + throw({malformed, "rebar.config is malformed: it cannot be tokenized"}); + {more, Cont} -> + case erl_scan:tokens(Cont, eof, Line) of + {done, {ok, [], _}, _} -> {ok, lists:reverse(Acc)}; + {done, {eof, _}, _} -> {ok, lists:reverse(Acc)}; + _ -> throw({malformed, "rebar.config is malformed: incomplete term"}) + end + end. + +project_policy(Terms) -> + Mcp = [V || {mcp, V} <- Terms], + case Mcp of + [] -> + case profile_policy(Terms) of + true -> {error, "an 'mcp' policy inside a rebar profile is not supported: " + "move it to the top level of rebar.config"}; + false -> {ok, none} + end; + [Term] -> normalize_project_term(Term); + _ -> {error, "rebar.config has several top-level 'mcp' terms"} + end. + +profile_policy(Terms) -> + lists:any( + fun({profiles, Profiles}) when is_list(Profiles) -> + lists:any(fun({_Name, Opts}) when is_list(Opts) -> lists:keymember(mcp, 1, Opts); + (_) -> false + end, Profiles); + (_) -> false + end, Terms). + +%% The {mcp, Term} value -> {ok, #{...}} | {error, Msg} +normalize_project_term(Term) when is_list(Term) -> + try + Keys = [element(1, T) || T <- Term, is_tuple(T), tuple_size(T) =:= 2], + length(Keys) =:= length(Term) orelse throw("the mcp term must be a list of {Key, Value} pairs"), + length(Keys) =:= length(lists:usort(Keys)) orelse throw("the mcp term has duplicate keys"), + lists:foreach( + fun(K) -> + lists:member(K, ?FORBIDDEN_PROJECT_KEYS) andalso + throw(io_lib:format("'~w' cannot be set in rebar.config: it is owned by the VS Code Erlang settings", [K])), + lists:member(K, [allowed_tools, allowed_ets_tables, limits]) orelse + throw(io_lib:format("unknown key '~w' in the mcp term", [K])) + end, Keys), + {ok, lists:foldl(fun project_key/2, #{}, Term)} + catch + throw:Msg when is_list(Msg) -> {error, "invalid mcp policy: " ++ Msg}; + throw:Msg -> {error, "invalid mcp policy: " ++ lists:flatten(Msg)} + end; +normalize_project_term(_) -> + {error, "invalid mcp policy: the mcp term must be a list"}. + +project_key({allowed_tools, Tools}, Acc) -> + Names = binary_list(Tools, "allowed_tools"), + length(Names) =:= length(lists:usort(Names)) orelse throw("allowed_tools has duplicates"), + lists:foreach(fun(N) -> lists:member(N, ?TOOLS) orelse throw("allowed_tools has an unknown tool name") end, Names), + Acc#{allowed_tools => Names}; +project_key({allowed_ets_tables, Tables}, Acc) -> + Names = binary_list(Tables, "allowed_ets_tables"), + length(Names) =:= length(lists:usort(Names)) orelse throw("allowed_ets_tables has duplicates"), + lists:foreach(fun(N) -> byte_size(N) > 0 andalso byte_size(N) =< 255 orelse + throw("allowed_ets_tables entries must be 1..255 bytes") end, Names), + Acc#{allowed_ets_tables => Names}; +project_key({limits, Limits}, Acc) -> + is_list(Limits) orelse throw("limits must be a list"), + Pairs = [case L of {K, V} -> {K, V}; _ -> throw("limits must be {Key, Integer} pairs") end || L <- Limits], + Ks = [K || {K, _} <- Pairs], + length(Ks) =:= length(lists:usort(Ks)) orelse throw("limits has duplicate keys"), + Map = lists:foldl( + fun({K, V}, M) -> + case lists:keyfind(K, 2, ?LIMITS) of + false -> throw(io_lib:format("unknown limit '~w'", [K])); + {Internal, _, _, Ceiling} -> + (is_integer(V) andalso V > 0 andalso V =< Ceiling) orelse + throw(io_lib:format("limit '~w' must be a positive integer <= ~w", [K, Ceiling])), + M#{Internal => V} + end + end, #{}, Pairs), + Acc#{limits => Map}. + +binary_list(L, What) when is_list(L) -> + lists:foreach(fun(B) -> is_binary(B) orelse throw(What ++ " entries must be binaries such as <<\"name\">>") end, L), + L; +binary_list(_, What) -> + throw(What ++ " must be a list"). + +%%------------------------------------------------------------------------------ +%% Authoritative validation (runs again in the target before any listener). +%%------------------------------------------------------------------------------ + +validate_config(#{host := Host, port := Port, allowed_tools := Tools, + allowed_ets_tables := Tables, limits := Limits} = Config) -> + Known = [host, port, allowed_tools, allowed_ets_tables, limits], + Extra = maps:keys(maps:without(Known, Config)), + Checks = [fun() -> Extra =:= [] orelse {error, "unknown MCP configuration key"} end, + fun() -> case literal_loopback(Host) of true -> ok; false -> {error, "MCP host must be a loopback address literal"} end end, + fun() -> valid_port(Port) orelse {error, "MCP port must be an integer between 0 and 65535"} end, + fun() -> (is_list(Tools) andalso lists:all(fun(T) -> lists:member(T, ?TOOLS) end, Tools) + andalso length(Tools) =:= length(lists:usort(Tools))) + orelse {error, "invalid MCP tool allowlist"} end, + fun() -> (is_list(Tables) andalso lists:all(fun is_binary/1, Tables) + andalso length(Tables) =:= length(lists:usort(Tables))) + orelse {error, "invalid MCP ETS table allowlist"} end, + fun() -> validate_limits(Limits) end], + run_checks(Checks); +validate_config(_) -> + {error, "incomplete MCP configuration"}. + +run_checks([]) -> ok; +run_checks([C | T]) -> + case C() of + ok -> run_checks(T); + true -> run_checks(T); + {error, _} = E -> E + end. + +literal_loopback(Host) when is_list(Host) -> + case inet:parse_strict_address(Host) of + {ok, Ip} -> is_loopback(Ip); + _ -> false + end; +literal_loopback(_) -> false. + +validate_limits(Limits) when is_map(Limits) -> + Expected = [K || {K, _, _, _} <- ?LIMITS], + case lists:sort(maps:keys(Limits)) =:= lists:sort(Expected) of + false -> {error, "MCP limits are incomplete or unknown"}; + true -> + Bad = [K || {K, _, _, Ceiling} <- ?LIMITS, + V <- [maps:get(K, Limits)], + not (is_integer(V) andalso V > 0 andalso V =< Ceiling)], + case Bad of + [] -> validate_relations(Limits); + _ -> {error, lists:flatten(io_lib:format("MCP limit(s) out of range: ~w", [Bad]))} + end + end; +validate_limits(_) -> + {error, "MCP limits must be a map"}. + +%% Error and result envelopes must fit. +validate_relations(#{max_result_bytes := Result, max_request_bytes := Request, + max_collection_bytes := Coll, max_items := Items, max_binary_bytes := Bin}) -> + if Result < 4096 -> {error, "max_result_bytes must be at least 4096"}; + Request < 1024 -> {error, "max_request_bytes must be at least 1024"}; + Coll < Result -> {error, "max_collection_bytes must be at least max_result_bytes"}; + Bin > Result div 4 -> {error, "max_binary_bytes must not exceed a quarter of max_result_bytes"}; + Items < 1 -> {error, "max_items must be positive"}; + true -> ok + end. + +%%------------------------------------------------------------------------------ +%% JSON form (crosses adapter -> target). Only fixed atoms are produced. +%%------------------------------------------------------------------------------ + +to_json_map(#{host := Host, port := Port, allowed_tools := Tools, + allowed_ets_tables := Tables, limits := Limits}) -> + #{<<"host">> => list_to_binary(Host), + <<"port">> => Port, + <<"allowedTools">> => Tools, + <<"allowedETSTables">> => Tables, + <<"limits">> => maps:from_list([{J, maps:get(K, Limits)} || {K, _, J, _} <- ?LIMITS])}. + +from_json_map(#{<<"host">> := Host, <<"port">> := Port, <<"allowedTools">> := Tools, + <<"allowedETSTables">> := Tables, <<"limits">> := Limits} = M) + when is_binary(Host), is_map(Limits) -> + Known = [<<"host">>, <<"port">>, <<"allowedTools">>, <<"allowedETSTables">>, + <<"limits">>, <<"mode">>, <<"authToken">>], + case maps:keys(maps:without(Known, M)) of + [] -> + case token_of(M) of + {error, _} = TE -> TE; + _ -> + KnownLimits = [J || {_, _, J, _} <- ?LIMITS], + case maps:keys(maps:without(KnownLimits, Limits)) of + [] -> + Internal = maps:from_list([{K, maps:get(J, Limits)} + || {K, _, J, _} <- ?LIMITS, maps:is_key(J, Limits)]), + Config = #{host => binary_to_list(Host), port => Port, + allowed_tools => Tools, allowed_ets_tables => Tables, + limits => Internal}, + case validate_config(Config) of + ok -> {ok, Config}; + {error, _} = E -> E + end; + _ -> {error, "unknown MCP limit"} + end + end; + _ -> {error, "unknown MCP configuration key"} + end; +from_json_map(_) -> + {error, "invalid MCP configuration"}. + +%% Optional fixed token (development convenience, erlang.mcp.authToken): when +%% absent the session gets a fresh random token. 16..128 URL-safe characters. +validate_token(T) when is_binary(T), byte_size(T) >= 16, byte_size(T) =< 128 -> + case re:run(T, "^[A-Za-z0-9._~-]+$", [{capture, none}]) of + match -> ok; + nomatch -> {error, "erlang.mcp.authToken may only contain letters, digits and ._~-"} + end; +validate_token(_) -> + {error, "erlang.mcp.authToken must be 16 to 128 characters"}. + +token_of(#{<<"authToken">> := T}) -> validate_token(T); +token_of(_) -> ok. + +%%------------------------------------------------------------------------------ +%% Helper VM entry point: erl -noshell -pa ebin -s mcp_policy cli +%% Reads one JSON line from stdin, prints one JSON line, halts. +%%------------------------------------------------------------------------------ + +cli() -> + Reply = try + case io:get_line("") of + Line when is_list(Line), length(Line) < 8192 -> + {ok, Input, _} = vscode_jsone_decode:decode(unicode:characters_to_binary(Line)), + case resolve(Input) of + disabled -> #{status => <<"disabled">>}; + {ok, Config} -> #{status => <<"ok">>, config => to_json_map(Config)}; + {error, Msg} -> #{status => <<"error">>, message => unicode:characters_to_binary(Msg)} + end; + _ -> + #{status => <<"error">>, message => <<"invalid MCP policy request">>} + end + catch + _:_ -> #{status => <<"error">>, message => <<"MCP policy could not be resolved">>} + end, + {ok, Json} = vscode_jsone:encode(Reply), + io:format("~s~n", [Json]), + init:stop(). + diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl new file mode 100644 index 00000000..ccfc3b2d --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -0,0 +1,1079 @@ +%% Read-only runtime inspection behind the seven MCP tools. +%% +%% Everything here is metadata: no process dictionary, no sys:get_state, no +%% mailbox, no ETS keys/objects, no application callbacks, no code loading. +%% Callers run `call/3` inside a monitored, time-bounded worker (mcp_server). +%% +%% Mapping contract (schemaVersion 1.0): entities (application, process, +%% module, ets_table), typed relationships (depends_on, supervises, owns_table, +%% linked_to, monitors, belongs_to, uses_module) with evidence, observation +%% time and confidence, retained as a bounded *collection* and served in pages +%% through signed cursors. A collection is an observation interval, never an +%% atomic snapshot. +%% +%% Cost notes (documented residual risk): application:which_applications/1, +%% registered/0, supervisor:which_children/1 and supervisor:count_children/1 +%% materialize lists in the target VM and cannot be interrupted once started; +%% killing the worker only discards the late reply. +-module(mcp_runtime). + +-export([call/3, bounded/2, args_hash/1]). + +-define(SV, <<"1.0">>). +-define(MAX_LISTED_CHILDREN, 20000). +-define(MAX_SPEC_LOOKUPS, 200). +%% default page size for agents (detail=summary); detail=full pages use max_items +-define(SUMMARY_PAGE, 100). + +%%------------------------------------------------------------------------------ +%% Entry point +%%------------------------------------------------------------------------------ + +%% Ctx: #{config, timeout_ms} +%% -> {ok, Structured} | {error, Code :: binary(), Message :: binary()} +call(Tool, Args, Ctx0) -> + Config = maps:get(config, Ctx0), + Timeout = maps:get(timeout_ms, Ctx0), + MaxItems = mcp_policy:limit(max_items, Config), + Detail = case maps:get(<<"detail">>, Args, <<"summary">>) of + <<"full">> -> full; + _ -> summary + end, + %% agents get small pages by default; detail=full is meant for exports + PageSize = case {maps:get(<<"pageSize">>, Args, undefined), Detail} of + {undefined, summary} -> min(?SUMMARY_PAGE, MaxItems); + {undefined, full} -> MaxItems; + {N, _} -> min(N, MaxItems) + end, + Ctx = Ctx0#{enc => maps:with([max_depth, max_items, max_binary_bytes], maps:get(limits, Config)), + soft_deadline => now_mono() + (Timeout * 7) div 10, + detail => Detail, page_size => PageSize}, + case maps:find(<<"cursor">>, Args) of + {ok, Cursor} -> page_from_cursor(Tool, Args, Cursor, Ctx); + error -> run(Tool, Args, Ctx) + end. + +run(<<"runtime_summary">>, Args, Ctx) -> runtime_summary(Args, Ctx); +run(<<"debug_session">>, _Args, Ctx) -> debug_session(Ctx); +run(<<"application_overview">>, Args, Ctx) -> application_overview(Args, Ctx); +run(<<"supervision_tree">>, Args, Ctx) -> supervision_tree(Args, Ctx); +run(<<"registered_processes">>, Args, Ctx) -> registered_processes(Args, Ctx); +run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); +run(<<"ets_tables">>, Args, Ctx) -> ets_tables(Args, Ctx); +run(_, _, _) -> {error, <<"unknown_tool">>, <<"unknown tool">>}. + +%% A deadline-bounded call in a monitored worker. On timeout the worker is +%% killed (an inspector-owned process) and any late reply is discarded. +%% -> {ok, Result} | timeout | {error, Reason} +bounded(Fun, Timeout) when Timeout > 0 -> + Parent = self(), + Ref = make_ref(), + {Pid, MRef} = spawn_monitor( + fun() -> + Res = try {ok, Fun()} catch C:R -> {error, {C, R}} end, + Parent ! {Ref, Res} + end), + receive + {Ref, Res} -> + erlang:demonitor(MRef, [flush]), + Res; + {'DOWN', MRef, process, Pid, Reason} -> + receive {Ref, Res} -> Res after 0 -> {error, {down, Reason}} end + after Timeout -> + exit(Pid, kill), + erlang:demonitor(MRef, [flush]), + receive {Ref, _} -> ok after 0 -> ok end, + timeout + end; +bounded(_, _) -> + timeout. + +now_mono() -> erlang:monotonic_time(millisecond). + +remaining(#{soft_deadline := D}) -> max(0, D - now_mono()). + +now_iso() -> mcp_encoder:iso8601(mcp_store:now_ms()). + +args_hash(Args) -> + Sorted = lists:sort(maps:to_list(Args)), + <> = crypto:hash(sha256, term_to_binary(Sorted)), + mcp_encoder:hex(H). + +%%------------------------------------------------------------------------------ +%% runtime_summary / debug_session (plain structured results) +%%------------------------------------------------------------------------------ + +runtime_summary(Args, _Ctx) -> + Redact = maps:get(<<"redactNodeHost">>, Args, true), + {Uptime, _} = erlang:statistics(wall_clock), + Mem = erlang:memory(), + {ok, #{<<"schemaVersion">> => ?SV, + <<"sessionId">> => mcp_store:session_id(), + <<"observedAt">> => now_iso(), + <<"otpRelease">> => list_to_binary(erlang:system_info(otp_release)), + <<"ertsVersion">> => list_to_binary(erlang:system_info(version)), + <<"node">> => node_text(Redact), + <<"nodeRedacted">> => Redact, + <<"uptimeMs">> => Uptime, + <<"schedulers">> => erlang:system_info(schedulers), + <<"processCount">> => erlang:system_info(process_count), + <<"memory">> => #{<<"unit">> => <<"bytes">>, + <<"total">> => proplists:get_value(total, Mem), + <<"processes">> => proplists:get_value(processes, Mem), + <<"system">> => proplists:get_value(system, Mem), + <<"atom">> => proplists:get_value(atom, Mem), + <<"binary">> => proplists:get_value(binary, Mem), + <<"ets">> => proplists:get_value(ets, Mem)}}}. + +node_text(false) -> atom_to_binary(node(), utf8); +node_text(true) -> + case binary:split(atom_to_binary(node(), utf8), <<"@">>) of + [Name, _Host] -> <">>; + [Name] -> Name + end. + +%% Safe debugger metadata only: mode, connection state, interpreted modules and +%% breakpoint locations (module + line). Source and options are not returned. +debug_session(#{enc := Enc} = _Ctx) -> + Max = maps:get(max_items, Enc), + {Interpreted, IUnavailable} = debug_call(fun() -> int:interpreted() end, []), + {Breaks, BUnavailable} = debug_call(fun() -> int:all_breaks() end, []), + Mods = [atom_to_binary(M, utf8) || M <- lists:sublist(lists:sort(Interpreted), Max), is_atom(M)], + Bps = [#{<<"module">> => atom_to_binary(M, utf8), <<"line">> => L} + || {{M, L}, _} <- lists:sublist(lists:sort(Breaks), Max), is_atom(M), is_integer(L)], + {ok, #{<<"schemaVersion">> => ?SV, + <<"sessionId">> => mcp_store:session_id(), + <<"observedAt">> => now_iso(), + <<"mode">> => mode_text(mcp_store:mode()), + <<"nodeConnected">> => true, + <<"interpretedModules">> => Mods, + <<"breakpoints">> => Bps, + <<"debuggerMetadata">> => case IUnavailable orelse BUnavailable of + true -> <<"unavailable">>; + false -> <<"available">> + end, + <<"truncated">> => length(Interpreted) > Max orelse length(Breaks) > Max}}. + +debug_call(Fun, Default) -> + case bounded(Fun, 1500) of + {ok, R} when is_list(R) -> {R, false}; + _ -> {Default, true} + end. + +mode_text(launch) -> <<"launch">>; +mode_text(attach) -> <<"attach">>; +mode_text(_) -> <<"unknown">>. + +%%------------------------------------------------------------------------------ +%% Builder: entities, relationships and omissions of one collection +%%------------------------------------------------------------------------------ + +new_b() -> #{ents => [], ent_ids => #{}, rels => [], rel_keys => #{}, oms => [], pids => #{}}. + +add_entity(#{ent_ids := Ids, ents := Es} = B, Id, Entity) -> + case maps:is_key(Id, Ids) of + true -> B; + false -> B#{ents := [Entity#{<<"id">> => Id} | Es], ent_ids := Ids#{Id => true}} + end. + +add_rel(#{rel_keys := Keys, rels := Rs} = B, Type, From, To, Evidence, Confidence) -> + Key = {Type, From, To}, + case maps:is_key(Key, Keys) of + true -> B; + false -> + R = #{<<"type">> => Type, <<"from">> => From, <<"to">> => To, + <<"evidence">> => Evidence, <<"observedAt">> => now_iso(), + <<"confidence">> => Confidence}, + B#{rel_keys := Keys#{Key => true}, rels := [R | Rs]} + end. + +add_omission(#{oms := Oms} = B, Reason, Count, Scope, Message, Resumable) -> + O0 = #{<<"reason">> => Reason, <<"count">> => Count, + <<"resumable">> => Resumable, <<"message">> => Message}, + O = case Scope of + undefined -> O0; + _ -> O0#{<<"scope">> => Scope} + end, + B#{oms := [O | Oms]}. + +note_pid(#{pids := P} = B, Pid) -> B#{pids := P#{Pid => true}}. + +%%------------------------------------------------------------------------------ +%% Collections and pages +%%------------------------------------------------------------------------------ + +finish_graph(Tool, Args, Scope, B, Started, Ctx) -> + Oms = lists:reverse(maps:get(oms, B)), + Truncated = lists:any(fun(#{<<"reason">> := R}) -> R =:= <<"limit_reached">> end, Oms), + Meta = #{scope => Scope, started_at => Started, finished_at => mcp_store:now_ms(), + complete => Oms =:= [], truncated => Truncated, omissions => Oms}, + Items = [{entity, E} || E <- lists:reverse(maps:get(ents, B))] + ++ [{relationship, R} || R <- lists:reverse(maps:get(rels, B))], + Hash = args_hash(maps:remove(<<"cursor">>, Args)), + {CollId, Meta1, Stored} = mcp_store:put_collection(Tool, Hash, Meta, Items, #{}), + render_page(Tool, Hash, CollId, Meta1, Stored, 0, Ctx). + +page_from_cursor(Tool, Args, Cursor, Ctx) -> + Hash = args_hash(maps:remove(<<"cursor">>, Args)), + case mcp_store:parse_cursor(Cursor, Tool, Hash) of + {error, _} -> + {error, <<"invalid_cursor">>, <<"the cursor is invalid for this tool, arguments or session">>}; + {ok, CollId, Offset} -> + case mcp_store:fetch_collection(CollId, Tool, Hash) of + {ok, #{meta := Meta, items := Items}} -> + render_page(Tool, Hash, CollId, Meta, Items, Offset, Ctx); + {error, cursor_expired} -> + {error, <<"cursor_expired">>, + <<"the collection is no longer retained; call the tool again (results are not silently restarted)">>} + end + end. + +render_page(Tool, Hash, CollId, Meta, Items, Offset, #{config := Config} = Ctx) -> + Rest = safe_nthtail(Offset, Items), + MaxItems = maps:get(page_size, Ctx), + MaxResult = mcp_policy:limit(max_result_bytes, Config), + fit_page(Tool, Hash, CollId, Meta, Rest, Offset, min(MaxItems, max(1, length(Rest))), MaxResult, Ctx). + +safe_nthtail(N, L) when N >= length(L) -> []; +safe_nthtail(N, L) -> lists:nthtail(N, L). + +fit_page(Tool, Hash, CollId, Meta, Rest, Offset, N, MaxResult, Ctx) -> + Page = lists:sublist(Rest, N), + Env = envelope(Tool, Hash, CollId, Meta, Page, Offset, length(Rest) > N, Ctx), + {ok, Json0} = vscode_jsone:encode(Env), + Json = iolist_to_binary(Json0), + %% the response carries the structured result twice: as structuredContent and + %% as escaped JSON text in content[0].text; plus the JSON-RPC framing + {ok, Text} = vscode_jsone:encode(Json), + case byte_size(Json) + iolist_size(Text) + 1024 =< MaxResult of + true -> {ok, Env}; + false when N > 1 -> + fit_page(Tool, Hash, CollId, Meta, Rest, Offset, N div 2, MaxResult, Ctx); + false -> + {error, <<"result_too_large">>, + <<"a single item exceeds max_result_bytes; the policy limits are too small for this node">>} + end. + +envelope(Tool, Hash, CollId, Meta, Page, Offset, More, Ctx) -> + Detail = maps:get(detail, Ctx, full), + Entities0 = [E || {entity, E} <- Page], + InPage = maps:from_list([{maps:get(<<"id">>, E), true} || E <- Entities0]), + Rels0 = [R#{<<"fromInPage">> => maps:is_key(maps:get(<<"from">>, R), InPage), + <<"toInPage">> => maps:is_key(maps:get(<<"to">>, R), InPage)} + || {relationship, R} <- Page], + {Entities, Rels, Scope} = + case Detail of + full -> {Entities0, Rels0, maps:get(scope, Meta)}; + summary -> {[compact_entity(E) || E <- Entities0], + [compact_rel(R) || R <- Rels0], + compact_scope(maps:get(scope, Meta), Offset)} + end, + Next = case More of + true -> mcp_store:make_cursor(CollId, Tool, Hash, Offset + length(Page)); + false -> null + end, + #{<<"schemaVersion">> => ?SV, + <<"sessionId">> => mcp_store:session_id(), + <<"collectionId">> => CollId, + <<"startedAt">> => mcp_encoder:iso8601(maps:get(started_at, Meta)), + <<"finishedAt">> => mcp_encoder:iso8601(maps:get(finished_at, Meta)), + <<"scope">> => Scope, + <<"detail">> => atom_to_binary(Detail, utf8), + <<"entities">> => Entities, + <<"relationships">> => Rels, + <<"complete">> => maps:get(complete, Meta), + <<"truncated">> => maps:get(truncated, Meta), + <<"omissions">> => maps:get(omissions, Meta), + <<"nextCursor">> => Next, + <<"page">> => #{<<"offset">> => Offset, <<"items">> => length(Page)}}. + +%% detail=summary: same entities and edges, without the fields an agent rarely +%% needs to reason about the topology (they stay available with detail=full). +compact_entity(E) -> + E1 = maps:without([<<"description">>, <<"pid">>, <<"specAvailable">>, <<"metadataAvailable">>], E), + maps:filter(fun(<<"alive">>, true) -> false; + (<<"application">>, null) -> false; + (<<"name">>, null) -> false; + (<<"includedApplications">>, []) -> false; + (<<"callbackModule">>, V) -> V =/= <<"unknown">> andalso V =/= null; + (<<"behaviour">>, <<"unknown">>) -> false; + (<<"modules">>, [M]) -> M =/= maps:get(<<"callbackModule">>, E, undefined); + (<<"childId">>, C) -> C =/= maps:get(<<"name">>, E, undefined); + (_, _) -> true + end, E1). + +%% the observation interval is the collection's startedAt/finishedAt +compact_rel(R) -> + maps:filter(fun(<<"observedAt">>, _) -> false; + (<<"fromInPage">>, true) -> false; + (<<"toInPage">>, true) -> false; + (_, _) -> true + end, R). + +%% coverage limitations are stated once, on the first page +compact_scope(Scope, 0) -> Scope; +compact_scope(Scope, _) -> maps:remove(<<"limitations">>, Scope). + +%%------------------------------------------------------------------------------ +%% Identities and small entity helpers +%%------------------------------------------------------------------------------ + +app_id(App) -> mcp_store:entity_id(application, {application, App}). +pid_id(Pid) -> mcp_store:entity_id(process, {process, Pid}). +module_id(Mod) -> mcp_store:entity_id(module, {module, Mod}). + +bin(A) when is_atom(A) -> atom_to_binary(A, utf8); +bin(L) when is_list(L) -> + case unicode:characters_to_binary(L) of + B when is_binary(B) -> B; + _ -> <<"">> + end; +bin(B) when is_binary(B) -> B. + +%% Modules that belong to the inspector are flagged, never presented as application processes. +inspector_name(Name) -> + lists:member(Name, [mcp_holder, mcp_sup, mcp_server, mcp_store]). + +%% Safe check that a pid is a supervisor before any supervisor API is called on +%% it (calling which_children on a plain gen_server would crash it). +is_supervisor(Pid) -> + is_pid(Pid) andalso node(Pid) =:= node() andalso + try proc_lib:initial_call(Pid) of + {supervisor, _, _} -> true; + _ -> false + catch _:_ -> false + end. + +behaviours(Mod) when is_atom(Mod) -> + case erlang:module_loaded(Mod) of + true -> + try + [B || {Attr, Bs} <- Mod:module_info(attributes), + Attr =:= behaviour orelse Attr =:= behavior, + B <- Bs, is_atom(B)] + of + L -> [bin(B) || B <- L] + catch _:_ -> unknown + end; + false -> unknown + end; +behaviours(_) -> unknown. + +behaviours_field(Mod) -> + case behaviours(Mod) of + unknown -> <<"unknown">>; + L -> L + end. + +process_entity(Pid, Fields, B) -> + Id = pid_id(Pid), + Name = case erlang:process_info(Pid, registered_name) of + {registered_name, N} -> bin(N); + _ -> null + end, + E = maps:merge(#{<<"kind">> => <<"process">>, + <<"pid">> => mcp_encoder:pid_text(Pid), + <<"name">> => Name, + <<"alive">> => true}, Fields), + {Id, note_pid(add_entity(B, Id, E), Pid)}. + +%%------------------------------------------------------------------------------ +%% application_overview +%%------------------------------------------------------------------------------ + +started_apps(Ctx) -> + case bounded(fun() -> application:which_applications(1500) end, min(2000, max(1, remaining(Ctx)))) of + {ok, L} when is_list(L) -> {ok, lists:keysort(1, L)}; + timeout -> timeout; + _ -> error + end. + +%% -> {discovered, RootPid} | no_start_module | unavailable +root_info(App, Ctx) -> + case application:get_key(App, mod) of + {ok, {_Mod, _Args}} -> + Res = bounded(fun() -> + case application_controller:get_master(App) of + Master when is_pid(Master) -> + {Root, _} = application_master:get_child(Master), + Root; + _ -> undefined + end + end, min(1000, max(1, remaining(Ctx)))), + case Res of + {ok, Root} when is_pid(Root) -> {discovered, Root}; + _ -> unavailable + end; + _ -> no_start_module + end. + +application_overview(Args, #{enc := Enc} = Ctx) -> + Started = mcp_store:now_ms(), + IncludeModules0 = maps:get(<<"includeModules">>, Args, false), + %% listing the modules of every application floods an agent: with + %% detail=summary, modules are only listed for a selected application + NodeWide = not maps:is_key(<<"application">>, Args), + IncludeModules = IncludeModules0 andalso not (NodeWide andalso maps:get(detail, Ctx) =:= summary), + case select_application(maps:get(<<"application">>, Args, undefined)) of + {error, _, _} = E -> E; + {ok, Selected} -> + {Apps, B0} = case started_apps(Ctx) of + {ok, L} -> {L, new_b()}; + timeout -> + {[], add_omission(new_b(), <<"timeout">>, 1, undefined, + <<"application_controller did not answer in time">>, false)}; + error -> + {[], add_omission(new_b(), <<"unavailable_while_paused">>, 1, undefined, + <<"the started applications could not be listed">>, false)} + end, + Names = [A || {A, _, _} <- Apps], + Wanted = case Selected of + all -> Apps; + {app, Name} -> [T || {A, _, _} = T <- Apps, A =:= Name] + end, + B1 = case {Selected, Wanted} of + {{app, _}, []} -> + add_omission(B0, <<"disappeared">>, 1, undefined, + <<"the selected application is not started (anymore)">>, false); + _ -> B0 + end, + B2a = lists:foldl(fun({App, Desc, Vsn}, Acc) -> + app_entities(App, Desc, Vsn, Names, IncludeModules, Enc, Ctx, Acc) + end, B1, Wanted), + %% references to applications outside the listing (not started, or not selected): + %% added last so that a real entity is never shadowed by a stub + Refs = lists:usort(lists:append([case application:get_key(A, applications) of + {ok, D} when is_list(D) -> D; + _ -> [] + end || {A, _, _} <- Wanted])), + B2 = lists:foldl(fun(Dep, Acc) -> + Running = lists:member(Dep, Names), + add_entity(Acc, app_id(Dep), + #{<<"kind">> => <<"application">>, <<"name">> => bin(Dep), + <<"running">> => Running, <<"metadataAvailable">> => false}) + end, B2a, Refs), + B3 = case IncludeModules0 andalso not IncludeModules of + true -> add_omission(B2, <<"limit_reached">>, 1, undefined, + <<"modules are listed for one application at a time: pass application= (or detail=full)">>, + false); + false -> B2 + end, + Scope = #{<<"tool">> => <<"application_overview">>, + <<"application">> => case Selected of all -> null; {app, N} -> app_id(N) end, + <<"limitations">> => + [<<"Only started applications are listed; dependencies that are not started appear as running=false stubs.">>, + <<"Declared dependencies (depends_on) are declarations from the application resource, not evidence of runtime communication.">>, + <<"Root discovery uses the application master; rootStatus explains when no root is available.">>, + <<"Application environment values, code paths and callbacks are never read or invoked.">>]}, + finish_graph(<<"application_overview">>, Args, Scope, B3, Started, Ctx) + end. + +select_application(undefined) -> {ok, all}; +select_application(Id) -> + case mcp_store:resolve_id(Id) of + {ok, {application, Name}} -> {ok, {app, Name}}; + {ok, _} -> {error, <<"invalid_id">>, <<"the id does not designate an application">>}; + expired -> {error, <<"unknown_or_expired_id">>, <<"unknown or expired entity id; call application_overview again">>} + end. + +app_entities(App, Desc, Vsn, _StartedNames, IncludeModules, Enc, Ctx, B0) -> + AppId = app_id(App), + Mods = case application:get_key(App, modules) of {ok, M} when is_list(M) -> M; _ -> unavailable end, + Deps = case application:get_key(App, applications) of {ok, D} when is_list(D) -> D; _ -> [] end, + Included = case application:get_key(App, included_applications) of {ok, I} when is_list(I) -> I; _ -> [] end, + StartMod = case application:get_key(App, mod) of {ok, {SM, _}} -> bin(SM); _ -> null end, + {RootStatus, Roots, B1} = case root_info(App, Ctx) of + {discovered, Root} -> + Kind = case is_supervisor(Root) of + true -> <<"supervisor">>; + false -> <<"unknown">> + end, + {RId, Bx} = process_entity(Root, #{<<"role">> => Kind, + <<"application">> => bin(App)}, B0), + mcp_store:note_member(Root, App, mcp_store:now_ms()), + Bx1 = add_rel(Bx, <<"belongs_to">>, RId, AppId, + <<"application master child">>, <<"confirmed">>), + {<<"discovered">>, [RId], Bx1}; + no_start_module -> {<<"no_start_module">>, [], B0}; + unavailable -> {<<"unavailable">>, [], B0} + end, + Entity = #{<<"kind">> => <<"application">>, + <<"name">> => bin(App), + <<"version">> => bin(Vsn), + <<"description">> => mcp_encoder:safe_binary(unicode:characters_to_binary(Desc), 256), + <<"running">> => true, + <<"startModule">> => StartMod, + <<"dependencies">> => [bin(D) || D <- Deps], + <<"includedApplications">> => [bin(D) || D <- Included], + <<"moduleCount">> => case Mods of unavailable -> null; _ -> length(Mods) end, + <<"rootStatus">> => RootStatus, + <<"roots">> => Roots}, + B2 = add_entity(B1, AppId, Entity), + B3 = lists:foldl( + fun(Dep, Acc) -> + add_rel(Acc, <<"depends_on">>, AppId, app_id(Dep), + <<"declared in the application resource (applications key)">>, <<"confirmed">>) + end, B2, Deps), + case {IncludeModules, Mods} of + {true, L} when is_list(L) -> + Max = maps:get(max_items, Enc), + Shown = lists:sublist(lists:sort(L), Max), + Bm = lists:foldl( + fun(Mod, Acc) -> + MId = module_id(Mod), + Acc1 = add_entity(Acc, MId, #{<<"kind">> => <<"module">>, <<"name">> => bin(Mod), + <<"loaded">> => erlang:module_loaded(Mod), + <<"behaviours">> => behaviours_field(Mod)}), + add_rel(Acc1, <<"belongs_to">>, MId, AppId, + <<"application resource modules key">>, <<"confirmed">>) + end, B3, Shown), + case length(L) > Max of + true -> add_omission(Bm, <<"limit_reached">>, length(L) - Max, AppId, + <<"module list truncated; use includeModules=false or a narrower scope">>, false); + false -> Bm + end; + _ -> B3 + end. + +%%------------------------------------------------------------------------------ +%% supervision_tree +%%------------------------------------------------------------------------------ + +supervision_tree(Args, #{config := Config} = Ctx) -> + Started = mcp_store:now_ms(), + MaxDepth = min(maps:get(<<"maxDepth">>, Args, 16), mcp_policy:limit(max_traversal_depth, Config)), + IncludeModules = maps:get(<<"includeModules">>, Args, false), + case tree_start(Args, Ctx) of + {error, _, _} = E -> E; + {ok, Roots, App, Scope0, B0} -> + Cfg = #{max_depth => MaxDepth, include_modules => IncludeModules, app => App, ctx => Ctx}, + B1 = walk([{R, 0} || R <- Roots], Cfg, #{}, B0), + Scope = Scope0#{<<"tool">> => <<"supervision_tree">>, <<"maxDepth">> => MaxDepth, + <<"limitations">> => + [<<"Only 'supervises' edges from supervisor:which_children/1 are emitted; links and monitors are never supervision.">>, + <<"Unsupervised, unregistered and transient processes are not part of this map.">>, + <<"Child start arguments are never returned.">>]}, + finish_graph(<<"supervision_tree">>, Args, Scope, B1, Started, Ctx) + end. + +%% -> {ok, [RootPid], AppName | undefined, Scope, Builder} | {error, Code, Msg} +tree_start(#{<<"id">> := Id}, Ctx) -> + case mcp_store:resolve_id(Id) of + {ok, {application, App}} -> + case lists:keymember(App, 1, application:which_applications(1000)) of + false -> {error, <<"not_found">>, <<"the application is not started (anymore)">>}; + true -> + B0 = new_b(), + AppId = app_id(App), + case root_info(App, Ctx) of + {discovered, Root} -> + {ok, [Root], App, #{<<"application">> => AppId}, B0}; + no_start_module -> + {ok, [], App, #{<<"application">> => AppId}, + add_omission(B0, <<"unknown_membership">>, 1, AppId, + <<"library application without a start module: no root supervisor exists">>, false)}; + unavailable -> + {ok, [], App, #{<<"application">> => AppId}, + add_omission(B0, <<"timeout">>, 1, AppId, + <<"the root supervisor could not be discovered">>, false)} + end + end; + {ok, {process, Pid}} -> + start_from_pid(Pid, undefined); + {ok, _} -> + {error, <<"invalid_id">>, <<"the id designates neither an application nor a process">>}; + expired -> + {error, <<"unknown_or_expired_id">>, <<"unknown or expired entity id; call application_overview again">>} + end; +tree_start(#{<<"supervisor">> := Name}, _Ctx) -> + case resolve_process(Name) of + {ok, Pid} -> start_from_pid(Pid, undefined); + {error, _, _} = E -> E + end. + +start_from_pid(Pid, App) -> + case is_pid(Pid) andalso is_process_alive(Pid) of + false -> {error, <<"not_found">>, <<"the process does not exist (anymore)">>}; + true -> + case is_supervisor(Pid) of + false -> {error, <<"not_a_supervisor">>, <<"the process is not a supervisor">>}; + true -> {ok, [Pid], App, #{<<"process">> => pid_id(Pid)}, new_b()} + end + end. + +walk([], _Cfg, _Visited, B) -> B; +walk([{Sup, Depth} | Rest], #{ctx := Ctx} = Cfg, Visited, B) -> + case {maps:is_key(Sup, Visited), remaining(Ctx)} of + {true, _} -> + walk(Rest, Cfg, Visited, B); + {false, 0} -> + B1 = add_omission(B, <<"timeout">>, length(Rest) + 1, undefined, + <<"the time budget was exhausted before every supervisor was visited">>, false), + B1; + {false, _} -> + {SupId, B1} = process_entity(Sup, #{<<"role">> => <<"supervisor">>, + <<"application">> => app_text(Cfg)}, B), + case children_of(Sup, Ctx) of + {ok, Children} -> + {B2, Next} = lists:foldl( + fun(C, {Acc, Q}) -> child(C, Sup, SupId, Depth, Cfg, Acc, Q) end, + {B1, []}, Children), + walk(Rest ++ lists:reverse(Next), Cfg, Visited#{Sup => true}, B2); + {too_many, N} -> + B2 = add_omission(B1, <<"limit_reached">>, N, SupId, + <<"too many children to list safely; children not listed">>, false), + walk(Rest, Cfg, Visited#{Sup => true}, B2); + timeout -> + B2 = add_omission(B1, <<"timeout">>, 1, SupId, + <<"the supervisor did not answer in time; its children are missing from this map">>, false), + walk(Rest, Cfg, Visited#{Sup => true}, B2); + gone -> + B2 = add_omission(B1, <<"disappeared">>, 1, SupId, + <<"the supervisor exited during the collection">>, false), + walk(Rest, Cfg, Visited#{Sup => true}, B2) + end + end. + +app_text(#{app := undefined}) -> null; +app_text(#{app := App}) -> bin(App). + +%% -> {ok, [{ChildId, Child, Type, Modules, SpecMeta}]} | {too_many, N} | timeout | gone +children_of(Sup, Ctx) -> + Fun = fun() -> + Counts = supervisor:count_children(Sup), + Total = proplists:get_value(specs, Counts, 0), + case Total > ?MAX_LISTED_CHILDREN of + true -> {too_many, Total}; + false -> + Cs = supervisor:which_children(Sup), + {ok, with_specs(Sup, Cs, 0, [])} + end + end, + %% one third of the request budget per supervisor: a blocked supervisor + %% must not starve its siblings + PerCall = max(100, maps:get(timeout_ms, Ctx) div 3), + case bounded(Fun, min(remaining(Ctx), PerCall)) of + {ok, R} -> R; + timeout -> timeout; + {error, _} -> gone + end. + +with_specs(_Sup, [], _N, Acc) -> lists:reverse(Acc); +with_specs(Sup, [{Id, Child, Type, Mods} | T], N, Acc) -> + Meta = case N < ?MAX_SPEC_LOOKUPS of + true -> spec_meta(Sup, Id, Child); + false -> unavailable + end, + with_specs(Sup, T, N + 1, [{Id, Child, Type, Mods, Meta} | Acc]); +with_specs(Sup, [_ | T], N, Acc) -> + with_specs(Sup, T, N, Acc). + +%% restart / shutdown / significant only - never the start MFA. +spec_meta(Sup, Id, Child) -> + Key = case Id of undefined when is_pid(Child) -> Child; _ -> Id end, + try supervisor:get_childspec(Sup, Key) of + {ok, Spec} when is_map(Spec) -> + #{restart => maps:get(restart, Spec, undefined), + shutdown => maps:get(shutdown, Spec, undefined), + significant => maps:get(significant, Spec, undefined)}; + {ok, Spec} when is_tuple(Spec), tuple_size(Spec) =:= 6 -> + #{restart => element(4, Spec), shutdown => element(5, Spec), significant => undefined}; + _ -> unavailable + catch _:_ -> unavailable + end. + +child({ChildId, Child, Type, Mods, Meta}, Sup, SupId, Depth, #{ctx := Ctx} = Cfg, B, Next) -> + Enc = maps:get(enc, Ctx), + Role = case Type of supervisor -> <<"supervisor">>; worker -> <<"worker">>; _ -> <<"unknown">> end, + ChildText = mcp_encoder:text(ChildId, Enc), + {ModNames, Callback} = module_names(Mods), + Common = #{<<"role">> => Role, + <<"childId">> => ChildText, + <<"modules">> => ModNames, + <<"callbackModule">> => Callback, + <<"behaviour">> => case Callback of + null -> <<"unknown">>; + _ -> behaviours_from_name(Mods) + end, + <<"application">> => app_text(Cfg)}, + Common1 = add_meta(Common, Meta), + {ChildEntId, B1} = + case Child of + P when is_pid(P) -> + case Cfg of + #{app := App} when App =/= undefined -> + mcp_store:note_member(P, App, mcp_store:now_ms()); + _ -> ok + end, + process_entity(P, Common1, B); + Other -> + State = case Other of restarting -> <<"restarting">>; _ -> <<"not_running">> end, + Id = mcp_store:entity_id(process, {slot, Sup, ChildText}), + E = Common1#{<<"kind">> => <<"process">>, <<"alive">> => false, + <<"childState">> => State, <<"pid">> => null, <<"name">> => null}, + {Id, add_entity(B, Id, E)} + end, + B2 = add_rel(B1, <<"supervises">>, SupId, ChildEntId, + <<"supervisor:which_children/1">>, <<"confirmed">>), + B3 = case maps:get(include_modules, Cfg) of + true -> module_edges(Mods, ChildEntId, B2); + false -> B2 + end, + Next1 = case {Child, Type} of + {P2, supervisor} when is_pid(P2) -> + case Depth + 1 < maps:get(max_depth, Cfg) of + true -> [{P2, Depth + 1} | Next]; + false -> Next + end; + _ -> Next + end, + B4 = case {Child, Type} of + {P3, supervisor} when is_pid(P3) -> + case Depth + 1 < maps:get(max_depth, Cfg) of + true -> B3; + false -> + add_omission(B3, <<"limit_reached">>, 1, ChildEntId, + <<"depth limit reached; call supervision_tree with this id to expand the subtree">>, + false) + end; + _ -> B3 + end, + {B4, Next1}. + +add_meta(M, unavailable) -> M#{<<"specAvailable">> => false}; +add_meta(M, #{restart := R, shutdown := S, significant := Sig}) -> + M1 = M#{<<"specAvailable">> => true, + <<"restart">> => spec_value(R), + <<"shutdown">> => spec_value(S)}, + case Sig of + undefined -> M1; + _ -> M1#{<<"significant">> => Sig =:= true} + end. + +spec_value(undefined) -> <<"unknown">>; +spec_value(V) when is_atom(V) -> bin(V); +spec_value(V) when is_integer(V) -> V; +spec_value(_) -> <<"unknown">>. + +module_names(dynamic) -> {<<"dynamic">>, null}; +module_names([M]) when is_atom(M) -> {[bin(M)], bin(M)}; +module_names(L) when is_list(L) -> {[bin(M) || M <- L, is_atom(M)], null}; +module_names(_) -> {<<"unknown">>, null}. + +behaviours_from_name([M]) when is_atom(M) -> behaviours_field(M); +behaviours_from_name(_) -> <<"unknown">>. + +module_edges(Mods, EntId, B) when is_list(Mods) -> + lists:foldl( + fun(M, Acc) when is_atom(M) -> + MId = module_id(M), + Acc1 = add_entity(Acc, MId, #{<<"kind">> => <<"module">>, <<"name">> => bin(M), + <<"loaded">> => erlang:module_loaded(M), + <<"behaviours">> => behaviours_field(M)}), + add_rel(Acc1, <<"uses_module">>, EntId, MId, + <<"supervisor child specification modules">>, <<"confirmed">>); + (_, Acc) -> Acc + end, B, Mods); +module_edges(_, _, B) -> B. + +%%------------------------------------------------------------------------------ +%% registered_processes +%%------------------------------------------------------------------------------ + +registered_processes(Args, #{enc := Enc, config := Config} = Ctx) -> + Started = mcp_store:now_ms(), + case select_application(maps:get(<<"application">>, Args, undefined)) of + {error, _, _} = E -> E; + {ok, Selected} -> + Cap = mcp_policy:limit(max_items, Config) * 4, + All = [N || N <- registered(), not inspector_name(N)], + Sorted = lists:sort(All), + Shown = lists:sublist(Sorted, Cap), + Masters = masters(Ctx), + {Confirmed, B0} = case Selected of + {app, App} -> tree_pids(App, Ctx); + all -> {#{}, new_b()} + end, + {B1, Unknown} = lists:foldl( + fun(Name, {Acc, Unk}) -> + reg_entry(Name, Selected, Confirmed, Masters, Enc, Acc, Unk) + end, {B0, 0}, Shown), + B2 = case Unknown of + 0 -> B1; + _ -> add_omission(B1, <<"unknown_membership">>, Unknown, undefined, + <<"registered processes not attributable to the selected application were left out">>, + false) + end, + B3 = case length(Sorted) > Cap of + true -> add_omission(B2, <<"limit_reached">>, length(Sorted) - Cap, undefined, + <<"registered process list truncated; filter by application">>, false); + false -> B2 + end, + Scope = #{<<"tool">> => <<"registered_processes">>, + <<"application">> => case Selected of all -> null; {app, A} -> app_id(A) end, + <<"limitations">> => + [<<"Only locally registered names are listed; unregistered processes are not enumerated.">>, + <<"A registered name alone never confirms application membership.">>, + <<"Inspector processes are excluded.">>]}, + finish_graph(<<"registered_processes">>, Args, Scope, B3, Started, Ctx) + end. + +masters(Ctx) -> + case started_apps(Ctx) of + {ok, Apps} -> + lists:foldl( + fun({App, _, _}, Acc) -> + case catch application_controller:get_master(App) of + M when is_pid(M) -> Acc#{M => App}; + _ -> Acc + end + end, #{}, Apps); + _ -> #{} + end. + +%% pids found in an application's supervision tree (confirmed membership) +tree_pids(App, #{config := Config} = Ctx) -> + case root_info(App, Ctx) of + {discovered, Root} -> + Cfg = #{max_depth => mcp_policy:limit(max_traversal_depth, Config), + include_modules => false, app => App, ctx => Ctx}, + B = walk([{Root, 0}], Cfg, #{}, new_b()), + Pids = (maps:get(pids, B))#{Root => true}, + {maps:map(fun(_, _) -> App end, Pids), new_b_with_oms(B)}; + _ -> + {#{}, add_omission(new_b(), <<"unknown_membership">>, 1, app_id(App), + <<"no root supervisor is discoverable for this application">>, false)} + end. + +new_b_with_oms(B) -> (new_b())#{oms := maps:get(oms, B)}. + +reg_entry(Name, Selected, Confirmed, Masters, Enc, B, Unknown) -> + case whereis(Name) of + undefined -> {B, Unknown}; + Pid -> + {Conf, App, Evidence} = membership(Pid, Confirmed, Masters), + Keep = case Selected of + all -> true; + {app, Wanted} -> App =:= Wanted andalso Conf =/= <<"unknown">> + end, + case Keep of + false -> {B, Unknown + 1}; + true -> + Info = erlang:process_info(Pid, [status, message_queue_len]), + Fields = #{<<"role">> => <<"unknown">>, + <<"status">> => bin(proplists:get_value(status, Info, unknown)), + <<"messageQueueLen">> => proplists:get_value(message_queue_len, Info, 0), + <<"application">> => case App of undefined -> null; _ -> bin(App) end, + <<"membership">> => #{<<"confidence">> => Conf, <<"evidence">> => Evidence}}, + _ = Enc, + {_, B1} = process_entity(Pid, Fields, B), + {B1, Unknown} + end + end. + +%% -> {Confidence, App | undefined, Evidence} +membership(Pid, Confirmed, Masters) -> + case maps:find(Pid, Confirmed) of + {ok, App} -> + {<<"confirmed">>, App, <<"found in the supervision tree of the application">>}; + error -> + case mcp_store:member(Pid) of + {App, _} -> + {<<"confirmed">>, App, <<"seen in a supervision tree observed earlier in this session">>}; + _ -> + case erlang:process_info(Pid, group_leader) of + {group_leader, GL} -> + case maps:find(GL, Masters) of + {ok, App} -> + {<<"inferred">>, App, <<"group leader is the application master (not proof of membership)">>}; + error -> {<<"unknown">>, undefined, <<"no evidence">>} + end; + _ -> {<<"unknown">>, undefined, <<"no evidence">>} + end + end + end. + +%%------------------------------------------------------------------------------ +%% process_info +%%------------------------------------------------------------------------------ + +tool_process_info(Args, #{config := Config} = Ctx) -> + Started = mcp_store:now_ms(), + case resolve_target(Args) of + {error, _, _} = E -> E; + {ok, Pid} -> + case is_process_alive(Pid) of + false -> {error, <<"not_found">>, <<"the process does not exist (anymore)">>}; + true -> + Items = [status, current_function, initial_call, reductions, memory, + message_queue_len, links, monitors], + case erlang:process_info(Pid, Items) of + undefined -> {error, <<"not_found">>, <<"the process exited during the observation">>}; + Info -> + build_process_info(Pid, Info, Started, Args, Ctx, Config) + end + end + end. + +build_process_info(Pid, Info, Started, Args, #{enc := Enc} = Ctx, Config) -> + Max = mcp_policy:limit(max_items, Config), + {Conf, App, Evidence} = membership(Pid, #{}, masters(Ctx)), + Fields = #{<<"role">> => <<"unknown">>, + <<"status">> => bin(proplists:get_value(status, Info, unknown)), + <<"currentFunction">> => mfa_text(proplists:get_value(current_function, Info)), + <<"initialCall">> => mfa_text(proplists:get_value(initial_call, Info)), + <<"reductions">> => proplists:get_value(reductions, Info, 0), + <<"memory">> => proplists:get_value(memory, Info, 0), + <<"memoryUnit">> => <<"bytes">>, + <<"messageQueueLen">> => proplists:get_value(message_queue_len, Info, 0), + <<"application">> => case App of undefined -> null; _ -> bin(App) end, + <<"membership">> => #{<<"confidence">> => Conf, <<"evidence">> => Evidence}, + <<"callbackModule">> => <<"unknown">>, + <<"behaviour">> => <<"unknown">>}, + {Id, B1} = process_entity(Pid, Fields, new_b()), + Links = [L || L <- proplists:get_value(links, Info, []), is_pid(L) orelse is_port(L)], + Mons = [M || {process, M} <- proplists:get_value(monitors, Info, []), is_pid(M)], + {B2, Omitted1} = peers(Links, <<"linked_to">>, <<"process_info links">>, Id, Max, B1), + {B3, Omitted2} = peers(Mons, <<"monitors">>, <<"process_info monitors">>, Id, Max, B2), + B4 = case Omitted1 + Omitted2 of + 0 -> B3; + N -> add_omission(B3, <<"limit_reached">>, N, Id, + <<"links/monitors truncated to max_items">>, false) + end, + _ = Enc, + Scope = #{<<"tool">> => <<"process_info">>, <<"process">> => Id, + <<"limitations">> => + [<<"Links and monitors do not imply supervision or message traffic.">>, + <<"Callback module and behaviour are only reported when supervisor child metadata is available (see supervision_tree).">>, + <<"Process dictionary, stack, mailbox and state are never read.">>]}, + finish_graph(<<"process_info">>, Args, Scope, B4, Started, Ctx). + +peers(Peers, Type, Evidence, FromId, Max, B) -> + {Shown, Rest} = case length(Peers) > Max of + true -> lists:split(Max, Peers); + false -> {Peers, []} + end, + B1 = lists:foldl( + fun(P, Acc) when is_pid(P), node(P) =:= node() -> + case is_process_alive(P) of + true -> + {PId, Acc1} = process_entity(P, #{<<"role">> => <<"unknown">>}, Acc), + add_rel(Acc1, Type, FromId, PId, Evidence, <<"confirmed">>); + false -> Acc + end; + (P, Acc) when is_port(P) -> + PId = mcp_store:entity_id(process, {port, P}), + Acc1 = add_entity(Acc, PId, #{<<"kind">> => <<"process">>, <<"role">> => <<"port">>, + <<"pid">> => null, <<"name">> => null, <<"alive">> => true}), + add_rel(Acc1, Type, FromId, PId, Evidence, <<"confirmed">>); + (_, Acc) -> Acc + end, B, Shown), + {B1, length(Rest)}. + +mfa_text({M, F, A}) when is_atom(M), is_atom(F), is_integer(A) -> + iolist_to_binary([atom_to_binary(M, utf8), ":", atom_to_binary(F, utf8), "/", integer_to_binary(A)]); +mfa_text(_) -> <<"unknown">>. + +resolve_target(#{<<"id">> := Id}) -> + case mcp_store:resolve_id(Id) of + {ok, {process, Pid}} when is_pid(Pid) -> {ok, Pid}; + {ok, _} -> {error, <<"invalid_id">>, <<"the id does not designate a live process">>}; + expired -> {error, <<"unknown_or_expired_id">>, <<"unknown or expired entity id">>} + end; +resolve_target(#{<<"name">> := Name}) -> resolve_process(Name); +resolve_target(#{<<"pid">> := Text}) -> resolve_process(Text). + +%% Registered name (existing atoms only) or local pid text. Never creates atoms. +resolve_process(<<"<", _/binary>> = Text) -> + case re:run(Text, "^<0\\.[0-9]{1,10}\\.[0-9]{1,10}>$", [{capture, none}]) of + match -> + try list_to_pid(binary_to_list(Text)) of + Pid when node(Pid) =:= node() -> {ok, Pid} + catch _:_ -> {error, <<"invalid_pid">>, <<"not a valid local pid">>} + end; + nomatch -> {error, <<"invalid_pid">>, <<"only local pids of this node are accepted">>} + end; +resolve_process(Name) when is_binary(Name) -> + try binary_to_existing_atom(Name, utf8) of + Atom -> + case whereis(Atom) of + undefined -> {error, <<"not_found">>, <<"no process is registered under this name">>}; + Pid -> {ok, Pid} + end + catch _:_ -> {error, <<"not_found">>, <<"no process is registered under this name">>} + end. + +%%------------------------------------------------------------------------------ +%% ets_tables (metadata of approved named tables only) +%%------------------------------------------------------------------------------ + +ets_tables(Args, #{config := Config} = Ctx) -> + Started = mcp_store:now_ms(), + Names = maps:get(allowed_ets_tables, Config), + {B, Denied, Gone} = lists:foldl( + fun(Name, {Acc, D, G}) -> ets_entry(Name, Acc, D, G) end, + {new_b(), 0, 0}, Names), + B1 = case Denied of + 0 -> B; + _ -> add_omission(B, <<"policy_denied">>, Denied, undefined, + <<"approved tables that are private are never inspected">>, false) + end, + B2 = case Gone of + 0 -> B1; + _ -> add_omission(B1, <<"disappeared">>, Gone, undefined, + <<"approved tables that do not exist (or no longer exist) on the node">>, false) + end, + Scope = #{<<"tool">> => <<"ets_tables">>, + <<"approvedTables">> => length(Names), + <<"limitations">> => + [<<"Only tables approved by allowed_ets_tables are inspected; no node-wide inventory exists.">>, + <<"Keys, objects and values are never read.">>, + <<"memory is reported in words with memoryBytes derived from the VM word size.">>]}, + finish_graph(<<"ets_tables">>, Args, Scope, B2, Started, Ctx). + +ets_entry(NameBin, B, Denied, Gone) -> + Atom = try binary_to_existing_atom(NameBin, utf8) catch _:_ -> undefined end, + case Atom of + undefined -> {B, Denied, Gone + 1}; + _ -> + case catch ets:info(Atom, protection) of + undefined -> {B, Denied, Gone + 1}; + private -> {B, Denied + 1, Gone}; + Prot when Prot =:= public; Prot =:= protected -> + case ets_facts(Atom, Prot) of + undefined -> {B, Denied, Gone + 1}; + {Owner, Facts} -> + TabIdent = ets_identity(Atom, Owner), + TId = mcp_store:entity_id(ets_table, {ets, Atom, TabIdent}), + B1 = add_entity(B, TId, Facts#{<<"kind">> => <<"ets_table">>}), + {OwnerId, B2} = process_entity(Owner, #{<<"role">> => <<"unknown">>}, B1), + B3 = add_rel(B2, <<"owns_table">>, OwnerId, TId, <<"ets:info owner">>, <<"confirmed">>), + {B3, Denied, Gone} + end; + _ -> {B, Denied, Gone + 1} + end + end. + +ets_facts(Tab, Prot) -> + try + Owner = ets:info(Tab, owner), + Words = ets:info(Tab, memory), + true = is_pid(Owner) andalso is_integer(Words), + {Owner, #{<<"name">> => bin(Tab), + <<"protection">> => bin(Prot), + <<"type">> => bin(ets:info(Tab, type)), + <<"size">> => ets:info(Tab, size), + <<"memory">> => Words, + <<"memoryUnit">> => <<"words">>, + <<"memoryBytes">> => Words * erlang:system_info(wordsize), + <<"owner">> => pid_id(Owner)}} + catch _:_ -> undefined + end. + +%% A recreated table has a different identity even if its name is reused. +ets_identity(Tab, Owner) -> + try ets:info(Tab, id) of + Id when Id =/= undefined -> Id; + _ -> {Owner, ets:info(Tab, heir)} + catch _:_ -> {Owner, undefined} + end. diff --git a/apps/erlangbridge/src/mcp/mcp_server.erl b/apps/erlangbridge/src/mcp/mcp_server.erl new file mode 100644 index 00000000..85c52a40 --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_server.erl @@ -0,0 +1,504 @@ +%% Authenticated MCP endpoint (Streamable HTTP, JSON responses only) of the +%% embedded inspector. +%% +%% Pinned protocol revision : 2025-06-18. Stateless: no Mcp-Session-Id is +%% issued, no SSE stream (GET -> 405). Implemented methods: initialize, +%% notifications/* (202), ping, tools/list, tools/call. No prompts, no +%% resources. Batches are rejected (removed in this revision). +%% +%% Security: explicit loopback bind, Host/Origin validation, no CORS, bearer +%% token required on every request *before* the body is read, body size cap, +%% header/body/connection timeouts, bounded connections and concurrent tool +%% calls. Tokens and parameters are never logged. +-module(mcp_server). +-behaviour(gen_server). + +-export([start_link/2, info/0, renew/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +%% spawned processes +-export([acceptor/3, conn_main/2]). + +-define(PROTOCOL_VERSION, <<"2025-06-18">>). +-define(PATH, <<"/mcp">>). +-define(LEASE_MS, 8000). +-define(LEASE_CHECK_MS, 1000). +-define(IO_TIMEOUT_MS, 5000). +-define(CONN_LIFETIME_MS, 30000). +-define(MAX_HEADERS, 64). + +-record(state, {listen, port, host, token, config, lease_deadline, conns = #{}, max_conns}). + +start_link(Config, Token) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, {Config, Token}, []). + +info() -> gen_server:call(?MODULE, info). + +%% Lease renewal from the debug adapter. It runs in the server process, never in +%% an application process, so it keeps working while the application is stopped +%% at a breakpoint. +renew() -> gen_server:cast(?MODULE, renew). + +%%------------------------------------------------------------------------------ + +init({Config, Token}) -> + process_flag(trap_exit, true), + Host = maps:get(host, Config), + {ok, Ip} = inet:parse_strict_address(Host), + Family = case tuple_size(Ip) of 4 -> []; 8 -> [inet6] end, + Opts = Family ++ [binary, {active, false}, {reuseaddr, true}, {ip, Ip}, + {packet, raw}, {backlog, 16}, {send_timeout, ?IO_TIMEOUT_MS}, + {send_timeout_close, true}], + case gen_tcp:listen(maps:get(port, Config), Opts) of + {ok, L} -> + {ok, Port} = inet:port(L), + Max = mcp_policy:limit(max_concurrency, Config) * 4, + State = #state{listen = L, port = Port, host = Host, token = Token, config = Config, + lease_deadline = mono() + ?LEASE_MS, max_conns = Max}, + spawn_link(?MODULE, acceptor, [L, self(), Token]), + erlang:send_after(?LEASE_CHECK_MS, self(), lease_check), + mcp_audit:event(listening, #{port => Port}), + {ok, State}; + {error, Reason} -> + mcp_audit:event(listen_failed, #{reason => Reason}), + {stop, {listen_failed, Reason}} + end. + +handle_call(info, _, #state{host = Host, port = Port} = S) -> + {reply, #{host => Host, port => Port, path => ?PATH}, S}; +handle_call({conn, Pid}, _, #state{conns = Conns, max_conns = Max} = S) -> + case maps:size(Conns) >= Max of + true -> {reply, busy, S}; + false -> + Ref = erlang:monitor(process, Pid), + erlang:send_after(?CONN_LIFETIME_MS, self(), {conn_timeout, Pid}), + {reply, ok, S#state{conns = Conns#{Pid => Ref}}} + end; +handle_call(_, _, S) -> + {reply, {error, unsupported}, S}. + +handle_cast(renew, S) -> + {noreply, S#state{lease_deadline = mono() + ?LEASE_MS}}; +handle_cast(_, S) -> + {noreply, S}. + +handle_info(lease_check, #state{lease_deadline = D} = S) -> + case mono() > D of + true -> + mcp_audit:event(lease_expired, #{}), + {stop, lease_expired, S}; + false -> + erlang:send_after(?LEASE_CHECK_MS, self(), lease_check), + {noreply, S} + end; +handle_info({'DOWN', _, process, Pid, _}, #state{conns = Conns} = S) -> + {noreply, S#state{conns = maps:remove(Pid, Conns)}}; +handle_info({conn_timeout, Pid}, S) -> + exit(Pid, kill), + {noreply, S}; +handle_info({'EXIT', _Acceptor, Reason}, S) -> + {stop, {acceptor_exit, Reason}, S}; +handle_info(_, S) -> + {noreply, S}. + +terminate(_, #state{listen = L, conns = Conns}) -> + catch gen_tcp:close(L), + [exit(Pid, kill) || Pid <- maps:keys(Conns)], + ok. + +mono() -> erlang:monotonic_time(millisecond). + +%%------------------------------------------------------------------------------ +%% Acceptor and connections +%%------------------------------------------------------------------------------ + +acceptor(L, Server, Token) -> + case gen_tcp:accept(L) of + {ok, Sock} -> + Pid = spawn(?MODULE, conn_main, [Sock, {Server, Token}]), + ok = gen_tcp:controlling_process(Sock, Pid), + Pid ! case gen_server:call(Server, {conn, Pid}) of + ok -> go; + busy -> busy + end, + acceptor(L, Server, Token); + {error, _} -> + exit(normal) + end. + +conn_main(Sock, {Server, Token}) -> + receive + go -> + try + handle_connection(Sock, Token) + catch + _:_ -> reply(Sock, 500, [], <<"{\"error\":\"internal error\"}">>) + end; + busy -> + reply(Sock, 503, [{<<"Retry-After">>, <<"1">>}], <<"{\"error\":\"too many connections\"}">>) + after ?IO_TIMEOUT_MS -> + ok + end, + _ = Server, + gen_tcp:close(Sock). + +handle_connection(Sock, Token) -> + Config = mcp_store:config(), + #{port := Port} = ?MODULE:info(), + Limits = maps:get(limits, Config), + MaxBody = maps:get(max_request_bytes, Limits), + ok = inet:setopts(Sock, [{packet, http_bin}, {packet_size, 8192}]), + case read_head(Sock) of + {ok, Method, Path, Headers} -> + case check_head(Method, Path, Headers, Port, Config, Token, MaxBody) of + {reject, Code, Extra, Body} -> reply(Sock, Code, Extra, Body); + {ok, Len} -> + ok = inet:setopts(Sock, [{packet, raw}]), + case read_body(Sock, Len) of + {ok, Body} -> serve(Sock, Body, Config); + error -> reply(Sock, 400, [], err_body(<<"incomplete request body">>)) + end + end; + error -> + reply(Sock, 400, [], err_body(<<"malformed request">>)) + end. + +read_head(Sock) -> + case gen_tcp:recv(Sock, 0, ?IO_TIMEOUT_MS) of + {ok, {http_request, Method, {abs_path, Path}, _}} -> + case read_headers(Sock, #{}, 0) of + {ok, Headers} -> {ok, Method, Path, Headers}; + error -> error + end; + _ -> error + end. + +read_headers(_, _, N) when N > ?MAX_HEADERS -> error; +read_headers(Sock, Acc, N) -> + case gen_tcp:recv(Sock, 0, ?IO_TIMEOUT_MS) of + {ok, http_eoh} -> {ok, Acc}; + {ok, {http_header, _, Field, _, Value}} -> + Name = header_name(Field), + case maps:is_key(Name, Acc) of + true when Name =:= <<"host">>; Name =:= <<"authorization">>; + Name =:= <<"content-length">>; Name =:= <<"origin">> -> + error; %% duplicated security-relevant header + _ -> read_headers(Sock, Acc#{Name => Value}, N + 1) + end; + _ -> error + end. + +header_name(A) when is_atom(A) -> lower(atom_to_binary(A, utf8)); +header_name(B) when is_binary(B) -> lower(B). + +lower(B) -> list_to_binary(string:to_lower(binary_to_list(B))). + +%% Cheap checks first; the bearer token before the body is read. +check_head(Method, Path, Headers, Port, Config, Token, MaxBody) -> + Checks = [fun() -> Path =:= ?PATH orelse {reject, 404, [], err_body(<<"not found">>)} end, + fun() -> Method =:= 'POST' orelse + {reject, 405, [{<<"Allow">>, <<"POST">>}], err_body(<<"method not allowed">>)} end, + fun() -> host_ok(maps:get(<<"host">>, Headers, undefined), Port, Config) orelse + {reject, 403, [], err_body(<<"forbidden host">>)} end, + fun() -> origin_ok(maps:get(<<"origin">>, Headers, undefined), Port, Config) orelse + {reject, 403, [], err_body(<<"forbidden origin">>)} end, + fun() -> auth_ok(maps:get(<<"authorization">>, Headers, undefined), Token) orelse + {reject, 401, [{<<"WWW-Authenticate">>, <<"Bearer">>}], err_body(<<"unauthorized">>)} end, + fun() -> content_type_ok(maps:get(<<"content-type">>, Headers, undefined)) orelse + {reject, 415, [], err_body(<<"content type must be application/json">>)} end, + fun() -> not maps:is_key(<<"transfer-encoding">>, Headers) orelse + {reject, 411, [], err_body(<<"content-length required">>)} end], + case run_checks(Checks) of + ok -> length_of(maps:get(<<"content-length">>, Headers, undefined), MaxBody); + Reject -> + mcp_audit:event(rejected, #{code => element(2, Reject)}), + journal(#{method => <<"http">>, status => integer_to_binary(element(2, Reject))}), + Reject + end. + +run_checks([]) -> ok; +run_checks([C | T]) -> + case C() of + true -> run_checks(T); + {reject, _, _, _} = R -> R + end. + +length_of(undefined, _) -> {reject, 411, [], err_body(<<"content-length required">>)}; +length_of(Bin, Max) -> + try binary_to_integer(Bin) of + N when N >= 0, N =< Max -> {ok, N}; + N when N > Max -> {reject, 413, [], err_body(<<"request too large">>)}; + _ -> {reject, 400, [], err_body(<<"invalid content-length">>)} + catch _:_ -> {reject, 400, [], err_body(<<"invalid content-length">>)} + end. + +allowed_hosts(Port, Config) -> + P = integer_to_binary(Port), + case inet:parse_strict_address(maps:get(host, Config)) of + {ok, {127, 0, 0, 1}} -> [<<"127.0.0.1:", P/binary>>, <<"localhost:", P/binary>>]; + {ok, {127, _, _, _} = Ip} -> [iolist_to_binary([inet:ntoa(Ip), ":", P])]; + {ok, _} -> [<<"[::1]:", P/binary>>, <<"localhost:", P/binary>>] + end. + +host_ok(undefined, _, _) -> false; +host_ok(Host, Port, Config) -> lists:member(lower(Host), allowed_hosts(Port, Config)). + +%% No Origin (non-browser client) is accepted; a browser Origin must itself be loopback. +origin_ok(undefined, _, _) -> true; +origin_ok(Origin, Port, Config) -> + lists:any(fun(H) -> lower(Origin) =:= <<"http://", H/binary>> end, allowed_hosts(Port, Config)). + +auth_ok(undefined, _) -> false; +auth_ok(Value, Token) -> + case binary:split(Value, <<" ">>) of + [Scheme, Presented] -> lower(Scheme) =:= <<"bearer">> andalso ct_eq(Presented, Token); + _ -> false + end. + +%% Timing-resistant comparison (length is not secret: tokens have a fixed size). +ct_eq(A, B) when byte_size(A) =:= byte_size(B) -> + ct_fold(binary_to_list(A), binary_to_list(B), 0) =:= 0; +ct_eq(_, _) -> false. + +ct_fold([], [], Acc) -> Acc; +ct_fold([X | Xs], [Y | Ys], Acc) -> ct_fold(Xs, Ys, Acc bor (X bxor Y)). + +content_type_ok(undefined) -> false; +content_type_ok(Value) -> + [Type | _] = binary:split(lower(Value), <<";">>), + trim(Type) =:= <<"application/json">>. + +trim(B) -> list_to_binary(string:strip(binary_to_list(B))). + +read_body(_Sock, 0) -> {ok, <<>>}; +read_body(Sock, Len) -> + case gen_tcp:recv(Sock, Len, ?IO_TIMEOUT_MS) of + {ok, Bin} -> {ok, Bin}; + _ -> error + end. + +%%------------------------------------------------------------------------------ +%% JSON-RPC +%%------------------------------------------------------------------------------ + +serve(Sock, Body, Config) -> + T0 = erlang:monotonic_time(millisecond), + {Code, Resp, Meta} = handle_rpc(Body, Config), + reply(Sock, Code, [], Resp), + journal(Meta#{durationMs => erlang:monotonic_time(millisecond) - T0, + bytes => byte_size(Resp)}). + +%% -> {HttpCode, ResponseBody, JournalMeta} +handle_rpc(Body, Config) -> + case decode(Body) of + {ok, #{<<"jsonrpc">> := <<"2.0">>, <<"method">> := Method} = Req} when is_binary(Method) -> + Id = maps:get(<<"id">>, Req, undefined), + Params = maps:get(<<"params">>, Req, #{}), + case {Id, is_map(Params)} of + {undefined, _} -> + %% notification (e.g. notifications/initialized, notifications/cancelled) + {202, <<>>, #{method => Method, status => <<"ok">>}}; + {_, false} -> + rpc(error_obj(Id, -32602, <<"params must be an object">>), Method, Params); + {_, true} when is_integer(Id); is_binary(Id) -> + rpc(dispatch(Method, Params, Id, Config), Method, Params); + _ -> + rpc(error_obj(null, -32600, <<"invalid request id">>), Method, #{}) + end; + {ok, L} when is_list(L) -> + rpc(error_obj(null, -32600, <<"batch requests are not supported">>), <<"batch">>, #{}); + {ok, _} -> + rpc(error_obj(null, -32600, <<"invalid request">>), <<"invalid">>, #{}); + error -> + {400, encode(error_obj(null, -32700, <<"parse error">>)), + #{method => <<"invalid">>, status => <<"-32700">>}} + end. + +rpc(Obj, Method, Params) -> + {200, encode(Obj), journal_meta(Method, Params, Obj)}. + +%% Request journal (see mcp_store:notify/1): request metadata only - never the +%% arguments, the result content or the token. The tool name is only kept when +%% it is a known tool, so a client cannot inject text in the journal. +journal_meta(Method, Params, Obj) -> + M0 = #{method => known_method(Method)}, + M1 = case Method of + <<"tools/call">> -> + Tool = maps:get(<<"name">>, Params, undefined), + Args = maps:get(<<"arguments">>, Params, #{}), + M0#{tool => case is_binary(Tool) andalso mcp_tools:known(Tool) of + true -> Tool; + false -> <<"?">> + end, + cursor => is_map(Args) andalso maps:is_key(<<"cursor">>, Args)}; + _ -> M0 + end, + maps:merge(M1, outcome(Obj)). + +known_method(M) -> + Known = [<<"initialize">>, <<"ping">>, <<"tools/list">>, <<"tools/call">>, + <<"notifications/initialized">>, <<"notifications/cancelled">>], + case lists:member(M, Known) of + true -> M; + false -> <<"other">> + end. + +outcome(#{<<"error">> := #{<<"code">> := Code}}) -> + #{status => integer_to_binary(Code)}; +outcome(#{<<"result">> := #{<<"isError">> := true, <<"structuredContent">> := #{<<"error">> := #{<<"code">> := C}}}}) -> + #{status => C}; +outcome(#{<<"result">> := #{<<"structuredContent">> := S}}) when is_map(S) -> + Graph = case S of + #{<<"entities">> := E, <<"relationships">> := R} -> + #{entities => length(E), relationships => length(R), + omissions => length(maps:get(<<"omissions">>, S, [])), + complete => maps:get(<<"complete">>, S, true), + more => is_binary(maps:get(<<"nextCursor">>, S, null)), + offset => maps:get(<<"offset">>, maps:get(<<"page">>, S, #{}), 0)}; + _ -> #{} + end, + Graph#{status => <<"ok">>}; +outcome(_) -> + #{status => <<"ok">>}. + +journal(Meta) -> + catch mcp_store:notify(Meta), + ok. + +decode(Body) -> + try vscode_jsone_decode:decode(Body) of + {ok, Term, Rest} -> + case binary:replace(Rest, [<<" ">>, <<"\r">>, <<"\n">>, <<"\t">>], <<>>, [global]) of + <<>> -> {ok, Term}; + _ -> error + end; + _ -> error + catch _:_ -> error + end. + +encode(Term) -> + {ok, Json} = vscode_jsone:encode(Term), + iolist_to_binary(Json). + +error_obj(Id, Code, Message) -> + #{<<"jsonrpc">> => <<"2.0">>, <<"id">> => Id, + <<"error">> => #{<<"code">> => Code, <<"message">> => Message}}. + +result_obj(Id, Result) -> + #{<<"jsonrpc">> => <<"2.0">>, <<"id">> => Id, <<"result">> => Result}. + +dispatch(<<"initialize">>, Params, Id, _Config) -> + case maps:get(<<"protocolVersion">>, Params, undefined) of + V when is_binary(V) -> + %% version negotiation: the server answers with the revision it implements + result_obj(Id, #{<<"protocolVersion">> => ?PROTOCOL_VERSION, + <<"capabilities">> => #{<<"tools">> => #{<<"listChanged">> => false}}, + <<"serverInfo">> => #{<<"name">> => <<"erlang-otp-topology-inspector">>, + <<"title">> => <<"Erlang OTP topology inspector">>, + <<"version">> => <<"1.0.0">>}, + <<"instructions">> => + <<"Read-only structural map of the debugged Erlang node. Workflow: runtime_summary, " + "then application_overview (no modules), pick the application and follow its 'roots' " + "ids with supervision_tree (expand deeper subtrees with the ids given in 'limit_reached' " + "omissions), then process_info / registered_processes(application=id) / ets_tables / " + "debug_session only where needed. Ids are reusable across tools. Default detail=summary " + "keeps answers small; follow nextCursor only if you need the remaining items. Use " + "detail=full only when the user asks for the complete JSON (e.g. to save it to a file). " + "Maps are bounded observation intervals, not atomic snapshots: honour complete/" + "truncated/omissions. Structural edges are not message traffic.">>}); + _ -> + error_obj(Id, -32602, <<"protocolVersion is required">>) + end; +dispatch(<<"ping">>, _, Id, _) -> + result_obj(Id, #{}); +dispatch(<<"tools/list">>, _, Id, Config) -> + result_obj(Id, #{<<"tools">> => mcp_tools:list(Config)}); +dispatch(<<"tools/call">>, Params, Id, Config) -> + tools_call(Params, Id, Config); +dispatch(_, _, Id, _) -> + error_obj(Id, -32601, <<"method not found">>). + +tools_call(Params, Id, Config) -> + Name = maps:get(<<"name">>, Params, undefined), + Args = maps:get(<<"arguments">>, Params, #{}), + Known = is_binary(Name) andalso mcp_tools:known(Name) andalso mcp_policy:tool_allowed(Name, Config), + case Known of + false -> + error_obj(Id, -32602, <<"unknown tool">>); + true -> + case mcp_tools:validate_args(Name, Args) of + {error, Msg} -> error_obj(Id, -32602, Msg); + ok -> run_tool(Name, Args, Id, Config) + end + end. + +run_tool(Name, Args, Id, Config) -> + case mcp_store:acquire_slot(self()) of + busy -> + mcp_audit:event(tool_call, #{tool => Name, status => busy}), + error_obj(Id, -32000, <<"inspector busy: too many concurrent requests, retry later">>); + ok -> + T0 = erlang:monotonic_time(millisecond), + Timeout = mcp_policy:limit(request_timeout_ms, Config), + Ctx = #{config => Config, timeout_ms => Timeout}, + Res = mcp_runtime:bounded(fun() -> mcp_runtime:call(Name, Args, Ctx) end, Timeout), + mcp_store:release_slot(self()), + {Status, Result} = tool_result(Res, Config), + mcp_audit:event(tool_call, #{tool => Name, status => Status, + duration_ms => erlang:monotonic_time(millisecond) - T0}), + result_obj(Id, Result) + end. + +tool_result({ok, {ok, Structured}}, Config) -> + Result = #{<<"content">> => [#{<<"type">> => <<"text">>, <<"text">> => encode(Structured)}], + <<"structuredContent">> => Structured, + <<"isError">> => false}, + Max = mcp_policy:limit(max_result_bytes, Config), + case byte_size(encode(result_obj(0, Result))) =< Max of + true -> {ok, Result}; + false -> {result_too_large, tool_error(<<"result_too_large">>, + <<"the result exceeds max_result_bytes; narrow the scope">>)} + end; +tool_result({ok, {error, Code, Message}}, _) -> + {error, tool_error(Code, Message)}; +tool_result(timeout, _) -> + {timeout, tool_error(<<"timeout">>, <<"the inspection exceeded the request time limit; narrow the scope or retry">>)}; +tool_result({error, _}, _) -> + %% no stack trace or internal term is exposed + {error, tool_error(<<"internal_error">>, <<"the inspection failed">>)}. + +tool_error(Code, Message) -> + Structured = #{<<"error">> => #{<<"code">> => Code, <<"message">> => Message}}, + #{<<"content">> => [#{<<"type">> => <<"text">>, <<"text">> => encode(Structured)}], + <<"structuredContent">> => Structured, + <<"isError">> => true}. + +err_body(Message) -> + encode(#{<<"error">> => Message}). + +%%------------------------------------------------------------------------------ +%% HTTP response +%%------------------------------------------------------------------------------ + +reply(Sock, Code, Extra, Body) -> + Headers = [{<<"Content-Type">>, <<"application/json">>}, + {<<"Content-Length">>, integer_to_binary(byte_size(Body))}, + {<<"Cache-Control">>, <<"no-store">>}, + {<<"Connection">>, <<"close">>} | Extra], + Head = [<<"HTTP/1.1 ">>, integer_to_binary(Code), $\s, reason(Code), <<"\r\n">>, + [[K, <<": ">>, V, <<"\r\n">>] || {K, V} <- Headers], <<"\r\n">>], + catch gen_tcp:send(Sock, [Head, Body]), + ok. + +reason(200) -> <<"OK">>; +reason(202) -> <<"Accepted">>; +reason(400) -> <<"Bad Request">>; +reason(401) -> <<"Unauthorized">>; +reason(403) -> <<"Forbidden">>; +reason(404) -> <<"Not Found">>; +reason(405) -> <<"Method Not Allowed">>; +reason(411) -> <<"Length Required">>; +reason(413) -> <<"Payload Too Large">>; +reason(415) -> <<"Unsupported Media Type">>; +reason(500) -> <<"Internal Server Error">>; +reason(503) -> <<"Service Unavailable">>; +reason(_) -> <<"Error">>. diff --git a/apps/erlangbridge/src/mcp/mcp_store.erl b/apps/erlangbridge/src/mcp/mcp_store.erl new file mode 100644 index 00000000..f642eee3 --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_store.erl @@ -0,0 +1,318 @@ +%% Per-session state of the MCP inspector: the session identity, bounded +%% entity-ID bookkeeping, retained collections (with TTL/count/byte budgets), +%% signed cursors, worker slots and the process-membership evidence. +%% +%% Everything here dies with the session (the process is a child of mcp_sup). +-module(mcp_store). +-behaviour(gen_server). + +-export([start_link/2, start_link/3, stop/0]). +-export([session_id/0, mode/0, config/0, started_at/0, now_ms/0]). +-export([entity_id/2, resolve_id/1]). +-export([put_collection/5, fetch_collection/3, make_cursor/4, parse_cursor/3]). +-export([acquire_slot/1, release_slot/1]). +-export([note_member/3, member/1]). +-export([stats/0, notify/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). + +-define(SERVER, ?MODULE). +-define(SWEEP_MS, 1000). +-define(MAX_MEMBERS, 5000). + +-record(state, { + config, + session_id, + mode, + started_at, + secret, %% signs cursors + max_ids, + ids_fwd = #{}, %% Key => Id + ids_rev = #{}, %% Id => Key + id_order = queue:new(), %% oldest first + collections = #{}, %% CollId => collection map + coll_order = [], %% newest first + slots = #{}, %% MonitorRef => Pid + notify, %% undefined | fun((map()) -> any()): request journal + seq = 0, %% MCP requests seen in this session + members = #{}, %% Pid => {AppName, ObservedAtMs} + member_order = queue:new() + }). + +start_link(Config, Mode) -> + start_link(Config, Mode, undefined). + +start_link(Config, Mode, Notify) -> + gen_server:start_link({local, ?SERVER}, ?MODULE, {Config, Mode, Notify}, []). + +stop() -> + catch gen_server:stop(?SERVER), + ok. + +session_id() -> gen_server:call(?SERVER, session_id). +mode() -> gen_server:call(?SERVER, mode). +config() -> gen_server:call(?SERVER, config). +started_at() -> gen_server:call(?SERVER, started_at). + +now_ms() -> erlang:system_time(millisecond). + +%% Stable id of an observed entity within the session. A restarted process or +%% a recreated table has a different Key and so a different id. +entity_id(Kind, Key) -> gen_server:call(?SERVER, {entity_id, Kind, Key}). + +%% -> {ok, Key} | expired +resolve_id(Id) when is_binary(Id) -> gen_server:call(?SERVER, {resolve_id, Id}); +resolve_id(_) -> expired. + +%% Meta: map (scope, startedAt, finishedAt, complete, truncated, omissions). +%% Items: [{entity, Map} | {relationship, Map}] in page order. +%% -> {CollId, Meta1, Stored}; items beyond the byte budget are dropped and the +%% collection is marked truncated with a non-resumable limit_reached omission. +put_collection(Tool, ArgsHash, Meta, Items, Ctx) -> + gen_server:call(?SERVER, {put_collection, Tool, ArgsHash, Meta, Items, Ctx}). + +%% -> {ok, Collection} | {error, cursor_expired} +fetch_collection(CollId, Tool, ArgsHash) -> + gen_server:call(?SERVER, {fetch_collection, CollId, Tool, ArgsHash}). + +make_cursor(CollId, Tool, ArgsHash, Offset) -> + gen_server:call(?SERVER, {make_cursor, CollId, Tool, ArgsHash, Offset}). + +%% -> {ok, CollId, Offset} | {error, invalid_cursor} +parse_cursor(Cursor, Tool, ArgsHash) when is_binary(Cursor) -> + gen_server:call(?SERVER, {parse_cursor, Cursor, Tool, ArgsHash}); +parse_cursor(_, _, _) -> + {error, invalid_cursor}. + +%% -> ok | busy. The slot is released when Pid exits or calls release_slot/1. +acquire_slot(Pid) -> gen_server:call(?SERVER, {acquire_slot, Pid}). +release_slot(Pid) -> gen_server:cast(?SERVER, {release_slot, Pid}). + +note_member(Pid, App, At) -> gen_server:cast(?SERVER, {note_member, Pid, App, At}). +member(Pid) -> gen_server:call(?SERVER, {member, Pid}). + +stats() -> gen_server:call(?SERVER, stats). + +%% Journal of MCP requests: numbers the request and hands the (non-secret) +%% metadata map to the notify fun in a separate process, so a slow consumer +%% never delays a request. +notify(Meta) when is_map(Meta) -> gen_server:cast(?SERVER, {notify, Meta}). + +%%------------------------------------------------------------------------------ + +init({Config, Mode, Notify}) -> + Limits = maps:get(limits, Config), + Secret = crypto:strong_rand_bytes(32), + SessionId = <<"s_", (mcp_encoder:hex(crypto:strong_rand_bytes(8)))/binary>>, + erlang:send_after(?SWEEP_MS, self(), sweep), + {ok, #state{config = Config, session_id = SessionId, mode = Mode, notify = Notify, + started_at = now_ms(), secret = Secret, + max_ids = maps:get(max_items, Limits) * 10}}. + +handle_call(session_id, _, S) -> {reply, S#state.session_id, S}; +handle_call(mode, _, S) -> {reply, S#state.mode, S}; +handle_call(config, _, S) -> {reply, S#state.config, S}; +handle_call(started_at, _, S) -> {reply, S#state.started_at, S}; + +handle_call({entity_id, Kind, Key}, _, S) -> + {Id, S1} = do_entity_id(Kind, Key, S), + {reply, Id, S1}; +handle_call({resolve_id, Id}, _, S) -> + {reply, case maps:find(Id, S#state.ids_rev) of + {ok, Key} -> {ok, Key}; + error -> expired + end, S}; + +handle_call({put_collection, Tool, ArgsHash, Meta, Items, Ctx}, _, S) -> + {CollId, Meta1, Stored, S1} = do_put_collection(Tool, ArgsHash, Meta, Items, Ctx, sweep_collections(S)), + {reply, {CollId, Meta1, Stored}, S1}; +handle_call({fetch_collection, CollId, Tool, ArgsHash}, _, S0) -> + S = sweep_collections(S0), + Reply = case maps:find(CollId, S#state.collections) of + {ok, #{tool := Tool, args_hash := ArgsHash} = C} -> {ok, C}; + _ -> {error, cursor_expired} + end, + {reply, Reply, S}; + +handle_call({make_cursor, CollId, Tool, ArgsHash, Offset}, _, S) -> + {reply, sign_cursor(CollId, Tool, ArgsHash, Offset, S), S}; +handle_call({parse_cursor, Cursor, Tool, ArgsHash}, _, S) -> + {reply, verify_cursor(Cursor, Tool, ArgsHash, S), S}; + +handle_call({acquire_slot, Pid}, _, #state{slots = Slots, config = Config} = S) -> + Max = mcp_policy:limit(max_concurrency, Config), + case maps:size(Slots) >= Max of + true -> {reply, busy, S}; + false -> + Ref = erlang:monitor(process, Pid), + {reply, ok, S#state{slots = Slots#{Ref => Pid}}} + end; + +handle_call({member, Pid}, _, S) -> + {reply, maps:get(Pid, S#state.members, undefined), S}; + +handle_call(stats, _, S) -> + {reply, #{ids => maps:size(S#state.ids_fwd), + collections => maps:size(S#state.collections), + collection_bytes => lists:sum([maps:get(bytes, C) || C <- maps:values(S#state.collections)]), + slots => maps:size(S#state.slots), + members => maps:size(S#state.members), + seq => S#state.seq}, S}; + +handle_call(_, _, S) -> {reply, {error, unsupported}, S}. + +handle_cast({release_slot, Pid}, #state{slots = Slots} = S) -> + Left = maps:filter(fun(Ref, P) when P =:= Pid -> erlang:demonitor(Ref, [flush]), false; + (_, _) -> true + end, Slots), + {noreply, S#state{slots = Left}}; +handle_cast({notify, Meta}, #state{notify = Notify, seq = Seq} = S) -> + N = Seq + 1, + case Notify of + F when is_function(F, 1) -> spawn(fun() -> catch F(Meta#{seq => N}) end); + _ -> ok + end, + {noreply, S#state{seq = N}}; +handle_cast({note_member, Pid, App, At}, S) -> + {noreply, do_note_member(Pid, App, At, S)}; +handle_cast(_, S) -> {noreply, S}. + +handle_info({'DOWN', Ref, process, _, _}, #state{slots = Slots} = S) -> + {noreply, S#state{slots = maps:remove(Ref, Slots)}}; +handle_info(sweep, S) -> + erlang:send_after(?SWEEP_MS, self(), sweep), + {noreply, sweep_collections(S)}; +handle_info(_, S) -> {noreply, S}. + +terminate(_, _) -> ok. + +%%------------------------------------------------------------------------------ +%% Identities +%%------------------------------------------------------------------------------ + +do_entity_id(Kind, Key, #state{ids_fwd = Fwd} = S) -> + case maps:find(Key, Fwd) of + {ok, Id} -> {Id, S}; + error -> + Id = <<(prefix(Kind))/binary, (mcp_encoder:hex(crypto:strong_rand_bytes(8)))/binary>>, + S1 = evict_ids(S#state{ids_fwd = Fwd#{Key => Id}, + ids_rev = (S#state.ids_rev)#{Id => Key}, + id_order = queue:in(Key, S#state.id_order)}), + {Id, S1} + end. + +prefix(application) -> <<"a_">>; +prefix(process) -> <<"p_">>; +prefix(module) -> <<"m_">>; +prefix(ets_table) -> <<"t_">>. + +%% Bounded bookkeeping: the oldest handles expire (resolve_id/1 -> expired). +evict_ids(#state{ids_fwd = Fwd, max_ids = Max} = S) when map_size(Fwd) =< Max -> S; +evict_ids(#state{id_order = Q, ids_fwd = Fwd, ids_rev = Rev} = S) -> + case queue:out(Q) of + {{value, Key}, Q1} -> + Id = maps:get(Key, Fwd), + evict_ids(S#state{id_order = Q1, ids_fwd = maps:remove(Key, Fwd), ids_rev = maps:remove(Id, Rev)}); + {empty, _} -> S + end. + +do_note_member(Pid, App, At, #state{members = M, member_order = Q} = S) -> + case maps:is_key(Pid, M) of + true -> S#state{members = M#{Pid => {App, At}}}; + false -> + S1 = S#state{members = M#{Pid => {App, At}}, member_order = queue:in(Pid, Q)}, + trim_members(S1) + end. + +trim_members(#state{members = M} = S) when map_size(M) =< ?MAX_MEMBERS -> S; +trim_members(#state{members = M, member_order = Q} = S) -> + case queue:out(Q) of + {{value, Pid}, Q1} -> trim_members(S#state{members = maps:remove(Pid, M), member_order = Q1}); + {empty, _} -> S + end. + +%%------------------------------------------------------------------------------ +%% Collections +%%------------------------------------------------------------------------------ + +do_put_collection(Tool, ArgsHash, Meta, Items, Ctx, #state{config = Config} = S) -> + MaxBytes = mcp_policy:limit(max_collection_bytes, Config), + MaxColls = mcp_policy:limit(max_collections, Config), + {Stored, Dropped} = fit(Items, MaxBytes, 0, []), + CollId = <<"c_", (mcp_encoder:hex(crypto:strong_rand_bytes(6)))/binary>>, + Meta1 = case Dropped of + 0 -> Meta; + _ -> add_limit_omission(Meta, Dropped) + end, + Bytes = erlang:external_size(Stored), + Coll = #{tool => Tool, args_hash => ArgsHash, meta => Meta1, items => Stored, + bytes => Bytes, created => erlang:monotonic_time(millisecond), ctx => Ctx}, + Colls = (S#state.collections)#{CollId => Coll}, + S1 = evict_collections(S#state{collections = Colls, coll_order = [CollId | S#state.coll_order]}, MaxColls), + {CollId, Meta1, Stored, S1}. + +fit([], _, _, Acc) -> {lists:reverse(Acc), 0}; +fit([I | T] = All, Max, Used, Acc) -> + Size = erlang:external_size(I), + case Used + Size > Max of + true -> {lists:reverse(Acc), length(All)}; + false -> fit(T, Max, Used + Size, [I | Acc]) + end. + +add_limit_omission(Meta, Dropped) -> + Om = #{<<"reason">> => <<"limit_reached">>, + <<"count">> => Dropped, + <<"resumable">> => false, + <<"message">> => <<"collection byte budget reached; narrow the scope (application, subtree or depth)">>}, + Meta#{omissions => maps:get(omissions, Meta, []) ++ [Om], truncated => true, complete => false}. + +evict_collections(#state{coll_order = Order, collections = Colls} = S, Max) -> + {Keep, Drop} = case length(Order) > Max of + true -> lists:split(Max, Order); + false -> {Order, []} + end, + S#state{coll_order = Keep, collections = maps:without(Drop, Colls)}. + +sweep_collections(#state{collections = Colls, config = Config} = S) -> + Ttl = mcp_policy:limit(collection_ttl_ms, Config), + Now = erlang:monotonic_time(millisecond), + Alive = maps:filter(fun(_, #{created := C}) -> Now - C < Ttl end, Colls), + S#state{collections = Alive, + coll_order = [Id || Id <- S#state.coll_order, maps:is_key(Id, Alive)]}. + +%%------------------------------------------------------------------------------ +%% Cursors: c1..., bound to session, collection, +%% tool and arguments. +%%------------------------------------------------------------------------------ + +sign_cursor(CollId, Tool, ArgsHash, Offset, S) -> + Sig = signature(CollId, Tool, ArgsHash, Offset, S), + <<"c1.", CollId/binary, ".", (integer_to_binary(Offset))/binary, ".", Sig/binary>>. + +signature(CollId, Tool, ArgsHash, Offset, #state{secret = Secret, session_id = Sid}) -> + Data = [Secret, Sid, 0, CollId, 0, Tool, 0, ArgsHash, 0, integer_to_binary(Offset)], + <> = crypto:hash(sha256, Data), + mcp_encoder:hex(Head). + +verify_cursor(Cursor, Tool, ArgsHash, S) -> + case binary:split(Cursor, <<".">>, [global]) of + [<<"c1">>, CollId, OffBin, Sig] when byte_size(CollId) < 64, byte_size(OffBin) < 12 -> + try binary_to_integer(OffBin) of + Offset when Offset >= 0 -> + case constant_eq(Sig, signature(CollId, Tool, ArgsHash, Offset, S)) of + true -> {ok, CollId, Offset}; + false -> {error, invalid_cursor} + end; + _ -> {error, invalid_cursor} + catch _:_ -> {error, invalid_cursor} + end; + _ -> + {error, invalid_cursor} + end. + +constant_eq(A, B) when byte_size(A) =:= byte_size(B) -> + fold_xor(binary_to_list(A), binary_to_list(B), 0) =:= 0; +constant_eq(_, _) -> + false. + +fold_xor([], [], Acc) -> Acc; +fold_xor([X | Xs], [Y | Ys], Acc) -> fold_xor(Xs, Ys, Acc bor (X bxor Y)). diff --git a/apps/erlangbridge/src/mcp/mcp_sup.erl b/apps/erlangbridge/src/mcp/mcp_sup.erl new file mode 100644 index 00000000..7f469988 --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_sup.erl @@ -0,0 +1,129 @@ +%% Dedicated supervisor of the embedded MCP inspector, plus the session API +%% used by the debugger bridge (vscode_connection). +%% +%% The subtree is started only on demand for a debug session and is completely +%% separate from the LSP supervision tree. It fails closed: any child exit +%% (listener failure, lease expiry, crash) shuts the whole subtree down; it is +%% never restarted with a different port or token. +%% +%% A `mcp_holder` process owns the supervisor link so that the subtree outlives +%% the (rpc) process that requested it. +-module(mcp_sup). +-behaviour(supervisor). + +-export([start_session/2, start_session/3, start_session/4, stop_session/0, renew/0, active/0]). +-export([init/1]). +%% spawned +-export([holder/6]). + +-define(START_TIMEOUT_MS, 5000). + +%% Config: normalized configuration (already validated by the caller and again here). +%% Mode: launch | attach +%% -> {ok, #{host, port, path, sessionId, token}} | {error, Reason :: binary()} +start_session(Config, Mode) -> + start_session(Config, Mode, undefined). + +%% FixedToken: undefined (random per session) or a validated developer token. +start_session(Config, Mode, FixedToken) -> + start_session(Config, Mode, FixedToken, #{}). + +%% Opts: #{notify => fun((map()) -> any())} - receives one non-secret metadata +%% map per MCP request (method, tool, status, duration, sizes); never arguments, +%% results or the token. +start_session(Config, Mode, FixedToken, Opts) -> + case mcp_policy:validate_config(Config) of + {error, Msg} -> {error, list_to_binary(Msg)}; + ok -> + case whereis(mcp_holder) of + undefined -> + Token = case FixedToken of undefined -> new_token(); _ -> FixedToken end, + Ref = make_ref(), + Caller = self(), + Holder = spawn(?MODULE, holder, [Caller, Ref, Config, Mode, Token, Opts]), + receive + {Ref, {ok, Info}} -> {ok, Info#{token => Token}}; + {Ref, {error, Reason}} -> {error, describe(Reason)} + after ?START_TIMEOUT_MS -> + exit(Holder, kill), + {error, <<"the MCP inspector did not start in time">>} + end; + _ -> + {error, <<"an MCP inspector is already running on this node">>} + end + end. + +stop_session() -> + case whereis(mcp_holder) of + undefined -> ok; + Pid -> + Ref = erlang:monitor(process, Pid), + Pid ! {stop, self()}, + receive {'DOWN', Ref, process, Pid, _} -> ok + after 3000 -> + exit(Pid, kill), + erlang:demonitor(Ref, [flush]), + ok + end + end. + +renew() -> + case whereis(mcp_server) of + undefined -> {error, not_running}; + _ -> mcp_server:renew(), ok + end. + +active() -> whereis(mcp_holder) =/= undefined. + +%%------------------------------------------------------------------------------ + +new_token() -> + B = base64:encode(crypto:strong_rand_bytes(32)), + << <<(url_safe(C))>> || <> <= B, C =/= $= >>. + +url_safe($+) -> $-; +url_safe($/) -> $_; +url_safe(C) -> C. + +describe({shutdown, {failed_to_start_child, mcp_server, {listen_failed, eaddrinuse}}}) -> + <<"the configured erlang.mcp.port is already in use; MCP is disabled for this session">>; +describe({shutdown, {failed_to_start_child, mcp_server, {listen_failed, Reason}}}) -> + iolist_to_binary(io_lib:format("the MCP listener cannot bind (~w)", [Reason])); +describe(_) -> + <<"the MCP inspector could not start">>. + +holder(Caller, Ref, Config, Mode, Token, Opts) -> + try register(mcp_holder, self()) of + true -> holder_start(Caller, Ref, Config, Mode, Token, Opts) + catch error:badarg -> + Caller ! {Ref, {error, already_started}} + end. + +holder_start(Caller, Ref, Config, Mode, Token, Opts) -> + process_flag(trap_exit, true), + case supervisor:start_link({local, ?MODULE}, ?MODULE, {Config, Mode, Token, maps:get(notify, Opts, undefined)}) of + {ok, Sup} -> + Info = mcp_server:info(), + Caller ! {Ref, {ok, Info#{sessionId => mcp_store:session_id()}}}, + holder_loop(Sup); + {error, Reason} -> + Caller ! {Ref, {error, Reason}} + end. + +holder_loop(Sup) -> + receive + {stop, _From} -> + exit(Sup, shutdown), + receive {'EXIT', Sup, _} -> ok after 3000 -> ok end; + {'EXIT', Sup, Reason} -> + mcp_audit:event(supervisor_exit, #{reason => Reason}); + _ -> + holder_loop(Sup) + end. + +init({Config, Mode, Token, Notify}) -> + Children = [#{id => mcp_store, start => {mcp_store, start_link, [Config, Mode, Notify]}, + restart => permanent, shutdown => 2000, type => worker, modules => [mcp_store]}, + #{id => mcp_server, start => {mcp_server, start_link, [Config, Token]}, + restart => permanent, shutdown => 2000, type => worker, modules => [mcp_server]}], + {ok, {#{strategy => one_for_all, intensity => 0, period => 1}, Children}}. diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl new file mode 100644 index 00000000..0308a4a0 --- /dev/null +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -0,0 +1,231 @@ +%% The seven read-only tools: descriptions, strict input schemas, output +%% schemas and the argument validator. Unknown arguments are rejected. +-module(mcp_tools). + +-export([list/1, known/1, validate_args/2, structured_error_schema/0]). + +-define(SCHEMA_VERSION, <<"1.0">>). +-export([schema_version/0]). +schema_version() -> ?SCHEMA_VERSION. + +%% tools/list result entries for the allowed tools. +list(Config) -> + [tool_def(Name) || Name <- mcp_policy:all_tools(), mcp_policy:tool_allowed(Name, Config)]. + +known(Name) -> lists:member(Name, mcp_policy:all_tools()). + +tool_def(Name) -> + {Title, Desc, In, Out} = def(Name), + #{<<"name">> => Name, + <<"title">> => Title, + <<"description">> => Desc, + <<"inputSchema">> => In, + <<"outputSchema">> => Out, + <<"annotations">> => #{<<"readOnlyHint">> => true, + <<"destructiveHint">> => false, + <<"idempotentHint">> => true, + <<"openWorldHint">> => false}}. + +%%------------------------------------------------------------------------------ +%% Definitions +%%------------------------------------------------------------------------------ + +def(<<"runtime_summary">>) -> + {<<"Runtime summary">>, + <<"Bounded metadata of the debugged Erlang node: OTP/ERTS versions, node name (host redacted by default), " + "uptime, scheduler and process counts, memory summary and the opaque debug session id. " + "Start here, then call application_overview.">>, + obj(#{<<"redactNodeHost">> => bool(<<"Replace the host part of the node name by . Default true.">>)}, []), + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), + <<"otpRelease">> => str(), <<"ertsVersion">> => str(), <<"node">> => str(), + <<"uptimeMs">> => int(), <<"schedulers">> => int(), <<"processCount">> => int(), + <<"memory">> => #{<<"type">> => <<"object">>}}, + [<<"schemaVersion">>, <<"sessionId">>])}; + +def(<<"application_overview">>) -> + {<<"Application overview">>, + <<"Discover the started OTP applications of the node without knowing any process name: names, versions, " + "declared dependencies (declarations, not evidence of runtime communication) and discoverable root " + "supervisors with reusable entity ids. Returns a paginated map (entities, typed relationships, coverage " + "limitations). Never calls application callbacks, loads code or reads application environment.">>, + obj(#{<<"application">> => id_prop(<<"Only this application (entity id from a previous call).">>), + <<"includeModules">> => bool(<<"Also list module entities (with declared behaviours when the module is already loaded). Default false.">>), + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + graph_output()}; + +def(<<"supervision_tree">>) -> + {<<"Supervision tree">>, + <<"Traverse supervisors from an application id or process (supervisor) id discovered by application_overview, " + "or from an explicitly given local supervisor (registered name or pid). Uses supervisor APIs with deadlines: " + "an unresponsive supervisor yields a partial result with a 'timeout' omission. Emits typed 'supervises' edges, " + "child kind, module and restart/shutdown metadata; never child start arguments. Never resumes or alters processes.">>, + obj(#{<<"id">> => id_prop(<<"Application or process entity id.">>), + <<"supervisor">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 255, + <<"description">> => <<"Registered name or local pid text of a supervisor.">>}, + <<"maxDepth">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 16}, + <<"includeModules">> => bool(<<"Add module entities and uses_module edges. Default false.">>), + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + graph_output()}; + +def(<<"registered_processes">>) -> + {<<"Registered processes">>, + <<"Bounded list of locally registered process names with safe summary metadata. Membership of an application is " + "reported as confirmed (found in its supervision tree), inferred (group leader) or unknown; unrelated names are " + "never silently assigned to the selected application. Does not enumerate unregistered processes.">>, + obj(#{<<"application">> => id_prop(<<"Only processes attributed to this application.">>), + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + graph_output()}; + +def(<<"process_info">>) -> + {<<"Process info">>, + <<"Allowlisted fields of one local process (by entity id, registered name or pid): registered name, status, " + "current function, initial call, reductions, memory (words), message queue length, links and monitors as " + "bounded identifiers, application membership with evidence. No dictionary, stack, mailbox or state.">>, + obj(#{<<"id">> => id_prop(<<"Process entity id.">>), + <<"name">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 255, + <<"description">> => <<"Registered name (must be an existing atom).">>}, + <<"pid">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 64, + <<"description">> => <<"Local pid text such as <0.123.0>.">>}, + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + graph_output()}; + +def(<<"ets_tables">>) -> + {<<"ETS tables">>, + <<"Metadata of the named ETS tables explicitly approved by the project policy (allowed_ets_tables): name, " + "protection, type, owner entity id, size and memory with units. Private tables are excluded. Never reads keys, " + "objects or values.">>, + obj(#{<<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + graph_output()}; + +def(<<"debug_session">>) -> + {<<"Debug session">>, + <<"Safe metadata of the current debug session: launch or attach, node connection, interpreted modules and " + "active breakpoint locations (module and line). No source, environment or credentials.">>, + obj(#{}, []), + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), + <<"mode">> => str(), <<"nodeConnected">> => #{<<"type">> => <<"boolean">>}, + <<"interpretedModules">> => #{<<"type">> => <<"array">>}, + <<"breakpoints">> => #{<<"type">> => <<"array">>}}, + [<<"schemaVersion">>, <<"sessionId">>])}. + +structured_error_schema() -> + obj(#{<<"error">> => obj(#{<<"code">> => str(), <<"message">> => str()}, [<<"code">>])}, [<<"error">>]). + +graph_output() -> + Arr = #{<<"type">> => <<"array">>}, + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"collectionId">> => str(), + <<"startedAt">> => str(), <<"finishedAt">> => str(), + <<"scope">> => #{<<"type">> => <<"object">>}, + <<"entities">> => Arr, <<"relationships">> => Arr, + <<"complete">> => #{<<"type">> => <<"boolean">>}, + <<"truncated">> => #{<<"type">> => <<"boolean">>}, + <<"omissions">> => Arr, + <<"nextCursor">> => #{<<"type">> => [<<"string">>, <<"null">>]}}, + [<<"schemaVersion">>, <<"sessionId">>, <<"collectionId">>, <<"startedAt">>, <<"finishedAt">>, + <<"scope">>, <<"entities">>, <<"relationships">>, <<"complete">>, <<"truncated">>, + <<"omissions">>, <<"nextCursor">>]). + +obj(Props, Required) -> + #{<<"type">> => <<"object">>, <<"properties">> => Props, + <<"required">> => Required, <<"additionalProperties">> => false}. + +str() -> #{<<"type">> => <<"string">>}. +int() -> #{<<"type">> => <<"integer">>}. +bool(Desc) -> #{<<"type">> => <<"boolean">>, <<"description">> => Desc}. +id_prop(Desc) -> #{<<"type">> => <<"string">>, <<"maxLength">> => 64, <<"description">> => Desc}. +detail_prop() -> + #{<<"type">> => <<"string">>, <<"enum">> => [<<"summary">>, <<"full">>], + <<"description">> => <<"summary (default): compact entities/edges, 100 items per page - best for reasoning. " + "full: every field (pid, descriptions, edge observedAt...) and the largest pages - " + "use it only to export the complete JSON (e.g. to write it to a file).">>}. + +page_size_prop() -> + #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 500, + <<"description">> => <<"Items (entities + relationships) per page; capped by the project policy.">>}. + +cursor_prop() -> + #{<<"type">> => <<"string">>, <<"maxLength">> => 200, + <<"description">> => <<"nextCursor of a previous call with the same arguments.">>}. + +%%------------------------------------------------------------------------------ +%% Validation of arguments against the input schema (strict subset of JSON schema) +%%------------------------------------------------------------------------------ + +%% -> ok | {error, Message} +validate_args(Tool, Args) -> + case known(Tool) of + false -> {error, <<"unknown tool">>}; + true -> + {_, _, Schema, _} = def(Tool), + case is_map(Args) of + false -> {error, <<"arguments must be an object">>}; + true -> + case validate_object(Schema, Args) of + ok -> exclusive(Tool, Args); + {error, _} = E -> E + end + end + end. + +exclusive(<<"process_info">>, Args) -> + case length([K || K <- [<<"id">>, <<"name">>, <<"pid">>], maps:is_key(K, Args)]) of + 1 -> ok; + _ -> {error, <<"exactly one of id, name or pid is required">>} + end; +exclusive(<<"supervision_tree">>, Args) -> + case maps:is_key(<<"cursor">>, Args) of + true -> ok; + false -> + case length([K || K <- [<<"id">>, <<"supervisor">>], maps:is_key(K, Args)]) of + 1 -> ok; + _ -> {error, <<"exactly one of id or supervisor is required">>} + end + end; +exclusive(_, _) -> + ok. + +validate_object(#{<<"properties">> := Props, <<"required">> := Required}, Args) -> + Unknown = [K || K <- maps:keys(Args), not maps:is_key(K, Props)], + Missing = [K || K <- Required, not maps:is_key(K, Args)], + case {Unknown, Missing} of + {[_ | _], _} -> {error, <<"unexpected argument">>}; + {_, [_ | _]} -> {error, <<"missing required argument">>}; + _ -> validate_props(maps:to_list(Args), Props) + end. + +validate_props([], _) -> ok; +validate_props([{K, V} | T], Props) -> + case validate_value(maps:get(K, Props), V) of + ok -> validate_props(T, Props); + {error, Msg} -> {error, <<"invalid argument '", K/binary, "': ", Msg/binary>>} + end. + +validate_value(#{<<"type">> := <<"string">>, <<"enum">> := Enum}, V) when is_binary(V) -> + case lists:member(V, Enum) of + true -> ok; + false -> {error, <<"not an allowed value">>} + end; +validate_value(#{<<"type">> := <<"string">>} = S, V) when is_binary(V) -> + case unicode:characters_to_binary(V, utf8, utf8) of + B when is_binary(B) -> + case byte_size(V) > maps:get(<<"maxLength">>, S, 4096) of + true -> {error, <<"too long">>}; + false -> ok + end; + _ -> {error, <<"not valid UTF-8">>} + end; +validate_value(#{<<"type">> := <<"boolean">>}, V) when is_boolean(V) -> ok; +validate_value(#{<<"type">> := <<"integer">>} = S, V) when is_integer(V) -> + Min = maps:get(<<"minimum">>, S, V), + Max = maps:get(<<"maximum">>, S, V), + case V >= Min andalso V =< Max of + true -> ok; + false -> {error, <<"out of range">>} + end; +validate_value(#{<<"type">> := T}, _) -> + {error, <<"expected ", T/binary>>}. diff --git a/apps/erlangbridge/src/vscode_connection.erl b/apps/erlangbridge/src/vscode_connection.erl index 476e7dad..c506bf84 100644 --- a/apps/erlangbridge/src/vscode_connection.erl +++ b/apps/erlangbridge/src/vscode_connection.erl @@ -77,6 +77,10 @@ configure(no_compiled_args_file) -> %%------------------------------------------------------------------------------ -define(ATTACH_MODULES, [vscode_jsone, gen_connection, vscode_connection]). +%% Pushed to the target only when the embedded MCP inspector is enabled for +%% this debug session (the helper is then started with -vscode_mcp). +-define(MCP_MODULES, [vscode_jsone_decode, mcp_policy, mcp_encoder, mcp_audit, mcp_store, + mcp_tools, mcp_runtime, mcp_server, mcp_sup]). attach() -> {ok, [[NodeString]]} = init:get_argument(vscode_attach_node), @@ -97,7 +101,7 @@ attach(Node, Port, ArgsModule) -> true -> case missing_apps(Node) of [] -> - lists:foreach(fun(M) -> push_module(Node, M) end, ?ATTACH_MODULES), + lists:foreach(fun(M) -> push_module(Node, M) end, ?ATTACH_MODULES ++ mcp_modules(Node)), case rpc:call(Node, ?MODULE, start_attached, [Port, ArgsModule], infinity) of ok -> ok; Other -> {error, io_lib:format("~p", [Other])} @@ -111,9 +115,26 @@ attach(Node, Port, ArgsModule) -> "is running on this machine, and the cookie"} end. -%% int (debugger app) runs the interpreter, httpc (inets app) posts the events. +%% int (debugger app) runs the interpreter, httpc (inets app) posts the events; +%% the MCP inspector also needs crypto (token, cursor signatures). missing_apps(Node) -> - [M || M <- [int, httpc], rpc:call(Node, code, which, [M]) =:= non_existing]. + Needed = case mcp_requested() of + true -> [int, httpc, crypto]; + false -> [int, httpc] + end, + [M || M <- Needed, rpc:call(Node, code, which, [M]) =:= non_existing]. + +mcp_requested() -> + case init:get_argument(vscode_mcp) of + {ok, _} -> true; + _ -> false + end. + +mcp_modules(_Node) -> + case mcp_requested() of + true -> ?MCP_MODULES; + false -> [] + end. push_module(Node, Module) -> {Module, Binary, File} = code:get_object_code(Module), @@ -160,6 +181,7 @@ detach() -> [int:continue(Pid) || {Pid, _, break, _} <- int:snapshot()], [int:nn(M) || M <- int:interpreted()], int:clear(), + stop_mcp(), stop_attached(), application:unset_env(vscode_debugger, port), ok. @@ -255,6 +277,13 @@ decode_request(Data) -> {error,{_,erl_parse, Messages}} -> debugger_eval_error(lists:flatten(Messages)) end; + {mcp_start, Body} -> + mcp_start(Body); + {mcp_renew, _Body} -> + mcp_renew(); + {mcp_stop, _Body} -> + stop_mcp(), + #{ok => true}; {debugger_exit, _Body} -> init:stop(0); {debugger_detach, _Body} -> @@ -265,6 +294,61 @@ decode_request(Data) -> unknown_command end. +%%------------------------------------------------------------------------------ +%% Embedded MCP inspector (debug sessions only, enabled by the adapter) +%% +%% The adapter posts the normalized, non-secret configuration; the token is +%% generated here and returned only to the adapter that started the session. +%% It never appears in a debug event, an output event or a log. +%%------------------------------------------------------------------------------ + +mcp_start(Body) -> + case code:which(mcp_sup) of + non_existing -> mcp_error(<<"the MCP inspector is not available in the debugged node">>); + _ -> + try vscode_jsone_decode:decode(unicode:characters_to_binary(Body)) of + {ok, Map, _} when is_map(Map) -> + case mcp_policy:from_json_map(Map) of + {ok, Config} -> + Fixed = maps:get(<<"authToken">>, Map, undefined), + Notify = fun(Meta) -> gen_connection:send_message_to_vscode(port_int(), "mcp_call", Meta) end, + case mcp_sup:start_session(Config, mcp_mode(Map), Fixed, #{notify => Notify}) of + {ok, #{host := Host, port := Port, path := Path, sessionId := Sid, token := Token}} -> + #{ok => true, host => list_to_binary(Host), port => Port, + path => Path, sessionId => Sid, token => Token}; + {error, Msg} -> mcp_error(Msg) + end; + {error, Msg} -> mcp_error(unicode:characters_to_binary(Msg)) + end; + _ -> mcp_error(<<"invalid MCP configuration">>) + catch _:_ -> mcp_error(<<"invalid MCP configuration">>) + end + end. + +%% get_port/0 returns the -vscode_port argument (a string) or, in attach mode, the stored port. +port_int() -> + case get_port() of + P when is_integer(P) -> P; + P when is_list(P) -> list_to_integer(P) + end. + +mcp_mode(#{<<"mode">> := <<"attach">>}) -> attach; +mcp_mode(_) -> launch. + +mcp_error(Msg) -> #{ok => false, error => Msg}. + +mcp_renew() -> + case code:which(mcp_sup) of + non_existing -> #{ok => false}; + _ -> case mcp_sup:renew() of ok -> #{ok => true}; _ -> #{ok => false} end + end. + +stop_mcp() -> + case code:is_loaded(mcp_sup) of + false -> ok; + _ -> catch mcp_sup:stop_session(), ok + end. + debugger_eval_error(Message) -> #{value => iolist_to_binary(Message), type => <<"error">>}. diff --git a/apps/erlangbridge/test/mcp/mcp_bridge_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_bridge_SUITE.erl new file mode 100644 index 00000000..0cd30640 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_bridge_SUITE.erl @@ -0,0 +1,282 @@ +-module(mcp_bridge_SUITE). + +%% Debugger-bridge integration of the MCP inspector (T12): the inspector is +%% pushed to and started inside the *attached target node* only when the +%% adapter asks for it, reports the target's own state, is stopped by a detach, +%% and never touches the node running the helper (LSP/adapter side). +%% +%% Same shape as vscode_connection_SUITE: the test node plays the debug adapter +%% (event receiver + commands), a peer is the target, a second peer (started +%% with -vscode_mcp) or the test node itself is the attach helper. + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +all() -> + [inspector_is_pushed_started_and_stopped_with_the_attach, + without_the_mcp_flag_nothing_is_pushed_or_started, + start_is_refused_for_an_invalid_configuration, + fixed_development_token_is_used_when_given]. + +init_per_suite(Config) -> + case ensure_distributed() of + ok -> Config; + {error, Reason} -> {skip, {no_distribution, Reason}} + end. + +end_per_suite(_Config) -> ok. + +init_per_testcase(_Case, Config) -> + {ok, Peer, Target} = peer:start_link(#{name => peer:random_name(?MODULE), connection => standard_io}), + Src = copy_fixture(Config), + {ok, mcp_attach_target} = compile:file(Src, [debug_info, {outdir, filename:dirname(Src)}]), + true = peer:call(Peer, code, add_patha, [filename:dirname(Src)]), + {Receiver, Port} = start_event_receiver(), + [{peer, Peer}, {target, Target}, {src, Src}, {receiver, Receiver}, {port, Port} | Config]. + +end_per_testcase(_Case, Config) -> + exit(?config(receiver, Config), kill), + catch peer:stop(?config(helper, Config)), + peer:stop(?config(peer, Config)). + +%% Helper started like the adapter does for an MCP-enabled attach: -vscode_mcp 1. +start_helper(Config) -> + Paths = lists:append([["-pa", P] || P <- code:get_path()]), + {ok, Helper, _} = peer:start_link(#{name => peer:random_name("mcp_helper"), connection => standard_io, + args => ["-vscode_mcp", "1"] ++ Paths}), + [{helper, Helper} | Config]. + +%%%%%%%%%%% +%% cases %% +%%%%%%%%%%% + +inspector_is_pushed_started_and_stopped_with_the_attach(Config0) -> + Config = start_helper(Config0), + Target = ?config(target, Config), + Helper = ?config(helper, Config), + ?assertEqual(ok, peer:call(Helper, vscode_connection, attach, + [Target, port_arg(Config), args_module(Config)], 60000)), + CmdPort = wait_listen(), + %% pushed to the target, because -vscode_mcp was requested + ?assertNotEqual(non_existing, rpc:call(Target, code, which, [mcp_sup])), + ?assertNotEqual(non_existing, rpc:call(Target, code, which, [mcp_server])), + %% nothing runs until the adapter asks + ?assertEqual(false, rpc:call(Target, mcp_sup, active, [])), + Start = decode(post_command(CmdPort, "mcp_start", start_body(#{}))), + ?assertMatch(#{<<"ok">> := true, <<"host">> := <<"127.0.0.1">>, <<"path">> := <<"/mcp">>, + <<"port">> := P, <<"token">> := T, <<"sessionId">> := _} when is_integer(P) andalso P > 0 andalso is_binary(T), + Start), + #{<<"port">> := McpPort, <<"token">> := Token} = Start, + ?assert(byte_size(Token) >= 43), + %% the inspector lives in the target, not in the adapter/helper/test nodes + ?assertEqual(true, rpc:call(Target, mcp_sup, active, [])), + ?assertEqual(false, mcp_sup:active()), + ?assertEqual(undefined, whereis(mcp_server)), + ?assertEqual(false, peer:call(Helper, mcp_sup, active, [])), + %% it reports the target's own state (attach mode, interpreted module, breakpoint) + Summary = tool(McpPort, Token, <<"runtime_summary">>, #{<<"redactNodeHost">> => false}), + ?assertEqual(atom_to_binary(Target, utf8), maps:get(<<"node">>, Summary)), + Debug = tool(McpPort, Token, <<"debug_session">>, #{}), + ?assertEqual(<<"attach">>, maps:get(<<"mode">>, Debug)), + ?assertEqual([<<"mcp_attach_target">>], maps:get(<<"interpretedModules">>, Debug)), + ?assertEqual([#{<<"module">> => <<"mcp_attach_target">>, <<"line">> => 5}], maps:get(<<"breakpoints">>, Debug)), + %% no source, environment or credentials + Text = iolist_to_binary(io_lib:format("~p", [Debug])), + ?assertEqual(nomatch, binary:match(Text, list_to_binary(?config(src, Config)))), + ?assertEqual(nomatch, binary:match(Text, Token)), + %% the token was not sent through the debug event channel + ?assertEqual(nomatch, binary:match(iolist_to_binary(io_lib:format("~p", [events()])), Token)), + %% the request journal reaches the adapter as /mcp_call events, without the token + Journal = wait_event(<<"/mcp_call">>), + %% (journal entries are sent asynchronously: any of the two calls may come first) + ?assertMatch(#{<<"method">> := <<"tools/call">>, <<"status">> := <<"ok">>, <<"seq">> := S} when S >= 1, + decode(Journal)), + ?assertEqual(nomatch, binary:match(Journal, Token)), + %% lease renewal from the adapter + ?assertMatch(#{<<"ok">> := true}, decode(post_command(CmdPort, "mcp_renew", <<>>))), + %% a second start on the same node is refused + ?assertMatch(#{<<"ok">> := false, <<"error">> := _}, decode(post_command(CmdPort, "mcp_start", start_body(#{})))), + %% detach: the inspector is stopped, its endpoint closed, the target keeps running + ?assertEqual(<<"{}">>, post_command(CmdPort, "debugger_detach", <<>>)), + wait_until(fun() -> rpc:call(Target, mcp_sup, active, []) =:= false end), + ?assertMatch({error, econnrefused}, gen_tcp:connect({127, 0, 0, 1}, McpPort, [binary], 1000)), + ?assertEqual(3, rpc:call(Target, mcp_attach_target, add, [1, 2])), + ?assert(is_pid(rpc:call(Target, erlang, whereis, [init]))). + +without_the_mcp_flag_nothing_is_pushed_or_started(Config) -> + Target = ?config(target, Config), + ok = vscode_connection:attach(Target, port_arg(Config), args_module(Config)), + CmdPort = wait_listen(), + ?assertEqual(non_existing, rpc:call(Target, code, which, [mcp_sup])), + ?assertEqual(non_existing, rpc:call(Target, code, which, [mcp_policy])), + ?assertMatch(#{<<"ok">> := false, <<"error">> := <<"the MCP inspector is not available", _/binary>>}, + decode(post_command(CmdPort, "mcp_start", start_body(#{})))), + ?assertMatch(#{<<"ok">> := false}, decode(post_command(CmdPort, "mcp_renew", <<>>))), + ?assertEqual(<<"{}">>, post_command(CmdPort, "debugger_detach", <<>>)), + wait_until(fun() -> rpc:call(Target, erlang, whereis, [vscode_connection]) =:= undefined end). + +start_is_refused_for_an_invalid_configuration(Config0) -> + Config = start_helper(Config0), + Target = ?config(target, Config), + ok = peer:call(Helper = ?config(helper, Config), vscode_connection, attach, + [Target, port_arg(Config), args_module(Config)], 60000), + _ = Helper, + CmdPort = wait_listen(), + [begin + ?assertMatch(#{<<"ok">> := false, <<"error">> := _}, decode(post_command(CmdPort, "mcp_start", Body))), + ?assertEqual(false, rpc:call(Target, mcp_sup, active, [])) + end || Body <- [start_body(#{<<"host">> => <<"0.0.0.0">>}), + start_body(#{<<"port">> => 70000}), + start_body(#{<<"authToken">> => <<"x">>}), + start_body(#{<<"required">> => true}), + <<"not json">>, + <<"[]">>]], + ?assertEqual(<<"{}">>, post_command(CmdPort, "debugger_detach", <<>>)). + +fixed_development_token_is_used_when_given(Config0) -> + Config = start_helper(Config0), + Target = ?config(target, Config), + ok = peer:call(?config(helper, Config), vscode_connection, attach, + [Target, port_arg(Config), args_module(Config)], 60000), + CmdPort = wait_listen(), + Fixed = <<"dev-fixed-token-0123456789">>, + #{<<"ok">> := true, <<"port">> := Port, <<"token">> := Fixed} = + decode(post_command(CmdPort, "mcp_start", start_body(#{<<"authToken">> => Fixed}))), + ?assertMatch(#{<<"sessionId">> := _}, tool(Port, Fixed, <<"runtime_summary">>, #{})), + %% the fixed token is the only credential + {ok, S} = gen_tcp:connect({127, 0, 0, 1}, Port, [binary, {active, false}], 5000), + ok = gen_tcp:send(S, ["POST /mcp HTTP/1.1\r\nHost: 127.0.0.1:", integer_to_list(Port), + "\r\nAuthorization: Bearer wrong\r\nContent-Type: application/json\r\nContent-Length: 2\r\n\r\n{}"]), + ?assertMatch(<<"HTTP/1.1 401", _/binary>>, recv_all(S, <<>>)), + ?assertEqual(<<"{}">>, post_command(CmdPort, "debugger_detach", <<>>)). + +%%%%%%%%%%%%% +%% helpers %% +%%%%%%%%%%%%% + +start_body(Override) -> + Base = (mcp_policy:to_json_map(mcp_policy:defaults()))#{<<"mode">> => <<"attach">>}, + {ok, Json} = vscode_jsone:encode(maps:merge(Base, Override)), + iolist_to_binary(Json). + +decode(Bin) -> + {ok, T, _} = vscode_jsone_decode:decode(Bin), + T. + +%% MCP tool call over the real endpoint -> structuredContent +tool(Port, Token, Name, Args) -> + Req = #{<<"jsonrpc">> => <<"2.0">>, <<"id">> => 1, <<"method">> => <<"tools/call">>, + <<"params">> => #{<<"name">> => Name, <<"arguments">> => Args}}, + {ok, Json} = vscode_jsone:encode(Req), + Body = iolist_to_binary(Json), + {ok, S} = gen_tcp:connect({127, 0, 0, 1}, Port, [binary, {active, false}], 5000), + ok = gen_tcp:send(S, ["POST /mcp HTTP/1.1\r\nHost: 127.0.0.1:", integer_to_list(Port), + "\r\nAuthorization: Bearer ", Token, "\r\nContent-Type: application/json\r\n" + "Content-Length: ", integer_to_list(byte_size(Body)), "\r\n\r\n", Body]), + Raw = recv_all(S, <<>>), + [_, RespBody] = binary:split(Raw, <<"\r\n\r\n">>), + #{<<"result">> := #{<<"isError">> := false, <<"structuredContent">> := Structured}} = decode(RespBody), + Structured. + +events() -> + receive {event, P, B} -> [{P, B} | events()] after 0 -> [] end. + +ensure_distributed() -> + case node() of + nonode@nohost -> + _ = os:cmd("epmd -daemon"), + case net_kernel:start([list_to_atom("mcp_bridge_suite_" ++ os:getpid()), shortnames]) of + {ok, _} -> ok; + {error, Reason} -> {error, Reason} + end; + _ -> ok + end. + +copy_fixture(Config) -> + Dir = filename:join(?config(priv_dir, Config), "attach"), + ok = filelib:ensure_dir(filename:join(Dir, "x")), + Src = filename:join(Dir, "mcp_attach_target.erl"), + {ok, _} = file:copy(filename:join(?config(data_dir, Config), "mcp_attach_target.erl"), Src), + Src. + +port_arg(Config) -> integer_to_list(?config(port, Config)). + +args_module(Config) -> + Src = ?config(src, Config), + Forms = io_lib:format( + "-module(bp_attach_test).~n-export([configure/0]).~n" + "configure() -> int:start(), int:ni(~p), int:break(mcp_attach_target, 5), ok.~n", [Src]), + File = filename:join(filename:dirname(Src), "bp_attach_test.erl"), + ok = file:write_file(File, Forms), + {ok, bp_attach_test, Bin} = compile:file(File, [binary]), + {bp_attach_test, Bin}. + +wait_listen() -> + Body = wait_event(<<"/listen">>), + {match, [Port]} = re:run(Body, "\"port\":([0-9]+)", [{capture, all_but_first, list}]), + list_to_integer(Port). + +wait_event(Path) -> + receive + {event, Path, Body} -> Body + after 10000 -> ct:fail({no_event, Path}) + end. + +wait_until(Fun) -> wait_until(Fun, 50). +wait_until(_Fun, 0) -> ct:fail(condition_never_met); +wait_until(Fun, N) -> + case Fun() of + true -> ok; + false -> timer:sleep(100), wait_until(Fun, N - 1) + end. + +start_event_receiver() -> + Self = self(), + {ok, LSock} = gen_tcp:listen(0, [binary, {packet, http_bin}, {active, false}, + {ip, {127,0,0,1}}, {reuseaddr, true}]), + {ok, Port} = inet:port(LSock), + Pid = spawn(fun() -> accept_loop(LSock, Self) end), + ok = gen_tcp:controlling_process(LSock, Pid), + {Pid, Port}. + +accept_loop(LSock, Test) -> + {ok, Sock} = gen_tcp:accept(LSock), + spawn(fun() -> serve(Sock, Test) end), + accept_loop(LSock, Test). + +serve(Sock, Test) -> + {ok, {http_request, 'POST', {abs_path, Path}, _}} = gen_tcp:recv(Sock, 0, 5000), + Len = content_length(Sock, 0), + ok = inet:setopts(Sock, [{packet, raw}]), + Body = case Len of + 0 -> <<>>; + _ -> {ok, B} = gen_tcp:recv(Sock, Len, 5000), B + end, + Test ! {event, Path, Body}, + gen_tcp:send(Sock, <<"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok">>), + gen_tcp:close(Sock). + +content_length(Sock, Len) -> + case gen_tcp:recv(Sock, 0, 5000) of + {ok, {http_header, _, 'Content-Length', _, V}} -> content_length(Sock, binary_to_integer(V)); + {ok, {http_header, _, _, _, _}} -> content_length(Sock, Len); + {ok, http_eoh} -> Len + end. + +%% Command as the debug adapter sends it (erlangConnection.ts post/3), with a body. +post_command(CmdPort, Verb, Body) -> + {ok, Sock} = gen_tcp:connect({127,0,0,1}, CmdPort, [binary, {active, false}], 5000), + ok = gen_tcp:send(Sock, ["POST ", Verb, " HTTP/1.1\r\nContent-Type: plain/text\r\n" + "Content-Length: ", integer_to_list(byte_size(Body)), + "\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n", Body]), + Response = recv_all(Sock, <<>>), + [_Headers, RespBody] = binary:split(Response, <<"\r\n\r\n">>), + RespBody. + +recv_all(Sock, Acc) -> + case gen_tcp:recv(Sock, 0, 5000) of + {ok, Data} -> recv_all(Sock, <>); + {error, _} -> Acc + end. diff --git a/apps/erlangbridge/test/mcp/mcp_bridge_SUITE_data/mcp_attach_target.erl b/apps/erlangbridge/test/mcp/mcp_bridge_SUITE_data/mcp_attach_target.erl new file mode 100644 index 00000000..8bca4f3d --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_bridge_SUITE_data/mcp_attach_target.erl @@ -0,0 +1,5 @@ +-module(mcp_attach_target). +-export([add/2]). + +add(A, B) -> + A + B. diff --git a/apps/erlangbridge/test/mcp/mcp_capture_logger.erl b/apps/erlangbridge/test/mcp/mcp_capture_logger.erl new file mode 100644 index 00000000..b52b7beb --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_capture_logger.erl @@ -0,0 +1,8 @@ +%% Test-only logger handler: forwards every log event to the test process. +-module(mcp_capture_logger). + +-export([log/2]). + +log(Event, #{config := #{owner := Owner}}) -> + Owner ! {mcp_log, Event}, + ok. diff --git a/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl new file mode 100644 index 00000000..1d042f34 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl @@ -0,0 +1,117 @@ +-module(mcp_encoder_SUITE). + +%% Bounded, safe conversion of arbitrary terms (mcp_encoder). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +all() -> + [scalars_and_special_terms, + binaries_are_validated_and_bounded, + depth_and_item_limits, + maps_with_non_string_keys_and_secret_keys, + improper_lists_and_unicode, + never_creates_atoms_and_always_json_encodable, + text_is_bounded, + timestamps_and_hex]. + +limits() -> #{max_depth => 4, max_items => 5, max_binary_bytes => 16}. + +scalars_and_special_terms(_) -> + L = limits(), + ?assertEqual(42, mcp_encoder:term(42, L)), + ?assertEqual(1.5, mcp_encoder:term(1.5, L)), + ?assertEqual(true, mcp_encoder:term(true, L)), + ?assertEqual(null, mcp_encoder:term(undefined, L)), + ?assertEqual(<<"hello">>, mcp_encoder:term(hello, L)), + ?assertEqual([], mcp_encoder:term([], L)), + ?assertEqual(<<"<0.1.0>">>, mcp_encoder:term(list_to_pid("<0.1.0>"), L)), + ?assertMatch(#{<<"port">> := _}, mcp_encoder:term(hd(erlang:ports()), L)), + ?assertMatch(#{<<"ref">> := _}, mcp_encoder:term(make_ref(), L)), + %% functions are described, never a recoverable handle + Fun = mcp_encoder:term(fun lists:sort/1, L), + ?assertEqual(#{<<"fun">> => <<"lists">>}, Fun), + ?assertEqual(#{<<"fun">> => <<"mcp_encoder_SUITE">>}, mcp_encoder:term(fun() -> secret_closure end, L)), + ?assertEqual(nomatch, re:run(io_lib:format("~p", [Fun]), "sort")), + ?assertEqual(#{<<"tuple">> => [1, <<"a">>]}, mcp_encoder:term({1, a}, L)). + +binaries_are_validated_and_bounded(_) -> + L = limits(), + ?assertEqual(<<"short">>, mcp_encoder:term(<<"short">>, L)), + Long = binary:copy(<<"x">>, 100), + ?assertMatch(#{<<"binary">> := #{<<"size">> := 100, <<"truncated">> := true, <<"encoding">> := <<"base64">>}}, + mcp_encoder:term(Long, L)), + Bin = mcp_encoder:term(<<255, 254, 1, 2>>, L), + ?assertMatch(#{<<"binary">> := #{<<"size">> := 4, <<"truncated">> := false}}, Bin), + ?assertEqual(<<255, 254, 1, 2>>, base64:decode(maps:get(<<"data">>, maps:get(<<"binary">>, Bin)))), + %% base64 payload is bounded by max_binary_bytes, never the whole binary + Big = mcp_encoder:term(binary:copy(<<0>>, 1000000), L), + ?assert(byte_size(maps:get(<<"data">>, maps:get(<<"binary">>, Big))) =< 24), + %% safe_binary cuts on a character boundary + ?assertEqual(<<"aé"/utf8>>, mcp_encoder:safe_binary(<<"aéb"/utf8>>, 3)), + ?assertEqual(<<"a"/utf8>>, mcp_encoder:safe_binary(<<"aé"/utf8>>, 2)), + ?assertEqual(<<"">>, mcp_encoder:safe_binary(<<255>>, 10)). + +depth_and_item_limits(_) -> + L = limits(), + Deep = lists:foldl(fun(_, Acc) -> [Acc] end, x, lists:seq(1, 1000)), + Enc = mcp_encoder:term(Deep, L), + ?assertNotEqual(nomatch, re:run(io_lib:format("~p", [Enc]), "max depth")), + ?assert(length(lists:flatten(io_lib:format("~p", [Enc]))) < 400), + Many = mcp_encoder:term(lists:seq(1, 1000), L), + ?assertEqual([1, 2, 3, 4, 5, #{<<"truncated">> => true}], Many), + Tuple = mcp_encoder:term(list_to_tuple(lists:seq(1, 1000)), L), + ?assertEqual(6, length(maps:get(<<"tuple">>, Tuple))). + +maps_with_non_string_keys_and_secret_keys(_) -> + L = limits(), + M = mcp_encoder:term(#{1 => a, {t, 1} => b, <<"k">> => c, "str" => d}, L), + ?assertEqual(4, maps:get(<<"size">>, M)), + Keys = [maps:get(<<"key">>, E) || E <- maps:get(<<"map">>, M)], + ?assert(lists:member(1, Keys)), + ?assert(lists:member(#{<<"tuple">> => [<<"t">>, 1]}, Keys)), + %% likely secrets are redacted by key name + S = mcp_encoder:term(#{password => "SENTINEL_PW", <<"api_key">> => <<"SENTINEL_KEY">>, + auth_token => x, cookie => y, name => visible}, L), + Text = iolist_to_binary(io_lib:format("~p", [S])), + ?assertEqual(nomatch, binary:match(Text, <<"SENTINEL">>)), + ?assertNotEqual(nomatch, binary:match(Text, <<"visible">>)), + ?assertNotEqual(nomatch, binary:match(Text, <<"">>)), + Many = mcp_encoder:term(maps:from_list([{I, I} || I <- lists:seq(1, 100)]), L), + ?assertEqual(5, length(maps:get(<<"map">>, Many))), + ?assertEqual(100, maps:get(<<"size">>, Many)). + +improper_lists_and_unicode(_) -> + L = limits(), + ?assertEqual([1, 2, #{<<"improperTail">> => <<"tail">>}], mcp_encoder:term([1, 2 | tail], L)), + ?assertEqual(<<"héllo"/utf8>>, mcp_encoder:term("héllo", L#{max_binary_bytes => 64})), + ?assertEqual(<<"日本語"/utf8>>, mcp_encoder:term([26085, 26412, 35486], L#{max_binary_bytes => 64})), + %% invalid code points fall back to a bounded list instead of crashing + ?assert(is_list(mcp_encoder:term([1114112, 2], L)) orelse is_binary(mcp_encoder:term([1114112, 2], L))). + +never_creates_atoms_and_always_json_encodable(_) -> + L = limits(), + Terms = [self(), make_ref(), fun() -> ok end, {a, {b, {c, {d, {e, f}}}}}, #{a => #{b => #{c => #{d => #{e => f}}}}}, + [a | b], <<0, 255>>, "hi", 'weird atom', 1 bsl 200, -0.0, "", <<>>, {}, #{}], + Before = erlang:system_info(atom_count), + [begin + J = mcp_encoder:term(T, L), + {ok, Json} = vscode_jsone:encode(J), + ?assert(is_binary(iolist_to_binary(Json))) + end || T <- Terms], + [mcp_encoder:term(list_to_binary("unique_" ++ integer_to_list(I)), L) || I <- lists:seq(1, 500)], + ?assertEqual(Before, erlang:system_info(atom_count)). + +text_is_bounded(_) -> + L = limits(), + T = mcp_encoder:text(lists:seq(1, 100000), L), + ?assert(byte_size(T) =< 16), + ?assertEqual(<<"worker_a">>, mcp_encoder:text(worker_a, L#{max_binary_bytes => 64})), + ?assert(is_binary(mcp_encoder:text(make_ref(), L#{max_binary_bytes => 64}))). + +timestamps_and_hex(_) -> + ?assertEqual(<<"1970-01-01T00:00:00.000Z">>, mcp_encoder:iso8601(0)), + ?assertEqual(<<"2026-09-29T12:34:56.789Z">>, mcp_encoder:iso8601(1790685296789)), + ?assertEqual(<<"00ff10">>, mcp_encoder:hex(<<0, 255, 16>>)). diff --git a/apps/erlangbridge/test/mcp/mcp_fixture_sup.erl b/apps/erlangbridge/test/mcp/mcp_fixture_sup.erl new file mode 100644 index 00000000..75954800 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_fixture_sup.erl @@ -0,0 +1,67 @@ +%% Test-only OTP fixture for the MCP topology inspector. Never part of production. +%% +%% Expected topology (independent of inspector output): +%% +%% mcp_fx_root (supervisor, app root) +%% |- worker_a registered mcp_fx_worker_a, links to an unsupervised orphan +%% |- mcp_fx_sub_sup supervisor +%% | |- worker_b unregistered +%% | `- worker_c registered mcp_fx_worker_c, owns ETS mcp_fx_orders (public) +%% | and mcp_fx_private (private) +%% `- mcp_fx_dyn_sup supervisor (simple_one_for_one), N dynamic workers +%% +%% Forbidden data: every SENTINEL_* string below must never appear in a result. +-module(mcp_fixture_sup). +-behaviour(supervisor). +-behaviour(application). + +-export([start/2, stop/1, init/1]). +-export([start_fixture/0, stop_fixture/0, add_dynamic/1, sentinel/0]). + +sentinel() -> "SENTINEL". + +start(_Type, _Args) -> + supervisor:start_link({local, mcp_fx_root}, ?MODULE, root). + +stop(_State) -> ok. + +%% Load and start the fixture as a real OTP application. +start_fixture() -> + App = {application, mcp_fixture_app, + [{description, "mcp fixture"}, {vsn, "9.9.9"}, + {modules, [mcp_fixture_sup, mcp_fixture_worker]}, + {registered, [mcp_fx_root]}, + {applications, [kernel, stdlib]}, + {env, [{secret, "SENTINEL_ENV"}]}, + {mod, {mcp_fixture_sup, []}}]}, + ok = application:load(App), + ok = application:start(mcp_fixture_app, temporary), + ok. + +stop_fixture() -> + catch application:stop(mcp_fixture_app), + catch application:unload(mcp_fixture_app), + ok. + +add_dynamic(N) -> + [{ok, _} = supervisor:start_child(mcp_fx_dyn_sup, [{dynamic_worker, I}]) || I <- lists:seq(1, N)], + ok. + +init(root) -> + Children = [#{id => worker_a, start => {mcp_fixture_worker, start_link, [mcp_fx_worker_a, {"SENTINEL_START_ARG", orphan}]}, + restart => permanent, shutdown => 1000, type => worker, modules => [mcp_fixture_worker]}, + #{id => mcp_fx_sub_sup, start => {supervisor, start_link, [{local, mcp_fx_sub_sup}, ?MODULE, sub]}, + restart => permanent, shutdown => infinity, type => supervisor, modules => [?MODULE]}, + #{id => mcp_fx_dyn_sup, start => {supervisor, start_link, [{local, mcp_fx_dyn_sup}, ?MODULE, dyn]}, + restart => permanent, shutdown => infinity, type => supervisor, modules => [?MODULE]}], + {ok, {#{strategy => one_for_one, intensity => 10, period => 5}, Children}}; +init(sub) -> + Children = [#{id => worker_b, start => {mcp_fixture_worker, start_link, [undefined, {"SENTINEL_START_ARG", plain}]}, + restart => transient, shutdown => 2000, type => worker, modules => [mcp_fixture_worker]}, + #{id => worker_c, start => {mcp_fixture_worker, start_link, [mcp_fx_worker_c, {"SENTINEL_START_ARG", tables}]}, + restart => permanent, shutdown => 1000, type => worker, modules => [mcp_fixture_worker]}], + {ok, {#{strategy => one_for_one, intensity => 10, period => 5}, Children}}; +init(dyn) -> + Child = #{id => dyn, start => {mcp_fixture_worker, start_link, [undefined]}, + restart => temporary, shutdown => 1000, type => worker, modules => [mcp_fixture_worker]}, + {ok, {#{strategy => simple_one_for_one, intensity => 10, period => 5}, [Child]}}. diff --git a/apps/erlangbridge/test/mcp/mcp_fixture_worker.erl b/apps/erlangbridge/test/mcp/mcp_fixture_worker.erl new file mode 100644 index 00000000..ae867341 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_fixture_worker.erl @@ -0,0 +1,38 @@ +%% Test-only worker of the MCP fixture; holds forbidden sentinel data everywhere +%% the inspector must never look (state, dictionary, ETS objects, mailbox). +-module(mcp_fixture_worker). +-behaviour(gen_server). + +-export([start_link/1, start_link/2]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). + +start_link(Arg) -> start_link(undefined, Arg). + +start_link(undefined, Arg) -> + gen_server:start_link(?MODULE, Arg, []); +start_link(Name, Arg) -> + gen_server:start_link({local, Name}, ?MODULE, {Name, Arg}, []). + +init({mcp_fx_worker_a, _}) -> + put(secret, "SENTINEL_PDICT"), + %% a linked but unsupervised process (registered) + Orphan = spawn_link(fun() -> + register(mcp_fx_orphan, self()), + receive stop -> ok end + end), + {ok, #{secret => "SENTINEL_STATE", orphan => Orphan}}; +init({mcp_fx_worker_c, _}) -> + put(secret, "SENTINEL_PDICT"), + catch ets:delete(mcp_fx_orders), + ets:new(mcp_fx_orders, [named_table, public, set]), + ets:insert(mcp_fx_orders, {"SENTINEL_ETS_KEY", "SENTINEL_ETS_VALUE"}), + ets:new(mcp_fx_private, [named_table, private, set]), + ets:insert(mcp_fx_private, {"SENTINEL_PRIVATE_KEY", "SENTINEL_PRIVATE_VALUE"}), + {ok, #{secret => "SENTINEL_STATE"}}; +init(_) -> + put(secret, "SENTINEL_PDICT"), + {ok, #{secret => "SENTINEL_STATE"}}. + +handle_call(_, _, S) -> {reply, ok, S}. +handle_cast(_, S) -> {noreply, S}. +handle_info(_, S) -> {noreply, S}. diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl new file mode 100644 index 00000000..ed3086a4 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -0,0 +1,251 @@ +-module(mcp_policy_SUITE). + +%% Settings + rebar.config policy resolution (mcp_policy): defaults, validation, +%% loopback enforcement, project lookup boundaries, non-evaluation, ownership. + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +all() -> + [disabled_by_default_and_when_not_true, + untrusted_workspace_disables, + defaults_without_project_file, + loopback_hosts_accepted, + non_loopback_hosts_rejected, + port_range_validated, + project_policy_normalized, + explicit_empty_allowlist_enables_no_tool, + project_cannot_set_activation_or_binding, + project_limits_cannot_exceed_ceilings, + invalid_and_duplicate_project_terms, + profile_only_policy_reported, + malformed_project_file_disables, + script_is_never_executed, + umbrella_uses_enclosing_project_file, + lookup_never_leaves_the_workspace_folder, + oversized_project_file_rejected, + json_round_trip_and_authoritative_validation, + only_project_policy_is_returned, + fixed_token_is_validated]. + +init_per_testcase(Case, Config) -> + Dir = filename:join(?config(priv_dir, Config), atom_to_list(Case)), + ok = filelib:ensure_dir(filename:join(Dir, "x")), + [{root, Dir} | Config]. + +%%%%%%%%%%% +%% cases %% +%%%%%%%%%%% + +input(Config, Extra) -> + maps:merge(#{<<"enabled">> => true, <<"trusted">> => true, + <<"cwd">> => list_to_binary(?config(root, Config)), + <<"root">> => list_to_binary(?config(root, Config))}, Extra). + +write_rebar(Config, Text) -> + ok = file:write_file(filename:join(?config(root, Config), "rebar.config"), Text). + +disabled_by_default_and_when_not_true(Config) -> + ?assertEqual(disabled, mcp_policy:resolve(#{})), + ?assertEqual(disabled, mcp_policy:resolve(input(Config, #{<<"enabled">> => false}))), + ?assertEqual(disabled, mcp_policy:resolve(input(Config, #{<<"enabled">> => <<"true">>}))), + ?assertEqual(disabled, mcp_policy:resolve(input(Config, #{<<"enabled">> => 1}))). + +untrusted_workspace_disables(Config) -> + ?assertMatch({error, "MCP is disabled: the workspace is not trusted"}, + mcp_policy:resolve(input(Config, #{<<"trusted">> => false}))). + +defaults_without_project_file(Config) -> + {ok, C} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual("127.0.0.1", maps:get(host, C)), + ?assertEqual(0, maps:get(port, C)), + ?assertEqual(mcp_policy:all_tools(), maps:get(allowed_tools, C)), + ?assertEqual([], maps:get(allowed_ets_tables, C)), + ?assertEqual(mcp_policy:ceilings(), maps:get(limits, C)), + ?assertEqual(7, length(maps:get(allowed_tools, C))), + %% a project without an mcp term uses the same built-in policy + write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), + ?assertEqual({ok, C}, mcp_policy:resolve(input(Config, #{}))). + +loopback_hosts_accepted(Config) -> + [?assertMatch({ok, #{host := Expected}}, mcp_policy:resolve(input(Config, #{<<"host">> => Host}))) + || {Host, Expected} <- [{<<"127.0.0.1">>, "127.0.0.1"}, + {<<"127.0.0.2">>, "127.0.0.2"}, + {<<"::1">>, "::1"}]], + %% a name is resolved and every address must be loopback + ?assertMatch({ok, #{host := _}}, mcp_policy:resolve(input(Config, #{<<"host">> => <<"localhost">>}))). + +non_loopback_hosts_rejected(Config) -> + [?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{<<"host">> => Host}))) + || Host <- [<<"0.0.0.0">>, <<"::">>, <<"192.168.1.10">>, <<"10.0.0.1">>, <<"8.8.8.8">>, + <<"">>, <<"*">>, <<"[::]">>, 42, null]]. + +port_range_validated(Config) -> + [?assertMatch({ok, #{port := P}}, mcp_policy:resolve(input(Config, #{<<"port">> => P}))) + || P <- [0, 1, 65535]], + [?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{<<"port">> => P}))) + || P <- [-1, 65536, <<"80">>, 1.5, null]]. + +project_policy_normalized(Config) -> + write_rebar(Config, + "{erl_opts, [debug_info]}.\n" + "{mcp, [{allowed_tools, [<<\"runtime_summary\">>, <<\"ets_tables\">>]},\n" + " {allowed_ets_tables, [<<\"orders_index\">>]},\n" + " {limits, [{max_items, 10}, {request_timeout_ms, 500}]}]}.\n"), + {ok, C} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual([<<"runtime_summary">>, <<"ets_tables">>], maps:get(allowed_tools, C)), + ?assertEqual([<<"orders_index">>], maps:get(allowed_ets_tables, C)), + ?assertEqual(10, mcp_policy:limit(max_items, C)), + ?assertEqual(500, mcp_policy:limit(request_timeout_ms, C)), + %% untouched limits keep their defaults + ?assertEqual(16384, mcp_policy:limit(max_request_bytes, C)), + %% settings own activation/binding + ?assertMatch({ok, #{host := "127.0.0.1", port := 4711}}, + mcp_policy:resolve(input(Config, #{<<"port">> => 4711}))). + +explicit_empty_allowlist_enables_no_tool(Config) -> + write_rebar(Config, "{mcp, [{allowed_tools, []}]}.\n"), + {ok, C} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual([], maps:get(allowed_tools, C)), + [?assertNot(mcp_policy:tool_allowed(T, C)) || T <- mcp_policy:all_tools()], + ?assertEqual([], mcp_tools:list(C)). + +project_cannot_set_activation_or_binding(Config) -> + [begin + write_rebar(Config, lists:flatten(io_lib:format("{mcp, [~s]}.\n", [Term]))), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Term) + end || Term <- ["{enabled, true}", "{required, true}", "{host, <<\"0.0.0.0\">>}", "{port, 1234}", + "{transport, streamable_http}", "{auth_token, <<\"x\">>}", "{token, <<\"x\">>}", + "{credentials, []}", "{cookie, abc}"]]. + +project_limits_cannot_exceed_ceilings(Config) -> + [begin + write_rebar(Config, lists:flatten(io_lib:format("{mcp, [{limits, [~s]}]}.\n", [Term]))), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Term) + end || Term <- ["{max_items, 501}", "{max_items, 0}", "{max_items, -1}", "{max_items, 1.5}", + "{max_result_bytes, 262145}", "{request_timeout_ms, 3001}", "{max_concurrency, 3}", + "{unknown_limit, 1}", "{max_items, <<\"5\">>}", + %% unusable combinations: error envelopes must fit + "{max_result_bytes, 100}", "{max_request_bytes, 100}", + "{max_result_bytes, 8192}, {max_collection_bytes, 4096}"]], + write_rebar(Config, "{mcp, [{limits, [{max_items, 1}, {max_result_bytes, 4096}, {max_collection_bytes, 4096}, {max_binary_bytes, 1024}]}]}.\n"), + ?assertMatch({ok, _}, mcp_policy:resolve(input(Config, #{}))). + +invalid_and_duplicate_project_terms(Config) -> + [begin + write_rebar(Config, Text), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Text) + end || Text <- ["{mcp, [{allowed_tools, [<<\"eval\">>]}]}.\n", + "{mcp, [{allowed_tools, [<<\"runtime_summary\">>, <<\"runtime_summary\">>]}]}.\n", + "{mcp, [{allowed_tools, [runtime_summary]}]}.\n", + "{mcp, [{allowed_tools, []}, {allowed_tools, []}]}.\n", + "{mcp, [{allowed_ets_tables, [foo]}]}.\n", + "{mcp, [{allowed_ets_tables, [<<\"a\">>, <<\"a\">>]}]}.\n", + "{mcp, [{whatever, 1}]}.\n", + "{mcp, [{limits, [{max_items, 5}, {max_items, 6}]}]}.\n", + "{mcp, notalist}.\n", + "{mcp, [{allowed_tools, []}]}.\n{mcp, [{allowed_tools, []}]}.\n"]]. + +profile_only_policy_reported(Config) -> + write_rebar(Config, "{profiles, [{test, [{mcp, [{allowed_tools, []}]}]}]}.\n"), + {error, Msg} = mcp_policy:resolve(input(Config, #{})), + ?assertNotEqual(nomatch, string:find(Msg, "profile")), + %% a top-level term wins: the profile term is not merged + write_rebar(Config, "{mcp, []}.\n{profiles, [{test, [{mcp, [{allowed_tools, []}]}]}]}.\n"), + ?assertMatch({ok, #{allowed_tools := [_ | _]}}, mcp_policy:resolve(input(Config, #{}))). + +malformed_project_file_disables(Config) -> + [begin + write_rebar(Config, Text), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Text) + end || Text <- ["{mcp, [{allowed_tools, [}.\n", + "{mcp, [].\n", + "not a term at all\n", + "{mcp, [{limits, [{max_items, 1 + 1}]}]}.\n", + "{mcp, foo(bar)}.\n", + <<255, 254, 253>>]]. + +script_is_never_executed(Config) -> + Marker = filename:join(?config(root, Config), "script_ran"), + write_rebar(Config, "{mcp, [{allowed_tools, [<<\"runtime_summary\">>]}]}.\n"), + ok = file:write_file(filename:join(?config(root, Config), "rebar.config.script"), + io_lib:format("file:write_file(~p, <<\"ran\">>), CONFIG.~n", [Marker])), + {ok, C} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual([<<"runtime_summary">>], maps:get(allowed_tools, C)), + ?assertNot(filelib:is_file(Marker)), + %% the parser does not evaluate function calls placed in the file either + write_rebar(Config, io_lib:format("{mcp, [{allowed_tools, [list_to_binary(\"x\")]}]}.~n{x, file:write_file(~p, <<\"x\">>)}.~n", [Marker])), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{}))), + ?assertNot(filelib:is_file(Marker)). + +umbrella_uses_enclosing_project_file(Config) -> + Root = ?config(root, Config), + App = filename:join([Root, "apps", "myapp"]), + ok = filelib:ensure_dir(filename:join(App, "x")), + write_rebar(Config, "{mcp, [{allowed_tools, [<<\"debug_session\">>]}]}.\n"), + Input = (input(Config, #{}))#{<<"cwd">> => list_to_binary(App)}, + ?assertMatch({ok, #{allowed_tools := [<<"debug_session">>]}}, mcp_policy:resolve(Input)), + %% the application's own file is the nearest one + ok = file:write_file(filename:join(App, "rebar.config"), "{mcp, [{allowed_tools, [<<\"ets_tables\">>]}]}.\n"), + ?assertMatch({ok, #{allowed_tools := [<<"ets_tables">>]}}, mcp_policy:resolve(Input)). + +lookup_never_leaves_the_workspace_folder(Config) -> + Base = ?config(root, Config), + Root = filename:join(Base, "folder"), + Sibling = filename:join(Base, "folder2"), + Outside = Base, + [ok = filelib:ensure_dir(filename:join(D, "x")) || D <- [Root, Sibling]], + %% a permissive-looking file above the folder must not be consulted + ok = file:write_file(filename:join(Outside, "rebar.config"), "{mcp, [{allowed_tools, []}]}.\n"), + Input = #{<<"enabled">> => true, <<"trusted">> => true, + <<"cwd">> => list_to_binary(Root), <<"root">> => list_to_binary(Root)}, + ?assertMatch({ok, #{allowed_tools := [_, _ | _]}}, mcp_policy:resolve(Input)), + %% cwd outside the folder (also a string-prefix sibling) disables MCP + ?assertMatch({error, _}, mcp_policy:resolve(Input#{<<"cwd">> => list_to_binary(Sibling)})), + ?assertMatch({error, _}, mcp_policy:resolve(Input#{<<"cwd">> => list_to_binary(Outside)})), + ?assertMatch({error, _}, mcp_policy:resolve(Input#{<<"cwd">> => list_to_binary(filename:join(Root, "../folder2"))})), + ?assertMatch({error, _}, mcp_policy:resolve(Input#{<<"cwd">> => <<>>})). + +oversized_project_file_rejected(Config) -> + Big = lists:duplicate(300000, $\s), + write_rebar(Config, ["{mcp, []}.\n", Big]), + ?assertMatch({error, "rebar.config is too large" ++ _}, mcp_policy:resolve(input(Config, #{}))). + +json_round_trip_and_authoritative_validation(Config) -> + {ok, C} = mcp_policy:resolve(input(Config, #{<<"port">> => 5000})), + Json = mcp_policy:to_json_map(C), + {ok, Encoded} = vscode_jsone:encode(Json), + {ok, Decoded, _} = vscode_jsone_decode:decode(iolist_to_binary(Encoded)), + ?assertEqual({ok, C}, mcp_policy:from_json_map(Decoded)), + %% the target validates again: tampered configuration is refused + [?assertMatch({error, _}, mcp_policy:from_json_map(Bad)) + || Bad <- [Decoded#{<<"host">> => <<"0.0.0.0">>}, + Decoded#{<<"port">> => 70000}, + Decoded#{<<"authToken">> => <<"x">>}, + Decoded#{<<"required">> => true}, + Decoded#{<<"allowedTools">> => [<<"eval">>]}, + Decoded#{<<"limits">> => (maps:get(<<"limits">>, Decoded))#{<<"maxItems">> => 100000}}, + Decoded#{<<"limits">> => (maps:get(<<"limits">>, Decoded))#{<<"extra">> => 1}}, + maps:remove(<<"limits">>, Decoded), + #{}]], + ?assertMatch({error, _}, mcp_sup:start_session(C#{host => "0.0.0.0"}, launch)), + ?assertNot(mcp_sup:active()). + +only_project_policy_is_returned(Config) -> + %% other project terms never leak into the normalized policy + write_rebar(Config, "{deps, [{secret_dep, \"SENTINEL_DEP\"}]}.\n{mcp, []}.\n"), + {ok, C} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual(nomatch, re:run(io_lib:format("~p", [C]), "SENTINEL")), + ?assertEqual([allowed_ets_tables, allowed_tools, host, limits, port], lists:sort(maps:keys(C))). + +fixed_token_is_validated(Config) -> + {ok, C} = mcp_policy:resolve(input(Config, #{})), + Base = mcp_policy:to_json_map(C), + ?assertEqual(ok, mcp_policy:validate_token(<<"dev-token_0123456789.AZ~">>)), + [?assertMatch({error, _}, mcp_policy:validate_token(T)) + || T <- [<<"short">>, <<>>, binary:copy(<<"a">>, 129), <<"has space 0123456789">>, + <<"quote\"0123456789abc">>, <<"sl/ash0123456789abc">>, 42, undefined]], + ?assertEqual({ok, C}, mcp_policy:from_json_map(Base#{<<"authToken">> => <<"dev-token_0123456789">>})), + ?assertMatch({error, _}, mcp_policy:from_json_map(Base#{<<"authToken">> => <<"x">>})). diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl new file mode 100644 index 00000000..f8e332e6 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -0,0 +1,1011 @@ +-module(mcp_server_SUITE). + +%% End-to-end tests of the embedded MCP inspector over its real HTTP endpoint, +%% against a deterministic OTP fixture (mcp_fixture_sup) whose expected +%% topology is defined independently of the inspector output. +%% +%% The fixture contains SENTINEL_* strings in every place the inspector must +%% never read (process state/dictionary/mailbox, ETS objects, child start +%% arguments, application environment): no result may contain "SENTINEL". + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +all() -> + [unauthenticated_and_bad_tokens_rejected, + host_and_origin_validated, + method_content_type_and_size_enforced, + initialize_negotiates_and_lists_only_readonly_tools, + allowlist_enforced, + arguments_are_strict, + runtime_summary_redacts_node_host, + application_overview_discovers_fixture_without_names, + supervision_tree_matches_independent_expected_graph, + linked_unsupervised_process_is_not_supervised, + process_info_inputs_are_safe, + registered_processes_membership_evidence, + ets_tables_metadata_only_for_approved_tables, + restart_changes_identity, + pagination_cursors_and_retention, + collection_byte_budget_is_partial_and_non_resumable, + blocked_supervisor_yields_partial_result, + concurrency_is_bounded, + stop_closes_endpoint_and_invalidates_credentials, + fixed_port_conflict_disables_without_rebinding, + renewal_keeps_the_session_alive_and_lease_expiry_stops_it, + no_forbidden_data_no_atoms_no_stacktraces, + logs_never_contain_token_or_runtime_data, + lsp_application_never_starts_the_inspector, + journal_reports_request_metadata_only, + agent_summary_is_compact]. + +init_per_suite(Config) -> + ok = mcp_fixture_sup:start_fixture(), + Mailbox = spawn(fun() -> register(mcp_fx_mailbox, self()), receive never -> ok end end), + Mailbox ! "SENTINEL_MAILBOX", + [{mailbox, Mailbox} | Config]. + +end_per_suite(Config) -> + exit(?config(mailbox, Config), kill), + mcp_fixture_sup:stop_fixture(), + ok. + +init_per_testcase(lsp_application_never_starts_the_inspector, Config) -> Config; +init_per_testcase(fixed_port_conflict_disables_without_rebinding, Config) -> Config; +init_per_testcase(renewal_keeps_the_session_alive_and_lease_expiry_stops_it, Config) -> Config; +init_per_testcase(Case, Config) -> + Cfg = test_config(Case), + Self = self(), + Opts = case Case of + journal_reports_request_metadata_only -> #{notify => fun(M) -> Self ! {journal, M} end}; + _ -> #{} + end, + {ok, Info} = mcp_sup:start_session(Cfg, launch, undefined, Opts), + [{cfg, Cfg}, {info, Info}, {port, maps:get(port, Info)}, {token, maps:get(token, Info)} | Config]. + +end_per_testcase(_Case, _Config) -> + catch sys:resume(mcp_fx_sub_sup), + mcp_sup:stop_session(), + ok. + +test_config(Case) -> + Base = mcp_policy:defaults(), + L = maps:get(limits, Base), + Ets = [<<"mcp_fx_orders">>, <<"mcp_fx_private">>, <<"mcp_fx_missing_table_zzz">>], + Base1 = Base#{allowed_ets_tables => Ets}, + case Case of + allowlist_enforced -> + Base1#{allowed_tools => [<<"runtime_summary">>, <<"ets_tables">>]}; + pagination_cursors_and_retention -> + Base1#{limits => L#{max_items => 10, max_collections => 2, collection_ttl_ms => 1500}}; + collection_byte_budget_is_partial_and_non_resumable -> + Base1#{limits => L#{max_result_bytes => 4096, max_collection_bytes => 4096, max_binary_bytes => 512}}; + blocked_supervisor_yields_partial_result -> + Base1#{limits => L#{request_timeout_ms => 800}}; + concurrency_is_bounded -> + Base1#{limits => L#{request_timeout_ms => 1500, max_concurrency => 1}}; + _ -> Base1 + end. + +%%%%%%%%%%%%%%%%%%%% +%% HTTP client %% +%%%%%%%%%%%%%%%%%%%% + +%% -> {Code, HeadersMap, Body} +http(Port, Method, Headers, Body) -> + http(Port, Method, "/mcp", Headers, Body, undefined). + +http(Port, Method, Path, Headers, Body, ContentLength) -> + {ok, S} = gen_tcp:connect({127, 0, 0, 1}, Port, [binary, {active, false}]), + Len = case ContentLength of undefined -> byte_size(Body); N -> N end, + Lines = [[K, ": ", V, "\r\n"] || {K, V} <- Headers], + LenLine = case Len of skip -> []; _ -> ["Content-Length: ", integer_to_list(Len), "\r\n"] end, + ok = gen_tcp:send(S, [Method, " ", Path, " HTTP/1.1\r\n", Lines, LenLine, "\r\n", Body]), + Raw = recv_all(S, <<>>), + gen_tcp:close(S), + parse_response(Raw). + +recv_all(S, Acc) -> + case gen_tcp:recv(S, 0, 10000) of + {ok, D} -> recv_all(S, <>); + {error, _} -> Acc + end. + +parse_response(<<>>) -> {0, #{}, <<>>}; +parse_response(Raw) -> + [Head, Body] = case binary:split(Raw, <<"\r\n\r\n">>) of + [H, B] -> [H, B]; + [H] -> [H, <<>>] + end, + [Status | HLines] = binary:split(Head, <<"\r\n">>, [global]), + [_, Code | _] = binary:split(Status, <<" ">>, [global]), + Hs = maps:from_list([case binary:split(L, <<": ">>) of + [K, V] -> {string:lowercase(K), V}; + [K] -> {string:lowercase(K), <<>>} + end || L <- HLines]), + {binary_to_integer(Code), Hs, Body}. + +auth(Config) -> {"Authorization", ["Bearer ", ?config(token, Config)]}. + +std_headers(Config) -> + Port = ?config(port, Config), + [{"Host", ["127.0.0.1:", integer_to_list(Port)]}, auth(Config), + {"Content-Type", "application/json"}]. + +%% JSON-RPC over the real endpoint -> decoded response map +rpc(Config, Method, Params) -> + rpc(Config, Method, Params, 1). + +rpc(Config, Method, Params, Id) -> + Req = #{<<"jsonrpc">> => <<"2.0">>, <<"id">> => Id, <<"method">> => Method, <<"params">> => Params}, + {ok, Json} = vscode_jsone:encode(Req), + {200, _, Body} = http(?config(port, Config), "POST", std_headers(Config), iolist_to_binary(Json)), + decode(Body). + +decode(Bin) -> + {ok, T, <<>>} = vscode_jsone_decode:decode(Bin), + T. + +%% tools/call -> {ok, Structured} | {tool_error, Structured} | {rpc_error, Code, Message} +%% The contract tests check every field, so graph tools are called with detail=full +%% unless the case chooses the detail itself (see agent_summary_is_compact). +call(Config, Tool, Args0) when is_map(Args0) -> + Graph = [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, + <<"process_info">>, <<"ets_tables">>], + Args = case lists:member(Tool, Graph) andalso not maps:is_key(<<"detail">>, Args0) of + true -> Args0#{<<"detail">> => <<"full">>}; + false -> Args0 + end, + raw_call(Config, Tool, Args); +call(Config, Tool, Args) -> + raw_call(Config, Tool, Args). + +raw_call(Config, Tool, Args) -> + case rpc(Config, <<"tools/call">>, #{<<"name">> => Tool, <<"arguments">> => Args}) of + #{<<"result">> := #{<<"isError">> := false, <<"structuredContent">> := S, <<"content">> := [#{<<"text">> := Text}]}} -> + %% the text content is the same JSON as the structured content + ?assertEqual(S, decode(Text)), + {ok, S}; + #{<<"result">> := #{<<"isError">> := true, <<"structuredContent">> := S}} -> + {tool_error, S}; + #{<<"error">> := #{<<"code">> := C, <<"message">> := M}} -> + {rpc_error, C, M} + end. + +ok_call(Config, Tool, Args) -> + {ok, S} = call(Config, Tool, Args), + S. + +%% every page of a paginated tool call +all_pages(Config, Tool, Args) -> + S = ok_call(Config, Tool, Args), + collect_pages(Config, Tool, Args, S, [S]). + +collect_pages(Config, Tool, Args, #{<<"nextCursor">> := Cursor}, Acc) when is_binary(Cursor) -> + S = ok_call(Config, Tool, Args#{<<"cursor">> => Cursor}), + collect_pages(Config, Tool, Args, S, [S | Acc]); +collect_pages(_, _, _, _, Acc) -> + lists:reverse(Acc). + +entities(Pages) -> lists:append([maps:get(<<"entities">>, P) || P <- Pages]). +relationships(Pages) -> lists:append([maps:get(<<"relationships">>, P) || P <- Pages]). + +by_name(Name, Ents) -> + [E || #{<<"name">> := N} = E <- Ents, N =:= Name]. + +ent(Name, Ents) -> + [E] = by_name(Name, Ents), + E. + +label(#{<<"childId">> := C}) -> C; +label(#{<<"name">> := N}) when is_binary(N) -> N; +label(#{<<"pid">> := P}) -> P. + +id_of(#{<<"id">> := I}) -> I. + +fixture_app_id(Config) -> + S = ok_call(Config, <<"application_overview">>, #{}), + id_of(ent(<<"mcp_fixture_app">>, maps:get(<<"entities">>, S))). + +fixture_root_id(Config) -> + S = ok_call(Config, <<"application_overview">>, #{}), + [RootId] = maps:get(<<"roots">>, ent(<<"mcp_fixture_app">>, maps:get(<<"entities">>, S))), + RootId. + +%% {ParentLabel, ChildLabel} of every supervises edge +supervises_edges(Pages) -> + Ents = entities(Pages), + Labels = maps:from_list([{id_of(E), label(E)} || E <- Ents]), + lists:sort([{maps:get(F, Labels), maps:get(T, Labels)} + || #{<<"type">> := <<"supervises">>, <<"from">> := F, <<"to">> := T} <- relationships(Pages)]). + +%%%%%%%%%%% +%% cases %% +%%%%%%%%%%% + +unauthenticated_and_bad_tokens_rejected(Config) -> + Port = ?config(port, Config), + Tok = ?config(token, Config), + Body = <<"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}">>, + Base = [{"Host", ["127.0.0.1:", integer_to_list(Port)]}, {"Content-Type", "application/json"}], + {401, H, B} = http(Port, "POST", Base, Body), + ?assertEqual(<<"Bearer">>, maps:get(<<"www-authenticate">>, H)), + ?assertEqual(nomatch, binary:match(B, Tok)), + [?assertMatch({401, _, _}, http(Port, "POST", Base ++ [{"Authorization", V}], Body)) + || V <- ["Bearer wrong", ["Bearer ", Tok, "x"], ["Bearer ", binary:part(Tok, 0, byte_size(Tok) - 1)], + ["Basic ", Tok], Tok, "Bearer ", ["Bearer ", Tok]]], + %% the scheme is case-insensitive, the token is not + ?assertMatch({200, _, _}, http(Port, "POST", Base ++ [{"Authorization", ["bearer ", Tok]}], Body)), + ?assertMatch({401, _, _}, http(Port, "POST", Base ++ [{"Authorization", ["Bearer ", string:lowercase(Tok)]}], Body)), + %% duplicated Authorization header is refused + ?assertMatch({400, _, _}, http(Port, "POST", Base ++ [auth(Config), auth(Config)], Body)), + %% no request is processed before authentication: a body that is not JSON is 401, not 400 + ?assertMatch({401, _, _}, http(Port, "POST", Base, <<"not json">>)), + %% token entropy: at least 32 random bytes + ?assert(byte_size(Tok) >= 43). + +host_and_origin_validated(Config) -> + Port = ?config(port, Config), + Body = <<"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}">>, + Ok = [auth(Config), {"Content-Type", "application/json"}], + HostOk = {"Host", ["127.0.0.1:", integer_to_list(Port)]}, + ?assertMatch({200, _, _}, http(Port, "POST", [HostOk | Ok], Body)), + ?assertMatch({200, _, _}, http(Port, "POST", [{"Host", ["localhost:", integer_to_list(Port)]} | Ok], Body)), + [?assertMatch({403, _, _}, http(Port, "POST", [{"Host", H} | Ok], Body)) + || H <- ["evil.example.com", "evil.example.com:80", ["127.0.0.1:", integer_to_list(Port + 1)], "127.0.0.1", + ["0.0.0.0:", integer_to_list(Port)], ["[::1]:", integer_to_list(Port)]]], + ?assertMatch({403, _, _}, http(Port, "POST", Ok, Body)), + %% browser-origin attacks: any Origin that is not this loopback endpoint is refused + [?assertMatch({403, _, _}, http(Port, "POST", [HostOk, {"Origin", O} | Ok], Body)) + || O <- ["http://evil.example.com", "https://127.0.0.1", "null", "http://127.0.0.1:1", "file://"]], + ?assertMatch({200, _, _}, http(Port, "POST", [HostOk, {"Origin", ["http://127.0.0.1:", integer_to_list(Port)]} | Ok], Body)), + %% CORS is never enabled + {200, H, _} = http(Port, "POST", [HostOk | Ok], Body), + ?assertEqual([], [K || K <- maps:keys(H), binary:match(K, <<"access-control">>) =/= nomatch]). + +method_content_type_and_size_enforced(Config) -> + Port = ?config(port, Config), + Std = std_headers(Config), + Body = <<"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}">>, + [?assertMatch({405, _, _}, http(Port, M, Std, <<>>)) || M <- ["GET", "PUT", "DELETE", "PATCH", "OPTIONS"]], + ?assertMatch({404, _, _}, http(Port, "POST", "/other", Std, Body, undefined)), + ?assertMatch({404, _, _}, http(Port, "POST", "/mcp/", Std, Body, undefined)), + ?assertMatch({404, _, _}, http(Port, "POST", "/mcp?x=1", Std, Body, undefined)), + Bad = [{"Host", ["127.0.0.1:", integer_to_list(Port)]}, auth(Config)], + ?assertMatch({415, _, _}, http(Port, "POST", Bad ++ [{"Content-Type", "text/plain"}], Body)), + ?assertMatch({415, _, _}, http(Port, "POST", Bad, Body)), + ?assertMatch({200, _, _}, http(Port, "POST", Bad ++ [{"Content-Type", "application/json; charset=utf-8"}], Body)), + %% body cap is enforced before the body is read + ?assertMatch({413, _, _}, http(Port, "POST", "/mcp", Std, <<>>, 16385)), + ?assertMatch({413, _, _}, http(Port, "POST", "/mcp", Std, <<>>, 999999999)), + ?assertMatch({400, _, _}, http(Port, "POST", "/mcp", Std, <<>>, -1)), + ?assertMatch({411, _, _}, http(Port, "POST", "/mcp", Std ++ [{"Transfer-Encoding", "chunked"}], Body, skip)), + ?assertMatch({411, _, _}, http(Port, "POST", "/mcp", Std, Body, skip)), + %% malformed payloads + ?assertMatch({400, _, _}, http(Port, "POST", Std, <<"{not json">>)), + ?assertMatch({400, _, _}, http(Port, "POST", Std, <<"{\"jsonrpc\":\"2.0\"} trailing">>)), + {200, _, B1} = http(Port, "POST", Std, <<"[{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}]">>), + ?assertMatch(#{<<"error">> := #{<<"code">> := -32600}}, decode(B1)), + {200, _, B2} = http(Port, "POST", Std, <<"{\"jsonrpc\":\"1.0\",\"id\":1,\"method\":\"ping\"}">>), + ?assertMatch(#{<<"error">> := #{<<"code">> := -32600}}, decode(B2)), + %% a notification is accepted without a body + ?assertMatch({202, _, <<>>}, http(Port, "POST", Std, <<"{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}">>)), + %% slow / truncated body: the server does not hang and answers + ?assertMatch({400, _, _}, http(Port, "POST", "/mcp", Std, <<"{\"jsonrpc\"">>, 100)). + +initialize_negotiates_and_lists_only_readonly_tools(Config) -> + #{<<"result">> := Init} = rpc(Config, <<"initialize">>, #{<<"protocolVersion">> => <<"2025-06-18">>, + <<"capabilities">> => #{}, + <<"clientInfo">> => #{<<"name">> => <<"t">>, <<"version">> => <<"1">>}}), + ?assertEqual(<<"2025-06-18">>, maps:get(<<"protocolVersion">>, Init)), + ?assertEqual([<<"tools">>], maps:keys(maps:get(<<"capabilities">>, Init))), + %% an older client version is answered with the implemented revision + #{<<"result">> := Init2} = rpc(Config, <<"initialize">>, #{<<"protocolVersion">> => <<"2024-11-05">>}), + ?assertEqual(<<"2025-06-18">>, maps:get(<<"protocolVersion">>, Init2)), + ?assertMatch(#{<<"error">> := #{<<"code">> := -32602}}, rpc(Config, <<"initialize">>, #{})), + ?assertMatch(#{<<"result">> := #{}}, rpc(Config, <<"ping">>, #{})), + #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), + Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), + ?assertEqual(lists:sort(mcp_policy:all_tools()), Names), + ?assertEqual(7, length(Tools)), + [begin + ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, + <<"outputSchema">> := #{<<"type">> := <<"object">>}, + <<"description">> := _, + <<"annotations">> := #{<<"readOnlyHint">> := true, <<"destructiveHint">> := false}}, T), + ?assert(byte_size(maps:get(<<"description">>, T)) > 40) + end || T <- Tools], + %% nothing dangerous is advertised or callable, and no prompts/resources exist + [?assertNotEqual(nomatch, re:run(N, "^[a-z_]+$")) || N <- Names], + [?assertEqual(nomatch, re:run(N, "eval|exec|rpc|shell|lookup|trace|kill|load|write|send", [{capture, none}])) + || N <- Names], + [?assertMatch(#{<<"error">> := #{<<"code">> := -32601}}, rpc(Config, M, #{})) + || M <- [<<"resources/list">>, <<"prompts/list">>, <<"resources/read">>, <<"tools/eval">>, + <<"completion/complete">>, <<"logging/setLevel">>]], + [?assertMatch({rpc_error, -32602, _}, call(Config, T, #{})) + || T <- [<<"eval">>, <<"ets_lookup">>, <<"rpc_call">>, <<"sys_get_state">>, <<"shell">>, <<"trace">>, + <<"kill_process">>, <<"set_breakpoint">>, <<"load_code">>]]. + +allowlist_enforced(Config) -> + #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), + ?assertEqual([<<"ets_tables">>, <<"runtime_summary">>], lists:sort([maps:get(<<"name">>, T) || T <- Tools])), + ?assertMatch({ok, _}, call(Config, <<"runtime_summary">>, #{})), + [?assertMatch({rpc_error, -32602, _}, call(Config, T, #{})) + || T <- [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, + <<"process_info">>, <<"debug_session">>]]. + +arguments_are_strict(Config) -> + [?assertMatch({rpc_error, -32602, _}, call(Config, T, A)) + || {T, A} <- [{<<"runtime_summary">>, #{<<"extra">> => 1}}, + {<<"runtime_summary">>, #{<<"redactNodeHost">> => <<"yes">>}}, + {<<"application_overview">>, #{<<"application">> => 5}}, + {<<"application_overview">>, #{<<"application">> => binary:copy(<<"a">>, 65)}}, + {<<"application_overview">>, #{<<"cursor">> => binary:copy(<<"a">>, 201)}}, + {<<"application_overview">>, #{<<"includeModules">> => 1}}, + {<<"supervision_tree">>, #{}}, + {<<"supervision_tree">>, #{<<"id">> => <<"x">>, <<"supervisor">> => <<"y">>}}, + {<<"supervision_tree">>, #{<<"id">> => <<"x">>, <<"maxDepth">> => 0}}, + {<<"supervision_tree">>, #{<<"id">> => <<"x">>, <<"maxDepth">> => 17}}, + {<<"process_info">>, #{}}, + {<<"process_info">>, #{<<"name">> => <<"a">>, <<"pid">> => <<"<0.1.0>">>}}, + {<<"process_info">>, #{<<"name">> => binary:copy(<<"a">>, 256)}}, + {<<"ets_tables">>, #{<<"table">> => <<"mcp_fx_orders">>}}, + {<<"debug_session">>, #{<<"anything">> => true}}]], + %% params must be an object + ?assertMatch(#{<<"error">> := #{<<"code">> := -32602}}, + rpc(Config, <<"tools/call">>, #{<<"name">> => <<"ets_tables">>, <<"arguments">> => [1]})). + +runtime_summary_redacts_node_host(Config) -> + S = ok_call(Config, <<"runtime_summary">>, #{}), + ?assertEqual(true, maps:get(<<"nodeRedacted">>, S)), + ?assertNotEqual(nomatch, binary:match(maps:get(<<"node">>, S), <<"@">>)), + Full = ok_call(Config, <<"runtime_summary">>, #{<<"redactNodeHost">> => false}), + ?assertEqual(atom_to_binary(node(), utf8), maps:get(<<"node">>, Full)), + ?assertEqual(list_to_binary(erlang:system_info(otp_release)), maps:get(<<"otpRelease">>, S)), + ?assertEqual(erlang:system_info(schedulers), maps:get(<<"schedulers">>, S)), + ?assert(maps:get(<<"processCount">>, S) > 10), + ?assert(maps:get(<<"uptimeMs">>, S) >= 0), + ?assertMatch(#{<<"unit">> := <<"bytes">>, <<"total">> := T} when is_integer(T), maps:get(<<"memory">>, S)), + %% opaque, non-secret session id shared with the map contract + Sid = maps:get(<<"sessionId">>, S), + ?assertEqual(Sid, maps:get(<<"sessionId">>, ok_call(Config, <<"ets_tables">>, #{}))), + ?assertEqual(Sid, maps:get(sessionId, ?config(info, Config))). + +application_overview_discovers_fixture_without_names(Config) -> + [S] = all_pages(Config, <<"application_overview">>, #{}), + %% MAP-05 envelope + [?assert(maps:is_key(K, S)) || K <- [<<"schemaVersion">>, <<"sessionId">>, <<"collectionId">>, <<"startedAt">>, + <<"finishedAt">>, <<"scope">>, <<"entities">>, <<"relationships">>, + <<"complete">>, <<"truncated">>, <<"omissions">>, <<"nextCursor">>]], + ?assertEqual(<<"1.0">>, maps:get(<<"schemaVersion">>, S)), + ?assertEqual(null, maps:get(<<"nextCursor">>, S)), + ?assertEqual(true, maps:get(<<"complete">>, S)), + ?assertEqual(false, maps:get(<<"truncated">>, S)), + ?assert(maps:get(<<"startedAt">>, S) =< maps:get(<<"finishedAt">>, S)), + Ents = maps:get(<<"entities">>, S), + App = ent(<<"mcp_fixture_app">>, Ents), + ?assertEqual(<<"application">>, maps:get(<<"kind">>, App)), + ?assertEqual(<<"9.9.9">>, maps:get(<<"version">>, App)), + ?assertEqual([<<"kernel">>, <<"stdlib">>], lists:sort(maps:get(<<"dependencies">>, App))), + ?assertEqual(<<"discovered">>, maps:get(<<"rootStatus">>, App)), + [RootId] = maps:get(<<"roots">>, App), + Root = [E || E <- Ents, id_of(E) =:= RootId], + ?assertMatch([#{<<"name">> := <<"mcp_fx_root">>, <<"role">> := <<"supervisor">>, <<"kind">> := <<"process">>}], Root), + %% a library application has no start module: explained, not an error + ?assertEqual(<<"no_start_module">>, maps:get(<<"rootStatus">>, ent(<<"stdlib">>, Ents))), + ?assertEqual([], maps:get(<<"roots">>, ent(<<"stdlib">>, Ents))), + %% dependencies are declarations, with evidence and confidence + Deps = [R || #{<<"type">> := <<"depends_on">>, <<"from">> := F} = R <- maps:get(<<"relationships">>, S), + F =:= id_of(App)], + ?assertEqual(2, length(Deps)), + [?assertMatch(#{<<"confidence">> := <<"confirmed">>, <<"evidence">> := <<"declared", _/binary>>, + <<"observedAt">> := _}, D) || D <- Deps], + ?assertMatch(#{<<"type">> := <<"belongs_to">>, <<"from">> := RootId, <<"to">> := _}, + hd([R || #{<<"type">> := <<"belongs_to">>, <<"from">> := F} = R <- maps:get(<<"relationships">>, S), F =:= RootId])), + %% MAP-04: coverage limitations are explicit + ?assert(length(maps:get(<<"limitations">>, maps:get(<<"scope">>, S))) >= 3), + %% selection by discovered id; unknown ids are refused + Only = ok_call(Config, <<"application_overview">>, #{<<"application">> => id_of(App)}), + ?assertEqual(<<"discovered">>, maps:get(<<"rootStatus">>, ent(<<"mcp_fixture_app">>, maps:get(<<"entities">>, Only)))), + ?assertEqual([], by_name(<<"ssl">>, maps:get(<<"entities">>, Only))), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"unknown_or_expired_id">>}}}, + call(Config, <<"application_overview">>, #{<<"application">> => <<"a_deadbeef">>})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_id">>}}}, + call(Config, <<"application_overview">>, #{<<"application">> => RootId})), + %% modules: names and behaviours only when already loaded, never loaded by the inspector + M = ok_call(Config, <<"application_overview">>, #{<<"application">> => id_of(App), <<"includeModules">> => true}), + Mods = [E || #{<<"kind">> := <<"module">>} = E <- maps:get(<<"entities">>, M)], + ?assertEqual([<<"mcp_fixture_sup">>, <<"mcp_fixture_worker">>], lists:sort([maps:get(<<"name">>, E) || E <- Mods])), + ?assertEqual([<<"gen_server">>], maps:get(<<"behaviours">>, hd(by_name(<<"mcp_fixture_worker">>, Mods)))), + ?assert(lists:all(fun(E) -> is_boolean(maps:get(<<"loaded">>, E)) end, Mods)), + %% no compile path, no environment + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [S])), "SENTINEL|\\.beam|/ebin")). + +supervision_tree_matches_independent_expected_graph(Config) -> + RootId = fixture_root_id(Config), + Pages = all_pages(Config, <<"supervision_tree">>, #{<<"id">> => RootId}), + Expected = lists:sort([{<<"mcp_fx_root">>, <<"worker_a">>}, + {<<"mcp_fx_root">>, <<"mcp_fx_sub_sup">>}, + {<<"mcp_fx_root">>, <<"mcp_fx_dyn_sup">>}, + {<<"mcp_fx_sub_sup">>, <<"worker_b">>}, + {<<"mcp_fx_sub_sup">>, <<"worker_c">>}]), + ?assertEqual(Expected, supervises_edges(Pages)), + Ents = entities(Pages), + ?assertEqual(<<"supervisor">>, maps:get(<<"role">>, ent(<<"mcp_fx_sub_sup">>, [E || E <- Ents, maps:get(<<"childId">>, E, undefined) =:= <<"mcp_fx_sub_sup">>]))), + Wa = ent(<<"mcp_fx_worker_a">>, Ents), + ?assertMatch(#{<<"role">> := <<"worker">>, <<"callbackModule">> := <<"mcp_fixture_worker">>, + <<"modules">> := [<<"mcp_fixture_worker">>], <<"behaviour">> := [<<"gen_server">>], + <<"restart">> := <<"permanent">>, <<"shutdown">> := 1000, <<"specAvailable">> := true}, Wa), + Wb = hd([E || #{<<"childId">> := <<"worker_b">>} = E <- Ents]), + ?assertMatch(#{<<"role">> := <<"worker">>, <<"restart">> := <<"transient">>, <<"name">> := null}, Wb), + %% typed edges carry evidence, observation time and confidence + [?assertMatch(#{<<"evidence">> := <<"supervisor:which_children/1">>, <<"confidence">> := <<"confirmed">>, + <<"observedAt">> := _}, R) + || #{<<"type">> := <<"supervises">>} = R <- relationships(Pages)], + %% never start arguments, dictionary, state + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [Pages])), "SENTINEL")), + ?assertNot(lists:any(fun(E) -> maps:is_key(<<"start">>, E) orelse maps:is_key(<<"args">>, E) end, Ents)), + %% no link/monitor edge in a supervision map, and complete + ?assertEqual([], [T || #{<<"type">> := T} <- relationships(Pages), + T =:= <<"linked_to">> orelse T =:= <<"monitors">>]), + [?assertEqual(true, maps:get(<<"complete">>, P)) || P <- Pages], + %% starting from an application id reaches the same tree; depth is bounded and explained + AppId = fixture_app_id(Config), + ?assertEqual(Expected, supervises_edges(all_pages(Config, <<"supervision_tree">>, #{<<"id">> => AppId}))), + Shallow = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId, <<"maxDepth">> => 1}), + ?assertEqual(lists:sort([{<<"mcp_fx_root">>, <<"worker_a">>}, {<<"mcp_fx_root">>, <<"mcp_fx_dyn_sup">>}, + {<<"mcp_fx_root">>, <<"mcp_fx_sub_sup">>}]), supervises_edges([Shallow])), + ?assertEqual(false, maps:get(<<"complete">>, Shallow)), + ?assertEqual(true, maps:get(<<"truncated">>, Shallow)), + Om = [O || #{<<"reason">> := <<"limit_reached">>} = O <- maps:get(<<"omissions">>, Shallow)], + ?assertEqual(2, length(Om)), + %% progressive expansion: the omission's scope is a usable id + SubId = maps:get(<<"scope">>, hd(Om)), + Sub = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => SubId, <<"maxDepth">> => 1}), + ?assert(length(supervises_edges([Sub])) =:= 2 orelse length(supervises_edges([Sub])) =:= 0), + %% an explicitly supplied local supervisor + Named = ok_call(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"mcp_fx_sub_sup">>}), + ?assertEqual([{<<"mcp_fx_sub_sup">>, <<"worker_b">>}, {<<"mcp_fx_sub_sup">>, <<"worker_c">>}], supervises_edges([Named])), + %% a plain worker is not treated as a supervisor (never called with supervisor APIs) + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_a_supervisor">>}}}, + call(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"mcp_fx_worker_a">>})), + ?assert(is_process_alive(whereis(mcp_fx_worker_a))), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"no_such_registered_name_zz">>})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_a_supervisor">>}}}, + call(Config, <<"supervision_tree">>, #{<<"id">> => id_of(hd([E || #{<<"kind">> := <<"process">>, <<"role">> := <<"worker">>} = E <- Ents]))})). + +linked_unsupervised_process_is_not_supervised(Config) -> + Orphan = whereis(mcp_fx_orphan), + ?assert(is_pid(Orphan)), + Pages = all_pages(Config, <<"supervision_tree">>, #{<<"id">> => fixture_root_id(Config)}), + ?assertEqual([], by_name(<<"mcp_fx_orphan">>, entities(Pages))), + P = ok_call(Config, <<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_a">>}), + Ents = maps:get(<<"entities">>, P), + Wa = ent(<<"mcp_fx_worker_a">>, Ents), + Or = ent(<<"mcp_fx_orphan">>, Ents), + Links = [R || #{<<"type">> := <<"linked_to">>} = R <- maps:get(<<"relationships">>, P)], + ?assert(lists:any(fun(#{<<"from">> := F, <<"to">> := T}) -> F =:= id_of(Wa) andalso T =:= id_of(Or) end, Links)), + [?assertMatch(#{<<"confidence">> := <<"confirmed">>, <<"evidence">> := <<"process_info links">>}, L) || L <- Links], + %% a link is never a supervision edge + ?assertEqual([], [R || #{<<"type">> := <<"supervises">>} = R <- maps:get(<<"relationships">>, P)]). + +process_info_inputs_are_safe(Config) -> + P = ok_call(Config, <<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_c">>}), + Wc = ent(<<"mcp_fx_worker_c">>, maps:get(<<"entities">>, P)), + Allowed = [<<"id">>, <<"kind">>, <<"pid">>, <<"name">>, <<"alive">>, <<"role">>, <<"status">>, + <<"currentFunction">>, <<"initialCall">>, <<"reductions">>, <<"memory">>, <<"memoryUnit">>, + <<"messageQueueLen">>, <<"application">>, <<"membership">>, <<"callbackModule">>, <<"behaviour">>], + ?assertEqual([], maps:keys(Wc) -- Allowed), + ?assertMatch(#{<<"status">> := _, <<"reductions">> := R, <<"memory">> := M, <<"messageQueueLen">> := 0, + <<"memoryUnit">> := <<"bytes">>, <<"initialCall">> := _, <<"currentFunction">> := _} + when is_integer(R) andalso is_integer(M), Wc), + ?assertMatch(#{<<"confidence">> := _, <<"evidence">> := _}, maps:get(<<"membership">>, Wc)), + %% callback/behaviour are unknown unless safe metadata is available (no dictionary, no sys:get_state) + ?assertEqual(<<"unknown">>, maps:get(<<"callbackModule">>, Wc)), + %% same process by id and by pid text + Id = id_of(Wc), + ?assertEqual(Id, id_of(ent(<<"mcp_fx_worker_c">>, maps:get(<<"entities">>, ok_call(Config, <<"process_info">>, #{<<"id">> => Id}))))), + PidText = maps:get(<<"pid">>, Wc), + ?assertEqual(Id, id_of(ent(<<"mcp_fx_worker_c">>, maps:get(<<"entities">>, ok_call(Config, <<"process_info">>, #{<<"pid">> => PidText}))))), + %% hostile identifiers: nothing is created, nothing crosses the local node + Atoms = erlang:system_info(atom_count), + Hostile = [<<"<1.2.3>">>, <<"<0.99999999999.0>">>, <<"<0.1.0>x">>, <<"<0.1.0> ">>, <<"<0.0.0.0>">>, + <<"#Port<0.1>">>, <<"<0.1>">>, <<"">>], + [?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := C}}} when C =:= <<"invalid_pid">> orelse C =:= <<"not_found">>, + call(Config, <<"process_info">>, #{<<"pid">> => H})) || H <- Hostile], + [?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"process_info">>, #{<<"name">> => <<"unique_hostile_name_", (integer_to_binary(I))/binary>>})) + || I <- lists:seq(1, 50)], + ?assertEqual(Atoms, erlang:system_info(atom_count)), + %% an exited process is reported, not invented + Gone = spawn(fun() -> ok end), + timer:sleep(50), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"process_info">>, #{<<"pid">> => list_to_binary(pid_to_list(Gone))})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"unknown_or_expired_id">>}}}, + call(Config, <<"process_info">>, #{<<"id">> => <<"p_0000000000000000">>})), + %% an application id is not a process id + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_id">>}}}, + call(Config, <<"process_info">>, #{<<"id">> => fixture_app_id(Config)})). + +registered_processes_membership_evidence(Config) -> + AppId = fixture_app_id(Config), + Pages = all_pages(Config, <<"registered_processes">>, #{<<"application">> => AppId}), + Ents = entities(Pages), + Names = lists:sort([N || #{<<"name">> := N} <- Ents, is_binary(N)]), + ?assertEqual([<<"mcp_fx_dyn_sup">>, <<"mcp_fx_orphan">>, <<"mcp_fx_root">>, <<"mcp_fx_sub_sup">>, + <<"mcp_fx_worker_a">>, <<"mcp_fx_worker_c">>], Names), + Conf = fun(N) -> maps:get(<<"confidence">>, maps:get(<<"membership">>, ent(N, Ents))) end, + %% found in the supervision tree: confirmed + [?assertEqual(<<"confirmed">>, Conf(N)) || N <- [<<"mcp_fx_root">>, <<"mcp_fx_sub_sup">>, + <<"mcp_fx_worker_a">>, <<"mcp_fx_worker_c">>, <<"mcp_fx_dyn_sup">>]], + %% the unsupervised orphan shares the group leader only: never "confirmed" + ?assertEqual(<<"inferred">>, Conf(<<"mcp_fx_orphan">>)), + %% unrelated registered names are not silently assigned to the application + ?assertEqual([], by_name(<<"kernel_sup">>, Ents)), + ?assertEqual([], by_name(<<"mcp_fx_mailbox">>, Ents)), + ?assert(lists:any(fun(#{<<"reason">> := R}) -> R =:= <<"unknown_membership">> end, + lists:append([maps:get(<<"omissions">>, P) || P <- Pages]))), + [?assertEqual(false, maps:get(<<"complete">>, P)) || P <- Pages], + %% without a filter: bounded, membership stated per entry, coverage limitations explicit + All = all_pages(Config, <<"registered_processes">>, #{}), + AllEnts = entities(All), + ?assert(length(by_name(<<"kernel_sup">>, AllEnts)) =:= 1), + ?assertEqual(<<"unknown">>, maps:get(<<"confidence">>, maps:get(<<"membership">>, ent(<<"mcp_fx_mailbox">>, AllEnts)))), + %% inspector processes are never listed as application processes + ?assertEqual([], [N || #{<<"name">> := N} <- AllEnts, is_binary(N), binary:match(N, <<"mcp_server">>) =/= nomatch + orelse binary:match(N, <<"mcp_store">>) =/= nomatch orelse binary:match(N, <<"mcp_holder">>) =/= nomatch + orelse binary:match(N, <<"mcp_sup">>) =/= nomatch]), + ?assert(length(maps:get(<<"limitations">>, maps:get(<<"scope">>, hd(All)))) >= 3), + %% mailbox contents are never read: only the queue length + MB = ent(<<"mcp_fx_mailbox">>, AllEnts), + ?assertEqual(1, maps:get(<<"messageQueueLen">>, MB)), + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [All])), "SENTINEL")). + +ets_tables_metadata_only_for_approved_tables(Config) -> + S = ok_call(Config, <<"ets_tables">>, #{}), + Ents = maps:get(<<"entities">>, S), + Tabs = [E || #{<<"kind">> := <<"ets_table">>} = E <- Ents], + %% only the approved, non-private, existing table + ?assertMatch([#{<<"name">> := <<"mcp_fx_orders">>, <<"protection">> := <<"public">>, <<"type">> := <<"set">>, + <<"size">> := 1, <<"memoryUnit">> := <<"words">>}], Tabs), + [Tab] = Tabs, + ?assert(is_integer(maps:get(<<"memory">>, Tab))), + ?assertEqual(maps:get(<<"memory">>, Tab) * erlang:system_info(wordsize), maps:get(<<"memoryBytes">>, Tab)), + %% owner correlation: joinable with process entities via an owns_table edge + Owner = ent(<<"mcp_fx_worker_c">>, Ents), + ?assertEqual(id_of(Owner), maps:get(<<"owner">>, Tab)), + ?assertMatch([#{<<"from">> := _, <<"to">> := _, <<"evidence">> := <<"ets:info owner">>}], + [R || #{<<"type">> := <<"owns_table">>} = R <- maps:get(<<"relationships">>, S)]), + %% private and missing approved tables are counted without disclosing names + Reasons = lists:sort([{maps:get(<<"reason">>, O), maps:get(<<"count">>, O)} || O <- maps:get(<<"omissions">>, S)]), + ?assertEqual([{<<"disappeared">>, 1}, {<<"policy_denied">>, 1}], Reasons), + Text = iolist_to_binary(io_lib:format("~p", [maps:get(<<"omissions">>, S)])), + ?assertEqual(nomatch, binary:match(Text, <<"mcp_fx_private">>)), + ?assertEqual(nomatch, binary:match(Text, <<"zzz">>)), + %% no node-wide inventory: an unapproved table is invisible + ?assertEqual([], by_name(<<"ac_tab">>, Ents)), + %% keys/objects/values are never read + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [S])), "SENTINEL")), + ?assertEqual(false, maps:get(<<"complete">>, S)), + %% a recreated table has a different identity even with the same name + OldId = id_of(Tab), + ets:delete(mcp_fx_orders), + Owner2 = spawn(fun() -> ets:new(mcp_fx_orders, [named_table, public, set]), receive stop -> ok end end), + timer:sleep(100), + S2 = ok_call(Config, <<"ets_tables">>, #{}), + [Tab2] = [E || #{<<"kind">> := <<"ets_table">>} = E <- maps:get(<<"entities">>, S2)], + ?assertNotEqual(OldId, id_of(Tab2)), + ?assertEqual(0, maps:get(<<"size">>, Tab2)), + exit(Owner2, kill), + %% a disappeared table is reported, not invented + timer:sleep(50), + S3 = ok_call(Config, <<"ets_tables">>, #{}), + ?assertEqual([], [E || #{<<"kind">> := <<"ets_table">>} = E <- maps:get(<<"entities">>, S3)]), + ?assertEqual(2, lists:sum([maps:get(<<"count">>, O) || #{<<"reason">> := <<"disappeared">>} = O <- maps:get(<<"omissions">>, S3)])), + %% restore the fixture table (its owner is restarted by its supervisor) + OldOwner = whereis(mcp_fx_worker_c), + exit(OldOwner, kill), + wait_until(fun() -> is_pid(whereis(mcp_fx_worker_c)) andalso whereis(mcp_fx_worker_c) =/= OldOwner end), + wait_until(fun() -> ets:info(mcp_fx_orders, size) =:= 1 end). + +restart_changes_identity(Config) -> + RootId = fixture_root_id(Config), + Before = entities(all_pages(Config, <<"supervision_tree">>, #{<<"id">> => RootId})), + Old = hd([E || #{<<"childId">> := <<"worker_c">>} = E <- Before]), + OldPid = whereis(mcp_fx_worker_c), + exit(OldPid, kill), + wait_until(fun() -> is_pid(whereis(mcp_fx_worker_c)) andalso whereis(mcp_fx_worker_c) =/= OldPid end), + After = entities(all_pages(Config, <<"supervision_tree">>, #{<<"id">> => RootId})), + New = hd([E || #{<<"childId">> := <<"worker_c">>} = E <- After]), + ?assertNotEqual(id_of(Old), id_of(New)), + %% unchanged entities keep their id across collections (join across tools) + ?assertEqual(id_of(hd([E || #{<<"childId">> := <<"worker_a">>} = E <- Before])), + id_of(hd([E || #{<<"childId">> := <<"worker_a">>} = E <- After]))), + %% the previous identity is stale, never resolved to the new process + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"process_info">>, #{<<"id">> => id_of(Old)})), + %% absence of the old id from a later collection does not mean anything else was invented + ?assertEqual(supervises_edges([#{<<"entities">> => Before, <<"relationships">> => []}]), []), + %% a supervisor entry that is restarting/absent is preserved without a live pid: terminate_child keeps the spec + ok = supervisor:terminate_child(mcp_fx_sub_sup, worker_b), + Slots = entities(all_pages(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"mcp_fx_sub_sup">>})), + Wb = hd([E || #{<<"childId">> := <<"worker_b">>} = E <- Slots]), + ?assertMatch(#{<<"alive">> := false, <<"pid">> := null, <<"childState">> := <<"not_running">>}, Wb), + {ok, _} = supervisor:restart_child(mcp_fx_sub_sup, worker_b). + +pagination_cursors_and_retention(Config) -> + ok = mcp_fixture_sup:add_dynamic(30), + RootId = fixture_root_id(Config), + Args = #{<<"id">> => RootId}, + First = ok_call(Config, <<"supervision_tree">>, Args), + %% max_items counts entities and relationships together per page + ?assert(length(maps:get(<<"entities">>, First)) + length(maps:get(<<"relationships">>, First)) =< 10), + Cursor = maps:get(<<"nextCursor">>, First), + ?assert(is_binary(Cursor)), + Pages = collect_pages(Config, <<"supervision_tree">>, Args, First, [First]), + ?assert(length(Pages) > 5), + [?assert(length(maps:get(<<"entities">>, P)) + length(maps:get(<<"relationships">>, P)) =< 10) || P <- Pages], + %% every page belongs to the same retained collection: no restart, no duplicates + ?assertEqual(1, length(lists:usort([maps:get(<<"collectionId">>, P) || P <- Pages]))), + Ids = [id_of(E) || E <- entities(Pages)], + ?assertEqual(length(Ids), length(lists:usort(Ids))), + %% 1 root + 3 children + 2 (sub) + 30 dynamic workers, 35 supervises edges + ?assertEqual(36, length(Ids)), + ?assertEqual(35, length(supervises_edges(Pages))), + %% each page is bounded, the last has no cursor + ?assertEqual(null, maps:get(<<"nextCursor">>, lists:last(Pages))), + %% cross-page edges reference safe ids, flagged as outside the page + ?assert(lists:any(fun(#{<<"fromInPage">> := F, <<"toInPage">> := T}) -> not (F andalso T) end, relationships(Pages))), + %% cursors are bound to tool, arguments and session + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_cursor">>}}}, + call(Config, <<"supervision_tree">>, #{<<"id">> => fixture_app_id(Config), <<"cursor">> => Cursor})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_cursor">>}}}, + call(Config, <<"registered_processes">>, #{<<"cursor">> => Cursor})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_cursor">>}}}, + call(Config, <<"supervision_tree">>, Args#{<<"maxDepth">> => 3, <<"cursor">> => Cursor})), + [?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_cursor">>}}}, + call(Config, <<"supervision_tree">>, Args#{<<"cursor">> => Bad})) + || Bad <- [<<"c1.x.0.deadbeef">>, <<"garbage">>, <<>>, <<"c1..0.">>, <<"c1.c_0000.-1.aa">>, + flip_last(Cursor)]], + %% retention: the count budget evicts the oldest collection (max_collections = 2) + _ = ok_call(Config, <<"registered_processes">>, #{}), + _ = ok_call(Config, <<"ets_tables">>, #{}), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"cursor_expired">>}}}, + call(Config, <<"supervision_tree">>, Args#{<<"cursor">> => Cursor})), + %% retention: TTL (1.5 s) - an expired collection is never silently restarted + Fresh = ok_call(Config, <<"supervision_tree">>, Args), + Cursor2 = maps:get(<<"nextCursor">>, Fresh), + ?assertMatch({ok, _}, call(Config, <<"supervision_tree">>, Args#{<<"cursor">> => Cursor2})), + timer:sleep(1800), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"cursor_expired">>}}}, + call(Config, <<"supervision_tree">>, Args#{<<"cursor">> => Cursor2})), + %% bounded identity bookkeeping and retained state + Stats = mcp_store:stats(), + ?assert(maps:get(collections, Stats) =< 2), + wait_until(fun() -> maps:get(slots, mcp_store:stats()) =:= 0 end), + lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, + supervisor:which_children(mcp_fx_dyn_sup)). + +collection_byte_budget_is_partial_and_non_resumable(Config) -> + ok = mcp_fixture_sup:add_dynamic(60), + Args = #{<<"supervisor">> => <<"mcp_fx_root">>}, + S = ok_call(Config, <<"supervision_tree">>, Args), + ?assertEqual(false, maps:get(<<"complete">>, S)), + ?assertEqual(true, maps:get(<<"truncated">>, S)), + [Om | _] = [O || #{<<"reason">> := <<"limit_reached">>, <<"resumable">> := false} = O <- maps:get(<<"omissions">>, S)], + ?assert(maps:get(<<"count">>, Om) > 0), + ?assertNotEqual(nomatch, binary:match(maps:get(<<"message">>, Om), <<"narrow">>)), + %% pages of the truncated collection still terminate and never claim completeness + Pages = collect_pages(Config, <<"supervision_tree">>, Args, S, [S]), + [?assertEqual(false, maps:get(<<"complete">>, P)) || P <- Pages], + ?assertEqual(null, maps:get(<<"nextCursor">>, lists:last(Pages))), + ?assert(maps:get(collection_bytes, mcp_store:stats()) =< 2 * 4096), + %% the whole response envelope stays under max_result_bytes + {200, _, Raw} = http(?config(port, Config), "POST", std_headers(Config), + iolist_to_binary(element(2, vscode_jsone:encode( + #{<<"jsonrpc">> => <<"2.0">>, <<"id">> => 9, <<"method">> => <<"tools/call">>, + <<"params">> => #{<<"name">> => <<"supervision_tree">>, + <<"arguments">> => Args}})))), + ?assert(byte_size(Raw) =< 4096), + lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, + supervisor:which_children(mcp_fx_dyn_sup)). + +blocked_supervisor_yields_partial_result(Config) -> + RootId = fixture_root_id(Config), + ok = sys:suspend(mcp_fx_sub_sup), + T0 = erlang:monotonic_time(millisecond), + S = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId}), + Elapsed = erlang:monotonic_time(millisecond) - T0, + ?assert(Elapsed < 3000), + ?assertEqual(false, maps:get(<<"complete">>, S)), + [Om] = [O || #{<<"reason">> := <<"timeout">>} = O <- maps:get(<<"omissions">>, S)], + %% no "paused" claim without debugger evidence + ?assertEqual([], [O || #{<<"reason">> := <<"unavailable_while_paused">>} = O <- maps:get(<<"omissions">>, S)]), + Ents = maps:get(<<"entities">>, S), + SubId = maps:get(<<"scope">>, Om), + ?assertEqual(<<"mcp_fx_sub_sup">>, maps:get(<<"childId">>, hd([E || E <- Ents, id_of(E) =:= SubId]))), + %% the healthy branches are still mapped, the blocked subtree is missing rather than invented + Edges = supervises_edges([S]), + ?assert(lists:member({<<"mcp_fx_root">>, <<"worker_a">>}, Edges)), + ?assertEqual([], [E || {<<"mcp_fx_sub_sup">>, _} = E <- Edges]), + %% the inspector resumed nothing and altered nothing + {status, _, _, [_, SysState | _]} = sys:get_status(mcp_fx_sub_sup), + ?assertEqual(suspended, SysState), + ok = sys:resume(mcp_fx_sub_sup), + %% inspector-owned workers and slots are back to baseline + wait_until(fun() -> maps:get(slots, mcp_store:stats()) =:= 0 end), + S2 = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId}), + ?assertEqual(true, maps:get(<<"complete">>, S2)), + ?assertEqual(5, length(supervises_edges([S2]))). + +concurrency_is_bounded(Config) -> + RootId = fixture_root_id(Config), + ok = sys:suspend(mcp_fx_sub_sup), + Self = self(), + spawn(fun() -> Self ! {first, call(Config, <<"supervision_tree">>, #{<<"id">> => RootId})} end), + wait_until(fun() -> maps:get(slots, mcp_store:stats()) =:= 1 end), + %% excess work is rejected with a clear MCP error, not queued + ?assertMatch({rpc_error, -32000, <<"inspector busy", _/binary>>}, call(Config, <<"runtime_summary">>, #{})), + receive {first, R} -> ?assertMatch({ok, _}, R) after 6000 -> ct:fail(no_first_result) end, + ok = sys:resume(mcp_fx_sub_sup), + wait_until(fun() -> maps:get(slots, mcp_store:stats()) =:= 0 end), + ?assertMatch({ok, _}, call(Config, <<"runtime_summary">>, #{})). + +stop_closes_endpoint_and_invalidates_credentials(Config) -> + Port = ?config(port, Config), + Tok = ?config(token, Config), + Cursor = maps:get(<<"nextCursor">>, ok_call_small(Config)), + ?assert(is_binary(Cursor)), + ?assertMatch({200, _, _}, http(Port, "POST", std_headers(Config), <<"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}">>)), + ok = mcp_sup:stop_session(), + ?assertNot(mcp_sup:active()), + ?assertEqual(undefined, whereis(mcp_server)), + ?assertEqual(undefined, whereis(mcp_store)), + ?assertEqual(undefined, whereis(mcp_sup)), + ?assertMatch({error, econnrefused}, gen_tcp:connect({127, 0, 0, 1}, Port, [binary], 1000)), + %% a new session has a fresh token and identity; old credentials and cursors are worthless + {ok, Info2} = mcp_sup:start_session(?config(cfg, Config), launch), + ?assertNotEqual(Tok, maps:get(token, Info2)), + ?assertNotEqual(maps:get(sessionId, ?config(info, Config)), maps:get(sessionId, Info2)), + Cfg2 = [{port, maps:get(port, Info2)}, {token, Tok}], + ?assertMatch({401, _, _}, http(maps:get(port, Info2), "POST", std_headers(Cfg2), <<"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}">>)), + New = [{port, maps:get(port, Info2)}, {token, maps:get(token, Info2)}], + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_cursor">>}}}, + call(New, <<"application_overview">>, #{<<"cursor">> => Cursor})), + %% concurrent start is refused: one inspector per node + ?assertMatch({error, <<"an MCP inspector is already running", _/binary>>}, mcp_sup:start_session(?config(cfg, Config), launch)), + ok = mcp_sup:stop_session(). + +ok_call_small(Config) -> + %% a call whose result spans several pages under the default limits: many registered names + Names = [list_to_atom("mcp_fx_pad_" ++ integer_to_list(I)) || I <- lists:seq(1, 600)], + Pids = [begin P = spawn(fun() -> receive stop -> ok end end), register(N, P), P end || N <- Names], + S = ok_call(Config, <<"registered_processes">>, #{}), + [exit(P, kill) || P <- Pids], + S. + +fixed_port_conflict_disables_without_rebinding(_Config) -> + {ok, Busy} = gen_tcp:listen(0, [binary, {ip, {127, 0, 0, 1}}, {reuseaddr, false}]), + {ok, Port} = inet:port(Busy), + Cfg = (mcp_policy:defaults())#{port => Port}, + {error, Msg} = mcp_sup:start_session(Cfg, launch), + ?assertNotEqual(nomatch, binary:match(Msg, <<"already in use">>)), + %% nothing partially initialized is left running, no other port was chosen + ?assertNot(mcp_sup:active()), + [?assertEqual(undefined, whereis(N)) || N <- [mcp_holder, mcp_sup, mcp_server, mcp_store]], + gen_tcp:close(Busy), + %% once the port is free the same fixed port can be used, and it is really that port + {ok, Info} = mcp_sup:start_session(Cfg, launch), + ?assertEqual(Port, maps:get(port, Info)), + ok = mcp_sup:stop_session(). + +renewal_keeps_the_session_alive_and_lease_expiry_stops_it(_Config) -> + {ok, Info} = mcp_sup:start_session(mcp_policy:defaults(), launch), + Port = maps:get(port, Info), + %% renewals (as the adapter sends them) keep the session beyond the lease duration + [begin timer:sleep(2000), ok = mcp_sup:renew() end || _ <- lists:seq(1, 6)], + ?assert(mcp_sup:active()), + ?assertMatch({ok, _}, gen_tcp:connect({127, 0, 0, 1}, Port, [binary], 1000)), + %% without renewal the inspector stops within the documented bound (10 s) + T0 = erlang:monotonic_time(millisecond), + wait_until(fun() -> not mcp_sup:active() end, 12000), + Elapsed = erlang:monotonic_time(millisecond) - T0, + ct:pal("lease expired after ~p ms without renewal", [Elapsed]), + ?assert(Elapsed =< 10000), + ?assertMatch({error, econnrefused}, gen_tcp:connect({127, 0, 0, 1}, Port, [binary], 1000)), + [?assertEqual(undefined, whereis(N)) || N <- [mcp_holder, mcp_sup, mcp_server, mcp_store]], + %% the application node itself is untouched + ?assert(is_pid(whereis(mcp_fx_root))). + +no_forbidden_data_no_atoms_no_stacktraces(Config) -> + %% warm up runtime dependencies (and the atoms they need), then measure + RootId = fixture_root_id(Config), + AppId = fixture_app_id(Config), + Calls = [{<<"runtime_summary">>, #{}}, {<<"debug_session">>, #{}}, + {<<"application_overview">>, #{<<"includeModules">> => true}}, + {<<"supervision_tree">>, #{<<"id">> => RootId, <<"includeModules">> => true}}, + {<<"registered_processes">>, #{}}, {<<"registered_processes">>, #{<<"application">> => AppId}}, + {<<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_a">>}}, + {<<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_c">>}}, + {<<"ets_tables">>, #{}}], + Results = [call(Config, T, A) || {T, A} <- Calls], + Atoms = erlang:system_info(atom_count), + %% hostile input, repeated with unique strings + Hostile = lists:flatten( + [[call(Config, <<"process_info">>, #{<<"name">> => <<"hostile_", (integer_to_binary(I))/binary>>}), + call(Config, <<"process_info">>, #{<<"pid">> => <<"<0.", (integer_to_binary(I + 100000))/binary, ".0>">>}), + call(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"sup_", (integer_to_binary(I))/binary>>}), + call(Config, <<"application_overview">>, #{<<"application">> => <<"a_", (integer_to_binary(I))/binary>>}), + call(Config, list_to_binary("no_such_tool_" ++ integer_to_list(I)), #{})] + || I <- lists:seq(1, 60)]), + _ = [http(?config(port, Config), "POST", std_headers(Config), <<"{\"a\":", (integer_to_binary(I))/binary>>) || I <- lists:seq(1, 20)], + _ = [http(?config(port, Config), "POST", [{"Host", ["h", integer_to_list(I)]}], <<>>) || I <- lists:seq(1, 20)], + %% >340 unique hostile inputs: allow only unrelated background noise + ?assert(erlang:system_info(atom_count) - Atoms < 20), + Text = iolist_to_binary(io_lib:format("~p", [Results ++ Hostile])), + %% forbidden data (state, dictionary, mailbox, ETS values, start args, env) is never returned + ?assertEqual(nomatch, binary:match(Text, <<"SENTINEL">>)), + %% no stack traces, paths or internal terms in errors + [?assertEqual(nomatch, re:run(Text, P, [{capture, none}])) + || P <- ["badmatch", "function_clause", "case_clause", "\\.erl", "/Users/", "/home/", "stacktrace"]], + %% every advertised tool answered with a well-formed result + [?assertMatch({ok, _}, R) || R <- Results]. + +logs_never_contain_token_or_runtime_data(Config) -> + Self = self(), + HandlerId = mcp_capture_handler, + ok = logger:add_handler(HandlerId, mcp_capture_logger, #{config => #{owner => Self}, level => all}), + PrevLevel = maps:get(level, logger:get_primary_config()), + ok = logger:set_primary_config(level, all), + try + Tok = ?config(token, Config), + _ = call(Config, <<"runtime_summary">>, #{}), + _ = call(Config, <<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_a">>}), + _ = call(Config, <<"application_overview">>, #{}), + _ = http(?config(port, Config), "POST", [{"Host", ["127.0.0.1:", integer_to_list(?config(port, Config))]}, + {"Authorization", "Bearer wrongtoken_SENTINEL_BAD"}], <<"{}">>), + _ = http(?config(port, Config), "POST", [{"Host", "evil"}], <<"{}">>), + timer:sleep(200), + Logged = collect_logs([]), + ct:pal("captured ~p log events", [length(Logged)]), + Text = iolist_to_binary(io_lib:format("~p", [Logged])), + ?assertNotEqual([], Logged), + ?assertEqual(nomatch, binary:match(Text, Tok)), + ?assertEqual(nomatch, binary:match(Text, <<"wrongtoken">>)), + ?assertEqual(nomatch, binary:match(Text, <<"SENTINEL">>)), + ?assertEqual(nomatch, binary:match(Text, <<"mcp_fx_worker_a">>)), + ?assertNotEqual(nomatch, binary:match(Text, <<"tool_call">>)) + after + logger:remove_handler(HandlerId), + logger:set_primary_config(level, PrevLevel) + end. + +collect_logs(Acc) -> + receive {mcp_log, E} -> collect_logs([E | Acc]) after 100 -> lists:reverse(Acc) end. + +lsp_application_never_starts_the_inspector(_Config) -> + %% enabling nothing: the LSP application (and its settings) never start MCP + ?assertNot(mcp_sup:active()), + ok = application:start(vscode_lsp, permanent), + try + ?assertNot(mcp_sup:active()), + [?assertEqual(undefined, whereis(N)) || N <- [mcp_holder, mcp_sup, mcp_server, mcp_store]], + %% the LSP application supervisor knows no MCP child + Kids = supervisor:which_children(vscode_lsp_app_sup), + ?assertEqual([], [K || {Id, _, _, _} = K <- Kids, lists:prefix("mcp_", lists:flatten(io_lib:format("~w", [Id])))]), + %% the entry module's hot-loaded module list does not include MCP + {ok, Beam} = file:read_file(code:where_is_file("vscode_lsp_entry.beam")), + ?assertEqual(nomatch, binary:match(Beam, <<"mcp_sup">>)) + after + application:stop(vscode_lsp) + end. + +flip_last(Bin) -> + Size = byte_size(Bin) - 1, + <> = Bin, + New = case Last of $a -> $b; _ -> $a end, + <>. + +journal_reports_request_metadata_only(Config) -> + Port = ?config(port, Config), + Tok = ?config(token, Config), + #{<<"result">> := _} = rpc(Config, <<"initialize">>, #{<<"protocolVersion">> => <<"2025-06-18">>}), + #{<<"result">> := _} = rpc(Config, <<"tools/list">>, #{}), + ok = mcp_fixture_sup:add_dynamic(5), + First = ok_call(Config, <<"registered_processes">>, #{}), + _ = call(Config, <<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_a">>}), + _ = call(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"SENTINEL_no_such_sup">>}), + _ = call(Config, <<"SENTINEL_tool">>, #{}), + {401, _, _} = http(Port, "POST", [{"Host", ["127.0.0.1:", integer_to_list(Port)]}, + {"Content-Type", "application/json"}], <<"{}">>), + Entries = lists:sort(fun(A, B) -> maps:get(seq, A) =< maps:get(seq, B) end, collect_journal(7)), + ?assertEqual(lists:seq(1, 7), [maps:get(seq, E) || E <- Entries]), + [Init, List, Reg, Pinfo, Tree, Unknown, Http] = Entries, + ?assertMatch(#{method := <<"initialize">>, status := <<"ok">>, durationMs := _, bytes := _}, Init), + ?assertMatch(#{method := <<"tools/list">>, status := <<"ok">>}, List), + ?assertMatch(#{method := <<"tools/call">>, tool := <<"registered_processes">>, status := <<"ok">>, + entities := N, relationships := 0, offset := 0, cursor := false, complete := true} + when N > 0, Reg), + ?assertEqual(maps:get(<<"nextCursor">>, First) =/= null, maps:get(more, Reg)), + ?assertMatch(#{tool := <<"process_info">>, status := <<"ok">>, entities := _}, Pinfo), + ?assertMatch(#{tool := <<"supervision_tree">>, status := <<"not_found">>}, Tree), + %% unknown tool names never reach the journal as text + ?assertMatch(#{tool := <<"?">>, status := <<"-32602">>}, Unknown), + ?assertMatch(#{method := <<"http">>, status := <<"401">>}, Http), + %% metadata only: no argument, result content or credential + Text = iolist_to_binary(io_lib:format("~p", [Entries])), + [?assertEqual(nomatch, binary:match(Text, Forbidden)) + || Forbidden <- [Tok, <<"SENTINEL">>, <<"mcp_fx_worker_a">>, <<"kernel_sup">>]], + Allowed = [seq, method, tool, cursor, status, durationMs, bytes, entities, relationships, + omissions, complete, more, offset], + ?assertEqual([], lists:usort(lists:append([maps:keys(E) || E <- Entries])) -- Allowed), + ?assertEqual(7, maps:get(seq, mcp_store:stats())), + lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, + supervisor:which_children(mcp_fx_dyn_sup)). + +agent_summary_is_compact(Config) -> + Summary = ok_call(Config, <<"application_overview">>, #{<<"detail">> => <<"summary">>}), + Full = ok_call(Config, <<"application_overview">>, #{<<"detail">> => <<"full">>}), + ?assertEqual(<<"summary">>, maps:get(<<"detail">>, Summary)), + ?assertEqual(<<"full">>, maps:get(<<"detail">>, Full)), + %% same graph (ids, kinds, edges), fewer fields + Ids = fun(S) -> lists:sort([maps:get(<<"id">>, E) || E <- maps:get(<<"entities">>, S)]) end, + ?assertEqual(Ids(Full), Ids(Summary)), + ?assertEqual(length(maps:get(<<"relationships">>, Full)), length(maps:get(<<"relationships">>, Summary))), + {ok, J1} = vscode_jsone:encode(Summary), + {ok, J2} = vscode_jsone:encode(Full), + ?assert(iolist_size(J1) < iolist_size(J2)), + App = ent(<<"mcp_fixture_app">>, maps:get(<<"entities">>, Summary)), + ?assertNot(maps:is_key(<<"description">>, App)), + ?assertMatch(#{<<"roots">> := [_], <<"version">> := <<"9.9.9">>, <<"rootStatus">> := <<"discovered">>}, App), + [?assertNot(maps:is_key(<<"observedAt">>, R)) || R <- maps:get(<<"relationships">>, Summary)], + [?assertMatch(#{<<"evidence">> := _, <<"confidence">> := _}, R) || R <- maps:get(<<"relationships">>, Summary)], + %% node-wide module lists are refused in summary (explained), allowed per application + NoMods = ok_call(Config, <<"application_overview">>, #{<<"detail">> => <<"summary">>, <<"includeModules">> => true}), + ?assertEqual([], [E || #{<<"kind">> := <<"module">>} = E <- maps:get(<<"entities">>, NoMods)]), + ?assertMatch([#{<<"reason">> := <<"limit_reached">>}], maps:get(<<"omissions">>, NoMods)), + Mods = ok_call(Config, <<"application_overview">>, #{<<"detail">> => <<"summary">>, <<"includeModules">> => true, + <<"application">> => maps:get(<<"id">>, App)}), + ?assertEqual(2, length([E || #{<<"kind">> := <<"module">>} = E <- maps:get(<<"entities">>, Mods)])), + %% agent pages: 100 items by default, pageSize honoured, limitations only on the first page + ok = mcp_fixture_sup:add_dynamic(120), + Args = #{<<"supervisor">> => <<"mcp_fx_root">>, <<"detail">> => <<"summary">>}, + P1 = ok_call(Config, <<"supervision_tree">>, Args), + ?assertEqual(100, length(maps:get(<<"entities">>, P1)) + length(maps:get(<<"relationships">>, P1))), + ?assert(maps:is_key(<<"limitations">>, maps:get(<<"scope">>, P1))), + P2 = ok_call(Config, <<"supervision_tree">>, Args#{<<"cursor">> => maps:get(<<"nextCursor">>, P1)}), + ?assertNot(maps:is_key(<<"limitations">>, maps:get(<<"scope">>, P2))), + Small = ok_call(Config, <<"supervision_tree">>, Args#{<<"pageSize">> => 7}), + ?assertEqual(7, length(maps:get(<<"entities">>, Small)) + length(maps:get(<<"relationships">>, Small))), + Big = ok_call(Config, <<"supervision_tree">>, #{<<"supervisor">> => <<"mcp_fx_root">>, <<"detail">> => <<"full">>}), + ?assert(length(maps:get(<<"entities">>, Big)) + length(maps:get(<<"relationships">>, Big)) > 100), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"supervision_tree">>, Args#{<<"detail">> => <<"verbose">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"supervision_tree">>, Args#{<<"pageSize">> => 0})), + lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, + supervisor:which_children(mcp_fx_dyn_sup)). + +collect_journal(0) -> []; +collect_journal(N) -> + receive {journal, M} -> [M | collect_journal(N - 1)] + after 3000 -> ct:fail({missing_journal_entries, N}) + end. + +wait_until(Fun) -> wait_until(Fun, 5000). + +wait_until(Fun, Timeout) -> + Deadline = erlang:monotonic_time(millisecond) + Timeout, + wait_loop(Fun, Deadline). + +wait_loop(Fun, Deadline) -> + case Fun() of + true -> ok; + _ -> + case erlang:monotonic_time(millisecond) > Deadline of + true -> ct:fail(wait_until_timeout); + false -> timer:sleep(50), wait_loop(Fun, Deadline) + end + end. diff --git a/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl new file mode 100644 index 00000000..f51a1e96 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl @@ -0,0 +1,131 @@ +-module(mcp_store_SUITE). + +%% Session state (mcp_store): bounded identities, retained collections and signed cursors, worker slots. + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all, nowarn_export_all]). + +all() -> + [identity_is_stable_and_bounded, + collection_count_and_ttl_budgets, + collection_byte_budget_truncates_with_non_resumable_omission, + cursors_are_bound_to_session_collection_tool_and_arguments, + slots_are_bounded_and_released_when_the_owner_dies, + members_are_bounded]. + +init_per_testcase(Case, Config) -> + L = (mcp_policy:ceilings())#{max_items => 2, max_collections => 2, collection_ttl_ms => 400, + max_result_bytes => 4096, max_collection_bytes => 4096, + max_binary_bytes => 512, max_concurrency => 2}, + Cfg = (mcp_policy:defaults())#{limits => L}, + {ok, Pid} = mcp_store:start_link(Cfg, launch), + unlink(Pid), + [{store, Pid}, {case_name, Case} | Config]. + +end_per_testcase(_, Config) -> + mcp_store:stop(), + _ = ?config(store, Config), + ok. + +meta() -> #{scope => #{}, started_at => 1, finished_at => 2, complete => true, truncated => false, omissions => []}. + +identity_is_stable_and_bounded(_) -> + A = mcp_store:entity_id(process, {process, self()}), + ?assertEqual(A, mcp_store:entity_id(process, {process, self()})), + ?assertMatch(<<"p_", _/binary>>, A), + ?assertMatch(<<"t_", _/binary>>, mcp_store:entity_id(ets_table, {ets, x, 1})), + ?assertMatch(<<"a_", _/binary>>, mcp_store:entity_id(application, {application, kernel})), + ?assertMatch(<<"m_", _/binary>>, mcp_store:entity_id(module, {module, lists})), + ?assertEqual({ok, {process, self()}}, mcp_store:resolve_id(A)), + %% a recreated entity (different key) gets a different id + ?assertNotEqual(A, mcp_store:entity_id(process, {process, spawn(fun() -> ok end)})), + %% bookkeeping is bounded (max_items * 10 = 20): the oldest handles expire explicitly + Ids = [mcp_store:entity_id(process, {process, {fake, I}}) || I <- lists:seq(1, 100)], + ?assert(maps:get(ids, mcp_store:stats()) =< 20), + ?assertEqual(expired, mcp_store:resolve_id(A)), + ?assertEqual(expired, mcp_store:resolve_id(hd(Ids))), + ?assertMatch({ok, _}, mcp_store:resolve_id(lists:last(Ids))), + ?assertEqual(expired, mcp_store:resolve_id(<<"p_unknown">>)), + ?assertEqual(expired, mcp_store:resolve_id(42)), + ?assertEqual(expired, mcp_store:resolve_id(undefined)). + +collection_count_and_ttl_budgets(_) -> + Items = [{entity, #{<<"id">> => <<"x">>}}], + {C1, _, _} = mcp_store:put_collection(<<"t">>, <<"h">>, meta(), Items, #{}), + {C2, _, _} = mcp_store:put_collection(<<"t">>, <<"h">>, meta(), Items, #{}), + ?assertMatch({ok, _}, mcp_store:fetch_collection(C1, <<"t">>, <<"h">>)), + {C3, _, _} = mcp_store:put_collection(<<"t">>, <<"h">>, meta(), Items, #{}), + %% count budget (2): the oldest is evicted + ?assertEqual({error, cursor_expired}, mcp_store:fetch_collection(C1, <<"t">>, <<"h">>)), + ?assertMatch({ok, _}, mcp_store:fetch_collection(C2, <<"t">>, <<"h">>)), + %% bound to tool and args + ?assertEqual({error, cursor_expired}, mcp_store:fetch_collection(C3, <<"other">>, <<"h">>)), + ?assertEqual({error, cursor_expired}, mcp_store:fetch_collection(C3, <<"t">>, <<"other">>)), + %% TTL (400 ms) + timer:sleep(600), + ?assertEqual({error, cursor_expired}, mcp_store:fetch_collection(C3, <<"t">>, <<"h">>)), + ?assertEqual(0, maps:get(collections, mcp_store:stats())), + ?assertEqual(0, maps:get(collection_bytes, mcp_store:stats())). + +collection_byte_budget_truncates_with_non_resumable_omission(_) -> + Items = [{entity, #{<<"id">> => integer_to_binary(I), <<"pad">> => binary:copy(<<"p">>, 300)}} || I <- lists:seq(1, 100)], + {C, Meta, Stored} = mcp_store:put_collection(<<"t">>, <<"h">>, meta(), Items, #{}), + ?assert(length(Stored) < 100), + ?assert(length(Stored) > 0), + ?assertEqual(false, maps:get(complete, Meta)), + ?assertEqual(true, maps:get(truncated, Meta)), + [Om] = maps:get(omissions, Meta), + ?assertMatch(#{<<"reason">> := <<"limit_reached">>, <<"resumable">> := false}, Om), + ?assertEqual(100 - length(Stored), maps:get(<<"count">>, Om)), + ?assert(maps:get(collection_bytes, mcp_store:stats()) =< 4096), + {ok, #{items := Kept}} = mcp_store:fetch_collection(C, <<"t">>, <<"h">>), + ?assertEqual(Stored, Kept). + +cursors_are_bound_to_session_collection_tool_and_arguments(_) -> + Cur = mcp_store:make_cursor(<<"c_1">>, <<"tool">>, <<"args">>, 10), + ?assertEqual({ok, <<"c_1">>, 10}, mcp_store:parse_cursor(Cur, <<"tool">>, <<"args">>)), + ?assertEqual({error, invalid_cursor}, mcp_store:parse_cursor(Cur, <<"tool2">>, <<"args">>)), + ?assertEqual({error, invalid_cursor}, mcp_store:parse_cursor(Cur, <<"tool">>, <<"args2">>)), + Other = mcp_store:make_cursor(<<"c_2">>, <<"tool">>, <<"args">>, 10), + [_, _, _, Sig1] = binary:split(Cur, <<".">>, [global]), + [_, _, _, Sig2] = binary:split(Other, <<".">>, [global]), + ?assertNotEqual(Sig1, Sig2), + %% a different offset with the same signature is rejected + Forged = binary:replace(Cur, <<".10.">>, <<".11.">>), + ?assertEqual({error, invalid_cursor}, mcp_store:parse_cursor(Forged, <<"tool">>, <<"args">>)), + [?assertEqual({error, invalid_cursor}, mcp_store:parse_cursor(Bad, <<"tool">>, <<"args">>)) + || Bad <- [<<>>, <<"c1">>, <<"c1.a.b.c">>, <<"c1.a.-5.sig">>, <<"c2.c_1.10.", Sig1/binary>>, + binary:copy(<<"a">>, 5000), <<"c1.", (binary:copy(<<"a">>, 100))/binary, ".1.sig">>]], + ?assertEqual({error, invalid_cursor}, mcp_store:parse_cursor(not_binary, <<"tool">>, <<"args">>)), + %% a new session (new secret) rejects it + mcp_store:stop(), + {ok, Pid} = mcp_store:start_link((mcp_policy:defaults()), launch), + unlink(Pid), + ?assertEqual({error, invalid_cursor}, mcp_store:parse_cursor(Cur, <<"tool">>, <<"args">>)). + +slots_are_bounded_and_released_when_the_owner_dies(_) -> + P1 = spawn(fun() -> receive stop -> ok end end), + P2 = spawn(fun() -> receive stop -> ok end end), + ?assertEqual(ok, mcp_store:acquire_slot(P1)), + ?assertEqual(ok, mcp_store:acquire_slot(P2)), + ?assertEqual(busy, mcp_store:acquire_slot(self())), + ?assertEqual(2, maps:get(slots, mcp_store:stats())), + P1 ! stop, + wait(fun() -> maps:get(slots, mcp_store:stats()) =:= 1 end), + ?assertEqual(ok, mcp_store:acquire_slot(self())), + mcp_store:release_slot(self()), + wait(fun() -> maps:get(slots, mcp_store:stats()) =:= 1 end), + exit(P2, kill), + wait(fun() -> maps:get(slots, mcp_store:stats()) =:= 0 end). + +members_are_bounded(_) -> + [mcp_store:note_member(list_to_pid("<0.1." ++ integer_to_list(I) ++ ">"), app, I) || I <- lists:seq(1, 6000)], + ?assert(maps:get(members, mcp_store:stats()) =< 5000), + ?assertMatch({app, _}, mcp_store:member(list_to_pid("<0.1.6000>"))), + ?assertEqual(undefined, mcp_store:member(list_to_pid("<0.1.1>"))). + +wait(F) -> wait(F, 50). +wait(_, 0) -> ct:fail(never); +wait(F, N) -> case F() of true -> ok; false -> timer:sleep(50), wait(F, N - 1) end. diff --git a/images/vscode-erlang-mcp-running.png b/images/vscode-erlang-mcp-running.png new file mode 100644 index 0000000000000000000000000000000000000000..8cd9cd19e487ef5f994fc600f8286e8dbb361eaa GIT binary patch literal 675072 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 *Z{Gc@P-I++M9(3T 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

H%ocm--}?NI+e@UqH(LqQ!S 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

ZF->EOtVmovLJ$>($E_T-Af8AD^ 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 uSTHa7r^)JxSE{qa; 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

cPicX~tU$3^@2 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

=V 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⪚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 z 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"mcp.enabled", false) === true, + host: mcp.get("mcp.host", "127.0.0.1"), + port: mcp.get("mcp.port", 0), + authToken: mcp.get("mcp.authToken", "") || undefined, + trusted: workspace.isTrusted, + root: folder ? folder.uri.fsPath : (getElangConfigConfiguration().rootPath ?? "") + }; + if (settings.enabled && !debugConfiguration.noDebug) { + debugConfiguration.mcpSettings = settings; + } +} + let currentSettings: ErlangSettings = null; // "auto" matches prior (unconfigurable) behavior: a shell is only needed on diff --git a/lib/ErlangShellDebugger.ts b/lib/ErlangShellDebugger.ts index 1291e0f8..7e8be5be 100644 --- a/lib/ErlangShellDebugger.ts +++ b/lib/ErlangShellDebugger.ts @@ -2,6 +2,7 @@ import { ChildProcess, spawn } from 'child_process'; import { EventEmitter } from 'events'; import { GenericShell, ILogOutput } from './GenericShell'; import { DebugProtocol } from '@vscode/debugprotocol'; +import { ErlangMcpSettings } from './erlangSettings'; import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs'; @@ -17,6 +18,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum addEbinsToCodepath: boolean; erlangPath : string; // path of erlang if specified in configuration useShell: boolean; // resolved from erlang.useShell (see ErlangConfigurationProvider) + mcpSettings?: ErlangMcpSettings; // internal, resolved from erlang.mcp.* (see ErlangConfigurationProvider) } /** `attach` request: debug a node that is already running on this machine. */ @@ -28,6 +30,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum verbose: boolean; erlangPath : string; useShell: boolean; + mcpSettings?: ErlangMcpSettings; } // Values end up on a shell command line when this shell is spawned through @@ -127,12 +130,55 @@ export class ErlangShellForDebugging extends GenericShell { "-pa", this.shellQuote(bridgePath), "-vscode_port", listen_port.toString(), "-vscode_attach_node", args.node, - "-compiled_args_file", this.shellQuote(this.argsPrecompiledFileName), - "-s", "vscode_connection", "attach"); + "-compiled_args_file", this.shellQuote(this.argsPrecompiledFileName)); + if (args.mcpSettings?.enabled) { + // makes the helper push the MCP inspector modules to the target (only then) + processArgs.push("-vscode_mcp", "1"); + } + processArgs.push("-s", "vscode_connection", "attach"); this.started = true; return this.LaunchProcess(erlPath, startDir, processArgs, !args.verbose); } + /** + * Resolve `erlang.mcp.*` + the project's rebar.config policy in a short-lived + * helper VM (the policy reader interns atoms, so it never runs in the LSP or + * target VM). Erlang (mcp_policy) is authoritative; only its normalized, + * non-secret result comes back. + */ + public ResolveMcpPolicy(erlPath: string, cwd: string, bridgePath: string, mcp: ErlangMcpSettings): Promise<{ status: string, config?: any, message?: string }> { + return new Promise((resolve) => { + const fail = (message: string) => resolve({ status: 'error', message }); + try { + let env = process.env; + if (this.erlangPath) { + env = { ...process.env, PATH: this.erlangPath + (process.platform == 'win32' ? ";" : ":") + process.env.PATH }; + } + const child = spawn(erlPath, ["-noshell", "-pa", this.shellQuote(bridgePath), "-s", "mcp_policy", "cli"], + { cwd: cwd, shell: this.useShell, stdio: 'pipe', env }); + let out = ""; + const timer = setTimeout(() => { child.kill(); fail("MCP policy resolution timed out"); }, 20000); + child.stdout.on('data', d => { out += d.toString('utf8'); }); + child.on('error', () => { clearTimeout(timer); fail("MCP policy helper cannot be started"); }); + child.on('close', () => { + clearTimeout(timer); + const line = out.split(/\r?\n/).reverse().find(l => l.startsWith('{')); + try { + resolve(line ? JSON.parse(line) : { status: 'error', message: "MCP policy could not be resolved" }); + } catch { + fail("MCP policy could not be resolved"); + } + }); + child.stdin.end(JSON.stringify({ + enabled: mcp.enabled, host: mcp.host, port: mcp.port, trusted: mcp.trusted, + root: mcp.root, cwd: cwd + }) + "\n"); + } catch { + fail("MCP policy helper cannot be started"); + } + }); + } + /** The helper node halts when its stdin closes; the attached node keeps running. */ public EndAttach() { this.childProcess?.stdin?.end(); diff --git a/lib/GenericShell.ts b/lib/GenericShell.ts index df749f56..078cbbe2 100644 --- a/lib/GenericShell.ts +++ b/lib/GenericShell.ts @@ -103,6 +103,11 @@ export function commandLineArgs(value: string | undefined, useShell: boolean): s return useShell ? [value] : splitCommandLine(value); } +// Arguments that carry a secret (the Erlang cookie) must not reach the logs. +export function redactArgs(args: string[]): string[] { + return args.map((a, i) => (i > 0 && args[i - 1] === '-setcookie') ? '' : a); +} + export class GenericShell extends EventEmitter { protected childProcess: ChildProcess; protected logOutput: ILogOutput; @@ -182,7 +187,7 @@ export class GenericShell extends EventEmitter { if (this.erlangPath) { this.log("log",`using erlang binaries from path : '${this.erlangPath}'`); } - this.log("log", `starting : ${processName} \r\n` + args.join(" ")); + this.log("log", `starting : ${processName} \r\n` + redactArgs(args).join(" ")); } var childEnv = null; if (this.erlangPath) { diff --git a/lib/erlangDebugConnection.ts b/lib/erlangDebugConnection.ts index 63856328..11b6ca0d 100644 --- a/lib/erlangDebugConnection.ts +++ b/lib/erlangDebugConnection.ts @@ -5,7 +5,11 @@ import { FunctionBreakpoint } from './ErlangShellDebugger'; export class ErlangDebugConnection extends ErlangConnection { protected get_ErlangFiles(): string[] { - return ["gen_connection.erl", "vscode_connection.erl", "vscode_jsone.erl"]; + // mcp/* is the embedded MCP inspector: compiled here (debugger bridge beams), + // never loaded by the LSP node, only started on request for an enabled debug session + return ["gen_connection.erl", "vscode_connection.erl", "vscode_jsone.erl", "vscode_jsone_decode.erl", + "mcp/mcp_policy.erl", "mcp/mcp_encoder.erl", "mcp/mcp_audit.erl", "mcp/mcp_store.erl", + "mcp/mcp_tools.erl", "mcp/mcp_runtime.erl", "mcp/mcp_server.erl", "mcp/mcp_sup.erl"]; } protected handle_erlang_event(url: string, body : any) : void { @@ -32,6 +36,10 @@ export class ErlangDebugConnection extends ErlangConnection { break; case "/delete_break": break; + case "/mcp_call": + // journal of the MCP inspector: request metadata only (no arguments, results or token) + this.emit("mcp_call", body); + break; case "/fbp_verified": this.emit("fbp_verified", body.module, body.name, body.arity); break; @@ -171,6 +179,22 @@ export class ErlangDebugConnection extends ErlangConnection { }); } + /** Start the MCP inspector in the target; the reply (with the session token) goes to the adapter only. */ + public mcpStart(config: any, mode: string): Promise<{ ok: boolean, error?: string, host?: string, port?: number, path?: string, sessionId?: string, token?: string }> { + return this.post("mcp_start", JSON.stringify({ ...config, mode })).catch(err => { + return { ok: false, error: "the MCP inspector could not be started" }; + }); + } + + /** Lease renewal: without it the target stops the inspector within ~10 seconds. */ + public mcpRenew(): Promise { + return this.post("mcp_renew", "").then(r => !!r?.ok, () => false); + } + + public mcpStop(): Promise { + return this.post("mcp_stop", "").then(() => undefined, () => undefined); + } + public closeEventsReceiver(): void { this.events_receiver?.close(); } diff --git a/lib/erlangDebugSession.ts b/lib/erlangDebugSession.ts index fcedf01f..c70166d3 100644 --- a/lib/erlangDebugSession.ts +++ b/lib/erlangDebugSession.ts @@ -1,13 +1,14 @@ import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, ThreadEvent, StackFrame, Scope, Source, Handles - , Breakpoint, ModuleEvent, Module, ContinuedEvent, Variable, BreakpointEvent + , Breakpoint, ModuleEvent, Module, ContinuedEvent, Variable, BreakpointEvent, Event } from '@vscode/debugadapter'; import { DebugProtocol } from '@vscode/debugprotocol'; import { ErlangShellForDebugging, LaunchRequestArguments, AttachRequestArguments, FunctionBreakpoint, validateAttachArguments } from './ErlangShellDebugger'; import { ILogOutput } from './GenericShell'; import * as path from 'path'; import * as fs from 'fs'; +import * as os from 'os'; import { erlangBridgePath, setExtensionPath } from './erlangConnection'; import { ErlangDebugConnection } from './erlangDebugConnection'; @@ -20,6 +21,17 @@ interface DebugVariable { children: Variable[]; } +// Launch/attach arguments are logged in verbose mode: never print secrets. +const SECRET_KEY = /token|secret|password|cookie|authorization|credential/i; +export function sanitizeArguments(args: any): any { + const copy: any = {}; + Object.keys(args ?? {}).forEach(k => { + copy[k] = SECRET_KEY.test(k) ? '' + : (args[k] && typeof args[k] === 'object' && !Array.isArray(args[k])) ? sanitizeArguments(args[k]) : args[k]; + }); + return copy; +} + class ConditionalBreakpoint { condition: string; hitCount: number; @@ -50,6 +62,12 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { // set by an attach request: the debuggee is a running node we must not stop private _AttachArguments: AttachRequestArguments; private _port: number; + // embedded MCP inspector (debug sessions only): lease renewal + secret descriptor + private _mcpRenewTimer: NodeJS.Timeout; + private _mcpDescriptorDir: string; + private _mcpStarted = false; + private _mcpCalls = 0; + private _mcpServerMs = 0; public constructor(verbose: boolean) { super(); @@ -115,6 +133,7 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { this.erlangConnection.on("new_process", (arg) => this.onNewProcess(arg)); this.erlangConnection.on("new_status", (pid, status, reason, moduleName, line) => this.onNewStatus(pid, status, reason, moduleName, line)); this.erlangConnection.on("on_break", (pid, moduleName, line, stacktrace) => this.onBreak(pid, moduleName, line, stacktrace)); + this.erlangConnection.on("mcp_call", (meta) => this.onMcpCall(meta)); this.erlangConnection.on("fbp_verified", (moduleName, functionName, arity) => this.onFbpVerified(moduleName, functionName, arity)); response.body.supportsConfigurationDoneRequest = true; @@ -150,7 +169,7 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { } this._LaunchArguments = args; if (this._LaunchArguments.verbose) { - this.log(`debugger launchRequest arguments : ${JSON.stringify(args)}`); + this.log(`debugger launchRequest arguments : ${JSON.stringify(sanitizeArguments(args))}`); } // Based on JS output path, not TS path this.erlangConnection.Start(this._LaunchArguments.verbose, this._LaunchArguments.erlangPath).then(port => { @@ -183,7 +202,7 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { arguments: "", addEbinsToCodepath: false, noDebug: false }; if (args.verbose) { - this.log(`debugger attachRequest arguments : ${JSON.stringify(args)}`); + this.log(`debugger attachRequest arguments : ${JSON.stringify(sanitizeArguments(args))}`); } this.erlangConnection.Start(args.verbose, args.erlangPath).then(port => { this._port = port; @@ -232,6 +251,7 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void { //this.debug("disconnectRequest"); + this.stopMcp(); if (this._AttachArguments) { // Detach unless asked to stop the node: stopping a node someone // else started must be an explicit choice. @@ -254,6 +274,7 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { protected quitEvent(exitCode: number) { this.log(`erl exit with code ${exitCode}`); + this.stopMcp(false); this.quit = true; this.sendEvent(new TerminatedEvent()); this.erlDebugger.CleanupAfterStart(); @@ -606,6 +627,79 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { private onStartListening(message: string): void { if (this._LaunchArguments.verbose) this.debug(message); + this.startMcp(); + } + + /** + * Start the embedded MCP inspector in the debugged node when (and only when) + * this is a real debug session and erlang.mcp.enabled is true for the folder. + * Every failure is non-fatal: it is reported and debugging goes on. + * The session token never travels in a DAP event: the extension host reads it + * from a 0600 descriptor that it deletes right away. + */ + private async startMcp(): Promise { + const mcp = this._AttachArguments?.mcpSettings ?? this._LaunchArguments?.mcpSettings; + if (!mcp?.enabled || this._LaunchArguments.noDebug || this._mcpStarted) { + return; + } + this._mcpStarted = true; + try { + const bridgeBinPath = path.normalize(path.join(erlangBridgePath, "..", "ebin")); + const policy = await this.erlDebugger.ResolveMcpPolicy(this._LaunchArguments.erlpath, this._LaunchArguments.cwd, bridgeBinPath, mcp); + if (policy.status !== 'ok') { + if (policy.status === 'error') { + this.mcpFailed(policy.message); + } + return; + } + const mode = this._AttachArguments ? 'attach' : 'launch'; + const started = await this.erlangConnection.mcpStart({ ...policy.config, ...(mcp.authToken ? { authToken: mcp.authToken } : {}) }, mode); + if (!started.ok) { + this.mcpFailed(started.error); + return; + } + const url = `http://${started.host!.includes(':') ? '[' + started.host + ']' : started.host}:${started.port}${started.path}`; + this._mcpDescriptorDir = fs.mkdtempSync(path.join(os.tmpdir(), 'erlang-mcp-')); + const descriptor = path.join(this._mcpDescriptorDir, 'endpoint.json'); + fs.writeFileSync(descriptor, JSON.stringify({ url: url, token: started.token, sessionId: started.sessionId }), { mode: 0o600 }); + this._mcpRenewTimer = setInterval(() => { + this.erlangConnection.mcpRenew(); + }, 2000); + this.sendEvent(new Event('erlangMcp', { status: 'started', url: url, sessionId: started.sessionId, descriptor: descriptor })); + this.log(`MCP inspector listening on ${url} (session ${started.sessionId}); the credential is delivered to VS Code only`); + } catch (e) { + this.mcpFailed("the MCP inspector could not be started"); + } + } + + /** One MCP request handled by the inspector: forwarded to the extension host's "Erlang MCP" channel. */ + private onMcpCall(meta: any): void { + this._mcpCalls++; + this._mcpServerMs += typeof meta?.durationMs === 'number' ? meta.durationMs : 0; + this.sendEvent(new Event('erlangMcpCall', meta)); + } + + private mcpFailed(message: string): void { + this.log(`MCP is disabled for this debug session: ${message}`); + this.sendEvent(new Event('erlangMcp', { status: 'error', message: message })); + this.stopMcp(true); + } + + private stopMcp(notifyTarget: boolean = true): void { + if (this._mcpRenewTimer) { + clearInterval(this._mcpRenewTimer); + this._mcpRenewTimer = undefined; + } + if (this._mcpDescriptorDir) { + try { fs.rmSync(this._mcpDescriptorDir, { recursive: true, force: true }); } catch { /* best effort */ } + this._mcpDescriptorDir = undefined; + } + if (this._mcpStarted) { + if (notifyTarget && this.erlangConnection?.isConnected) { + this.erlangConnection.mcpStop(); + } + this.sendEvent(new Event('erlangMcp', { status: 'stopped', calls: this._mcpCalls, serverMs: this._mcpServerMs })); + } } private onNewModule(moduleName: string): void { @@ -741,7 +835,8 @@ export class ErlangDebugSession extends DebugSession implements ILogOutput { setTimeout(function () { var currentThread = that.threadIDs[processName]; delete that.threadIDs[processName]; - if (currentThread.vscode) { + // an exit can be reported for a process never announced (or already removed) + if (currentThread && currentThread.vscode) { that.sendEvent(new ThreadEvent("exited", currentThread.thid)); } var thCount = that.threadCount(); diff --git a/lib/erlangSettings.ts b/lib/erlangSettings.ts index fc93df7c..41a8ee76 100644 --- a/lib/erlangSettings.ts +++ b/lib/erlangSettings.ts @@ -1,3 +1,19 @@ +/** + * Internal, folder-resolved MCP settings handed to the debug adapter (never a + * user-facing launch/attach block). Validation is authoritative in Erlang (mcp_policy). + */ +export interface ErlangMcpSettings { + enabled: boolean; + host: string; + port: number; + /** optional fixed development token (erlang.mcp.authToken); validated in Erlang */ + authToken?: string; + /** Workspace Trust status of the folder */ + trusted: boolean; + /** selected workspace folder; the project policy is never read outside it */ + root: string; +} + export interface ErlangSettings { erlangPath : string; erlangArgs : string[]; diff --git a/lib/extension.ts b/lib/extension.ts index fe869551..a8ea4c63 100644 --- a/lib/extension.ts +++ b/lib/extension.ts @@ -1,9 +1,12 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; import { workspace as Workspace, window as Window, ExtensionContext, TextDocument, OutputChannel, WorkspaceFolder, Uri, debug, - languages, IndentAction, DebugAdapterDescriptorFactory + languages, IndentAction, DebugAdapterDescriptorFactory, commands, lm, EventEmitter, env, + McpHttpServerDefinition, DebugSession } from 'vscode'; import * as Adapter from './vscodeAdapter'; @@ -75,6 +78,7 @@ export function activate(context: ExtensionContext) { } disposables.forEach((disposable => context.subscriptions.push(disposable))); LspClient.activate(context); + activateMcpIntegration(context); languages.setLanguageConfiguration("erlang", { onEnterRules: [ @@ -184,6 +188,165 @@ export function activate(context: ExtensionContext) { }); } +// ---- Embedded MCP inspector: thin VS Code hooks (all MCP logic is Erlang) ---------------------------- +// The debug adapter announces the live endpoint with a custom DAP event (no secret in it) and leaves +// the session token in a 0600 descriptor that is read and deleted immediately; the token then lives +// in this process' memory only, and is handed to VS Code through the MCP provider API. +interface McpSession { label: string; url: string; sessionId: string; token: string; } +const mcpSessions = new Map(); + +// "Erlang MCP" output channel: one line per MCP request (metadata only), to see how many calls an +// agent makes and whether the time goes to the inspector (ms) or to the agent (+delta between calls). +let mcpChannel: OutputChannel | undefined; +const mcpJournal = new Map(); + +function mcpLog(line: string) { + if (!mcpChannel) { + mcpChannel = Window.createOutputChannel('Erlang MCP'); + } + mcpChannel.appendLine(line); +} + +function clock(t: number): string { + return new Date(t).toISOString().substring(11, 23); +} + +function seconds(ms: number): string { + return ms < 1000 ? `${ms} ms` : `${(ms / 1000).toFixed(1)} s`; +} + +function formatMcpCall(m: any, sinceLast: number | undefined): string { + const parts = [`#${m.seq ?? '?'}`, m.method === 'tools/call' ? `tools/call ${m.tool}` : `${m.method}`, `${m.status}`]; + if (typeof m.durationMs === 'number') { parts.push(`${m.durationMs} ms`); } + if (typeof m.bytes === 'number') { parts.push(m.bytes >= 1024 ? `${(m.bytes / 1024).toFixed(1)} KB` : `${m.bytes} B`); } + if (typeof m.entities === 'number') { parts.push(`ents ${m.entities} rels ${m.relationships}`); } + if (typeof m.offset === 'number') { parts.push(`page@${m.offset}${m.cursor ? ' (cursor)' : ''}${m.more ? ' +more' : ''}`); } + if (m.omissions) { parts.push(`omissions ${m.omissions}`); } + if (m.complete === false) { parts.push('partial'); } + if (sinceLast !== undefined) { parts.push(`(+${seconds(sinceLast)} since previous)`); } + return parts.join(' '); +} + +function activateMcpIntegration(context: ExtensionContext) { + removeStaleMcpDescriptors(); + const changed = new EventEmitter(); + context.subscriptions.push(changed); + + context.subscriptions.push(debug.onDidReceiveDebugSessionCustomEvent(e => { + if (e.event !== 'erlangMcp' && e.event !== 'erlangMcpCall') { + return; + } + if (e.event === 'erlangMcpCall') { + const journal = mcpJournal.get(e.session.id) ?? { start: Date.now() }; + const now = Date.now(); + mcpLog(`${clock(now)} ${formatMcpCall(e.body ?? {}, journal.last === undefined ? undefined : now - journal.last)}`); + journal.last = now; + mcpJournal.set(e.session.id, journal); + return; + } + const body = e.body ?? {}; + if (body.status === 'started') { + mcpJournal.set(e.session.id, { start: Date.now() }); + mcpLog(`── ${clock(Date.now())} session "${e.session.name}": ${body.url}`); + const session = readMcpDescriptor(body.descriptor, e.session); + if (session) { + mcpSessions.set(e.session.id, session); + } else { + Window.showWarningMessage('Erlang MCP: the endpoint credential could not be read; MCP is unavailable for this debug session.'); + } + } else if (body.status === 'error') { + Window.showWarningMessage(`Erlang MCP is disabled for this debug session: ${body.message}`); + } else if (body.status === 'stopped') { + mcpSessions.delete(e.session.id); + const journal = mcpJournal.get(e.session.id); + if (journal) { + mcpLog(`── ${clock(Date.now())} end of "${e.session.name}": ${body.calls ?? 0} calls, ` + + `${seconds(body.serverMs ?? 0)} in the inspector, ${seconds(Date.now() - journal.start)} of session`); + mcpJournal.delete(e.session.id); + } + } + changed.fire(); + })); + context.subscriptions.push(debug.onDidTerminateDebugSession(s => { + if (mcpSessions.delete(s.id)) { + changed.fire(); + } + })); + + // dynamic registration: needs VS Code with the MCP provider API (>= 1.101) + if (typeof lm?.registerMcpServerDefinitionProvider === 'function') { + context.subscriptions.push(lm.registerMcpServerDefinitionProvider('erlang.mcp', { + onDidChangeMcpServerDefinitions: changed.event, + provideMcpServerDefinitions: async () => + [...mcpSessions.values()].map(s => new McpHttpServerDefinition(s.label, Uri.parse(s.url))), + resolveMcpServerDefinition: async (server) => { + const session = [...mcpSessions.values()].find(s => s.label === server.label); + if (!session || !(server instanceof McpHttpServerDefinition)) { + return undefined; + } + server.headers = { Authorization: `Bearer ${session.token}` }; + return server; + } + })); + } + + context.subscriptions.push(commands.registerCommand('erlang.mcp.showConnectionDetails', async () => { + const sessions = [...mcpSessions.entries()]; + if (sessions.length === 0) { + Window.showInformationMessage('Erlang MCP: no debug session with an MCP inspector is running (enable erlang.mcp.enabled and start a debug session).'); + return; + } + let picked = sessions[0][1]; + if (sessions.length > 1) { + const item = await Window.showQuickPick(sessions.map(([, s]) => ({ label: s.label, description: s.url, session: s })), + { placeHolder: 'Select the debug session' }); + if (!item) { + return; + } + picked = item.session; + } + // The token is never shown or copied automatically: only on an explicit choice. + const choice = await Window.showInformationMessage( + `Erlang MCP (${picked.label}): ${picked.url}\nVS Code clients are registered automatically. For a static client, ` + + `use this URL with a fixed erlang.mcp.port and an "Authorization: Bearer " header (the token changes at every debug session).`, + 'Copy URL', 'Copy Authorization header'); + if (choice === 'Copy URL') { + await env.clipboard.writeText(picked.url); + } else if (choice === 'Copy Authorization header') { + await env.clipboard.writeText(`Authorization: Bearer ${picked.token}`); + } + })); +} + +function readMcpDescriptor(descriptor: string, session: DebugSession): McpSession | undefined { + try { + if (!descriptor || !path.basename(path.dirname(descriptor)).startsWith('erlang-mcp-')) { + return undefined; + } + const data = JSON.parse(fs.readFileSync(descriptor, 'utf8')); + return { label: `Erlang debug: ${session.name} (${session.id.substring(0, 8)})`, url: data.url, sessionId: data.sessionId, token: data.token }; + } catch { + return undefined; + } finally { + try { fs.rmSync(path.dirname(descriptor), { recursive: true, force: true }); } catch { /* best effort */ } + } +} + +/** Descriptors are deleted right after being read; clean up those left by a crash. */ +function removeStaleMcpDescriptors() { + try { + const tmp = os.tmpdir(); + for (const name of fs.readdirSync(tmp)) { + if (name.startsWith('erlang-mcp-')) { + const dir = path.join(tmp, name); + if (Date.now() - fs.statSync(dir).mtimeMs > 60000) { + fs.rmSync(dir, { recursive: true, force: true }); + } + } + } + } catch { /* best effort */ } +} + export function deactivate(): Thenable { return LspClient.deactivate(); } diff --git a/mcp-tasks.md b/mcp-tasks.md new file mode 100644 index 00000000..14552031 --- /dev/null +++ b/mcp-tasks.md @@ -0,0 +1,623 @@ +# V1: Embedded MCP OTP Topology Inspector for Erlang Debug Sessions + +## Objective + +Add a feature to the extension's DAP debugger: when the Erlang extension/LSP setting `erlang.mcp.enabled` is explicitly enabled and an actual debug session starts, the target Erlang node starts an embedded Model Context Protocol (MCP) server. Configuration belongs to the existing Erlang settings layer; the DAP debugger owns the runtime lifecycle. Enabling the setting alone must not start a server in the LSP node. + +The V1 goal is to let an agent discover, reconstruct, and explain the OTP topology of a selected application without knowing supervisor or process names in advance. Map applications, supervisors, processes, modules, and approved ETS metadata, with explicit relationships, evidence, and coverage limitations. + +This is a structural map, not an observation of message exchanges or business workflows. Mailbox inspection is limited to queue length; ETS inspection is limited to metadata. No agent framework or model-specific runtime is required. + +This is not a general administration API and must never be active in a normal LSP session or in a non-debug application launch. + +## Expected Behavior + +- The DAP debugger starts or attaches to an Erlang node through the existing debug flow. +- The existing Erlang configuration layer resolves MCP settings for the debug session's workspace folder and passes a validated internal configuration to the debugger. Do not add a user-facing `mcp` block to launch/attach configuration. +- The MCP runtime inspector starts only when both conditions are true: + - The session is an actual debug session. + - The folder-resolved `erlang.mcp.enabled` setting is explicitly true. +- The MCP server runs inside the application/node being debugged, under a dedicated OTP supervisor. +- When the debug session ends, disconnects, or fails, the MCP server is stopped and its resources are released. +- If MCP configuration, startup, or client registration fails, disable/clean up the inspector, report a sanitized actionable error, and keep ordinary debugging usable. There is no `required` option in V1. +- Normal LSP, DAP, compilation, indexing, and debug behavior must remain unchanged when the feature is disabled. + +## Proposed Configuration + +### Erlang Extension/LSP Settings + +Use the existing VS Code `erlang` settings namespace, not a new configuration service. Resolve settings with the debug workspace folder URI, not a global cache or the first workspace folder in a multi-root workspace. + +```json +{ + "erlang.mcp.enabled": true, + "erlang.mcp.host": "127.0.0.1", + "erlang.mcp.port": 0 +} +``` + +`erlang.mcp.enabled` defaults to `false`. The example explicitly opts in. Changes apply to new sessions; changing the setting must not silently expand permissions or rebind an active inspector. Stopping the debug session revokes its credentials. + +### Project Policy in rebar.config + +Add an optional top-level extension-owned term to the target project's [rebar.config](rebar.config). This is metadata read by the extension's MCP integration, not a rebar plugin or an instruction for rebar to start a server. Builds/tests run outside the debugger must never activate MCP. + +```erlang +{mcp, [ + {allowed_tools, [ + <<"runtime_summary">>, + <<"application_overview">>, + <<"supervision_tree">>, + <<"process_info">>, + <<"registered_processes">>, + <<"ets_tables">>, + <<"debug_session">> + ]}, + {allowed_ets_tables, []}, + {limits, [ + {max_request_bytes, 16384}, + {max_result_bytes, 262144}, + {max_items, 500}, + {max_depth, 16}, + {max_binary_bytes, 4096}, + {max_traversal_depth, 8}, + {max_concurrency, 2}, + {request_timeout_ms, 3000}, + {collection_ttl_ms, 30000}, + {max_collection_bytes, 1048576}, + {max_collections, 2} + ]} +]}. +``` + +The policy example shows the defaults. For an approved named table, use a binary name such as `<<"orders_index">>` in `allowed_ets_tables`. Metadata access remains opt-in and excludes private tables. + +- Resolve the nearest target-project rebar configuration from the resolved debug `cwd`, within the selected trusted workspace folder. For an umbrella, use its enclosing project file when the application has none. Do not walk outside that folder, consult the extension's own build configuration instead, or fetch a remote target's files implicitly. +- If no project file or no `mcp` term exists, use the built-in policy. A malformed file or invalid/duplicate MCP keys disables MCP for that session with an actionable error; it does not abort ordinary debugging or fall back to a more permissive policy. +- V1 reads only the literal top-level term: no `rebar.config.script` execution, profile merging, includes, or evaluation. Detect an `mcp` policy placed only in a profile and report it as unsupported instead of silently ignoring it. +- Use a structured Erlang-term reader in a bounded helper outside the long-lived LSP/target VM, or a reviewed parser that does not intern arbitrary input as atoms. Existing `file:consult` usage is a starting point, not permission to parse client input or unbounded project files in the target. Apply Workspace Trust, input-size, parse-time, and output limits; return only normalized MCP policy, never other project terms. +- Normalize snake_case project keys into the internal camelCase contract once. Configuration ownership is disjoint: VS Code controls enablement/host/port; rebar controls tool/table allowlists and lower resource limits. Reject `enabled`, `required`, `host`, `port`, transport, and credentials inside the project term. Neither source can relax built-in security ceilings. +- Keep defaults and schemas in MCP-owned helpers. Projects without rebar remain usable with built-in defaults; do not create a project file automatically. + +### Validation and Defaults + +- `erlang.mcp.enabled` is the only user-facing activation switch. A user-authored launch/attach `mcp` block is unsupported and cannot override it. +- `required` is removed. All MCP failures are non-fatal to debugging, but must be visible and must leave no unauthenticated or partially initialized inspector running. +- V1 uses only `streamable_http`; do not expose an unused transport selector. +- `host` defaults to `127.0.0.1` and must only accept loopback addresses in this feature version. +- `port: 0` requests an ephemeral OS-assigned port and should be the default. +- Reject wildcard, LAN, container-wide, and public bindings such as `0.0.0.0` and `::`. +- Generate a new bearer token from at least 32 cryptographically random bytes per debug session. Do not accept an `authToken` configuration field or reuse an environment-provided token in V1. +- Internal `allowedTools` defaults to the seven tools in the rebar example; an explicit empty list enables none. +- Internal `allowedETSTables` defaults to an empty list. Accept exact local named-table names, resolved without creating atoms. Private tables remain excluded even if listed. +- The example's limits are V1 defaults and hard ceilings. Overrides must be positive integers no greater than the corresponding ceiling. Validate relationships between limits so error and result envelopes fit; reject unusable combinations rather than exceeding a limit. `maxItems` counts entities and relationships together per page, and ordinary list entries. `maxResultBytes` includes the serialized MCP response envelope. +- Validate all fields, ranges, tool names, and limits before starting the server. +- Unknown MCP configuration keys should produce a useful warning or validation error according to existing project conventions. +- Extend the existing Erlang settings schema and documentation; pass normalized configuration internally to the adapter without exposing duplicate user-facing debug options. + +## Scope for Version 1 + +### Read-Only Tools + +Implement exactly the seven tools below, with discoverable input/output schemas and descriptions. Do not expose prompts or resources in V1. + +#### `application_overview` + +- Discover started OTP applications without a required application, supervisor, or PID argument. +- Return names, versions, declared dependencies, and discoverable root supervisors with reusable entity IDs. Declared dependencies are not evidence of runtime communication. +- Support application selection by discovered ID and pagination. Preserve applications with no discoverable root and explain unavailable metadata. +- Expose safe module names and declared OTP behaviours where available, without invoking application callbacks, loading code, returning compile paths, or reading application environment values. + +#### `runtime_summary` + +Return bounded runtime metadata: + +- Erlang/OTP and ERTS versions. +- Node name, with an option to redact it if it contains host information. +- Uptime. +- Scheduler count. +- Process count. +- Memory summary. +- Current debug session identifier, using a non-secret opaque value. + +#### `registered_processes` + +Return a bounded list of locally registered process names and safe summary metadata. + +Do not enumerate every process by default. Support application filtering and continuation; mark membership as confirmed, inferred, or unknown. Do not silently assign unrelated registered processes to the selected application. + +#### `process_info` + +Accept a process entity ID, registered name, or validated PID belonging to the authorized local debug node. Return only an explicit allowlist of fields: + +- Registered name. +- Status. +- Current function. +- Initial call. +- Reductions. +- Memory. +- Message queue length. +- Links and monitors as bounded identifiers. +- Application membership with evidence and confidence. +- Callback module and OTP behaviour when available from safe metadata, otherwise an explicit unknown value. Do not read process dictionaries, call `sys:get_state`, or execute application callbacks to infer them. + +Do not return process dictionaries, heap fragments, stack contents, full mailbox messages, or arbitrary process state in version 1. + +#### `supervision_tree` + +Traverse from an application or supervisor entity ID discovered by `application_overview`, or an explicitly supplied local supervisor. Use OTP supervisor APIs where possible. Bound depth, items, bytes, and execution time; detect loops and stale PIDs. + +Return typed parent-child edges, safe child identifiers, worker/supervisor kind, module metadata, and available restart/shutdown metadata. Never return child start arguments. Preserve restarting or absent child entries without inventing live PIDs. + +Support progressive subtree expansion and continuation. An unresponsive supervisor produces a partial result, not a blocked map. Use `timeout` unless debugger evidence specifically establishes `unavailable_while_paused`; never resume or alter application processes to inspect them. + +#### `ets_tables` + +Return metadata only for named tables explicitly approved in `allowedETSTables`: name, protection, type, owner entity ID, size, and memory with its unit. Check approved names directly instead of dumping a node-wide table inventory. Exclude private tables even if listed. + +Return ownership edges joinable to process entities. Never read keys, objects, or values; `ets_lookup` is deferred beyond V1. + +#### `debug_session` + +Return safe debugger metadata such as launch versus attach, node connection status, interpreted modules, and active breakpoint locations if already available from the existing debug subsystem. + +Do not expose source contents, environment values, command-line secrets, cookies, or credentials. + +### Mapping Contract + +Mapping tools share a versioned structured contract. Prose may supplement it but cannot replace it. + +| ID | Requirement | +| --- | --- | +| MAP-01 | Discover applications and available roots without preconfigured process names. Distinguish the selected application, declared dependencies, and other node entities. | +| MAP-02 | Entity kinds are `application`, `process`, `module`, and `ets_table`. A supervisor is a process with role `supervisor`, not a duplicate entity. Opaque IDs are reusable across tools and collections in the live session, invalid across sessions/node incarnations, and different after process restart or table recreation. Bound identity bookkeeping and explicitly expire handles when identity cannot be retained safely. | +| MAP-03 | Typed edges are `depends_on`, `supervises`, `owns_table`, `linked_to`, `monitors`, `belongs_to`, and `uses_module`. Include evidence, observation time, and confidence (`confirmed`, `inferred`, `unknown`). Links/monitors do not imply supervision or message traffic. `uses_module` reflects callback/child metadata, not observed calls. | +| MAP-04 | Coverage is relative to the requested scope, not all runtime processes. Report limitations for unsupervised, unregistered, transient, and unattributed processes. A name or link alone cannot confirm application membership. | +| MAP-05 | Include `schemaVersion`, `sessionId`, `collectionId`, `startedAt`, `finishedAt`, `scope`, `entities`, `relationships`, `complete`, `truncated`, `omissions`, and `nextCursor`. Each call/subtree expansion identifies its collection. A collection is a bounded observation interval, never an atomic snapshot. | +| MAP-06 | Opaque cursors are bound to session, collection, tool, and arguments. Serve pages from bounded retained observations, not moving live offsets. Return `cursor_expired` after retention expires; never silently restart traversal. Explain non-resumable omissions. | +| MAP-07 | Expose safe module/behaviour names and available debugger locations for correlation through separate workspace/LSP tools. Explicitly report unavailable metadata. Do not load modules or return source contents through MCP. | + +`omissions` distinguishes `policy_denied`, `disappeared`, `timeout`, `unavailable_while_paused`, `limit_reached`, and `unknown_membership` without disclosing forbidden identifiers. Edge endpoints outside the current page need safe references or an unresolved status, never invented entities. + +Apply collection TTL, count, and retained-byte budgets per session. When acquisition exceeds a budget, return a partial map with a non-resumable reason and suggest a narrower scope. Pagination bounds output, not necessarily the cost of OTP APIs that materialize lists; review that cost explicitly. + +Agents may compare collections using entity IDs, but absence from a partial collection is not proof of termination. V1 needs no historical database or server-side diff tool. + +### Agent Exploration Workflow + +1. Discover tool schemas; call `runtime_summary` and `application_overview`. +2. Select an application, follow its root IDs, and expand `supervision_tree` progressively. +3. Enrich selected processes with `process_info`, registered names, approved ETS ownership, and debugger metadata. +4. Join IDs and typed edges; separate confirmed relationships from inferences and unresolved references. +5. Correlate modules through separately authorized workspace/LSP access when available. Topology reconstruction must work without source access. +6. Explain the topology, observation interval, incomplete regions, and uncertainty. Do not infer business flows or actual message traffic from structural edges. + +### Explicitly Out of Scope for Version 1 + +Do not implement any of the following: + +- Arbitrary Erlang expression evaluation. +- `rpc:call` with client-controlled module, function, or arguments. +- Shell command execution. +- Dynamically loading or purging code. +- Sending arbitrary messages to processes. +- Changing `sys` state. +- Killing, suspending, or restarting processes. +- Setting or removing breakpoints through MCP. +- Writing to ETS. +- Reading ETS keys or values, including exact-key lookups. +- Dumping full ETS tables. +- Tracing messages, function calls, or business flows. +- Traversing distributed topology on other nodes. +- Returning full process mailboxes. +- Returning complete `sys:get_state/1` values. +- Accessing arbitrary files, source files, environment variables, application secrets, Erlang cookies, or OS credentials. +- Proxying arbitrary LSP or DAP requests. + +If existing helper APIs make these actions easy, they must still not be exposed. + +## Architecture + +Implement the feature using OTP conventions and the repository's existing application structure. + +The MCP implementation is entirely Erlang: HTTP transport, protocol handling, authentication, tool schemas and dispatch, project-policy parsing/validation, runtime inspection, encoding, limits, and supervision. Do not add a production TypeScript MCP SDK, server, policy engine, or duplicate implementation. TypeScript changes are limited to existing VS Code settings, debugger bootstrap/event forwarding, and client-registration/UI integration; they do not process MCP tool requests. An external MCP client used only for interoperability tests is not part of the server implementation. + +### File Isolation and Naming + +- Put all new Erlang MCP production files under `apps/erlangbridge/src/mcp/`, with filenames and module names starting with `mcp_`: `mcp_sup.erl`, `mcp_server.erl`, `mcp_policy.erl`, `mcp_runtime.erl`, `mcp_encoder.erl`, and `mcp_audit.erl` as needed. Subdirectories do not namespace Erlang modules; verify name collisions, including dependencies. +- Do not create a dedicated TypeScript MCP directory or helper subsystem. Keep thin VS Code integration hooks in the existing settings, extension, and debugger files; place MCP implementation helpers in Erlang under `apps/erlangbridge/src/mcp/`. +- New MCP tests, fixtures, schemas, and auxiliary files must also use the `mcp_` prefix and live in dedicated MCP subfolders under the existing appropriate test/source directories. A mandatory framework basename is an exception only when documented. Keep test code out of production `src`. +- Existing integration files retain their names and contain only necessary hooks. Do not rename shared files such as `package.json`, `rebar.config`, or existing settings/debugger modules. Keep documentation in existing docs; new MCP-only documentation uses the same prefix. +- Verify rebar's recursive source discovery for the supported toolchain, target-side compilation from `src/mcp`, beam/dependency packaging, and launch/attach loading explicitly. Do not assume relocating a file makes the debugger compile/load it. Never load MCP automatically through the LSP startup module list. +- Prefix rules apply to project-owned additions, not upstream dependency files; do not rename or vendor dependencies merely to satisfy naming. + +Suggested components (combine helpers where appropriate): + +- `mcp_sup`: dedicated supervisor, started only for an enabled debug session. +- `mcp_server`: transport lifecycle and MCP request dispatch. +- `mcp_policy`: tool allowlist, authorization, validation, redaction, and limits. +- `mcp_runtime`: safe wrappers around Erlang runtime and OTP introspection. +- `mcp_encoder`: bounded Erlang-term to MCP-content conversion. +- `mcp_audit`: structured security and lifecycle events without sensitive payloads. + +Use names matching the existing codebase. + +The implementation must: + +- Keep the MCP supervision subtree isolated from the LSP and debugger core. +- Avoid blocking DAP or LSP handlers. +- Enforce request timeouts. +- Run inspection in monitored, bounded workers. Stop inspector-owned workers after timeout and discard late replies; ignoring results alone is insufficient. Never kill application processes. Document non-interruptible runtime operations and their residual resource risk. +- Enforce the shared mapping contract and bounded collection/identity retention. +- Bound concurrency and reject excess work with a clear MCP error. +- Handle dead PIDs, disappearing ETS tables, supervisor restarts, node disconnects, and malformed terms. +- Avoid atom creation from any client-controlled string. +- Avoid unsafe term decoding, especially `binary_to_term/1` on untrusted input. +- Avoid logging authorization tokens or returned runtime contents. +- Avoid converting arbitrary binaries to JSON strings without validation and limits. +- Expose MCP protocol errors without leaking Erlang stack traces or internal paths. + +## Transport and Endpoint Discovery + +Use a transport supported by the MCP library already present in the repository. If none exists, select a maintained Erlang-compatible implementation only after reviewing its license, dependency footprint, protocol support, and security posture. Avoid adding a custom partial MCP implementation unless repository constraints require it. + +For a local HTTP-based transport: + +- Bind only to an explicit loopback address. +- Default to an ephemeral port. +- Require the freshly generated per-session bearer token on every request. +- Compare tokens in a timing-resistant manner where practical. +- Validate `Origin` and `Host` for HTTP requests. +- Do not enable CORS. +- Do not provide a browser-oriented administrative UI. +- Cap request body size. +- Reject unsupported content types and methods. +- Apply connection, request, and idle timeouts. + +Publish endpoint metadata through the DAP event mechanism. Deliver credentials only through an explicitly secret-handling debugger/client integration, never ordinary output events. Define this integration before implementation. Sanitize existing verbose launch/attach argument logging and protocol tracing as well as new logs. Never write tokens into normal logs, telemetry, crash reports, workspace files, or source control. + +If the client requires a connection descriptor, create it with restrictive filesystem permissions, remove it at session end, and store it only in an OS-appropriate temporary location. Prefer passing secret material directly through the debugger integration rather than writing it to disk. + +### Connecting an AI MCP Client + +`port: 0` means the OS assigns the port when binding. The adapter must receive the actual bound address, port, MCP path, and session identity after readiness; never publish port zero as a usable endpoint. A URL in a debug log alone is not a complete client integration. + +1. For clients supporting dynamic server registration, the extension registers the live endpoint and session credential through the verified client API, then unregisters it on teardown. The user selects the debug session's server rather than guessing a port. Verify the VS Code MCP provider API and minimum VS Code version in T02; do not assume every client supports it. +2. Provide an `Erlang MCP: Show Connection Details` command through a thin hook in the existing extension integration, displaying connection metadata supplied by Erlang for live sessions. In multi-session workspaces, require session selection. Show the real URL and supported client setup route without revealing tokens in ordinary output, telemetry, or clipboard automatically. +3. For static clients, users can set `erlang.mcp.port` to a fixed local port and configure that URL. They must still supply a fresh per-session token using a supported secure credential input/export flow. A fixed port does not make the URL alone sufficient, and an old token must not reconnect after restart. +4. If a fixed port is occupied, disable MCP and report the conflict while debugging continues. Do not silently choose a different port for a statically configured client. Concurrent sessions need separate ports or dynamic registration. + +T02 must name at least one actually supported client and specify its complete endpoint-plus-credential setup, including any required user action and secure fallback. If no secure setup route exists, report a blocker rather than claiming automatic discovery or disabling authentication. Do not introduce an always-on proxy solely to hide ephemeral ports. + +## Security Model and Threat Analysis + +Localhost-only binding reduces network exposure, but it is not an authorization boundary. Other processes running as the same user, compromised development tools, browser-origin attacks against local services, malicious MCP clients, and accidentally forwarded ports may still reach the server. + +Treat all MCP requests, parameters, and client-supplied identifiers as untrusted. + +Required controls: + +- **Secure default:** disabled unless explicitly enabled for a debug session. +- **Loopback enforcement:** reject every non-loopback bind address, including values obtained after hostname resolution. +- **Per-session authentication:** use a fresh, high-entropy token and invalidate it when the debug session ends. +- **Least privilege:** expose only the configured read-only tool allowlist. +- **No arbitrary execution:** no eval, RPC trampoline, shell, module loading, or client-controlled function calls. +- **Data minimization:** return summaries rather than raw state and redact likely secrets. +- **Resource limits:** cap request size, response size, item count, nesting depth, binary size, traversal depth, concurrency, and execution time. +- **Safe identifiers:** resolve registered names through existing atoms only. Never call `list_to_atom/1`, `binary_to_atom/1`, or equivalent on input. +- **Safe serialization:** handle PIDs, ports, references, functions, improper lists, maps with non-string keys, large binaries, and cyclic-looking graph traversal without crashing or producing unbounded output. +- **No secret logging:** log only lifecycle, tool name, result status, duration, and bounded metadata. Do not log parameters or results by default. +- **Session isolation:** bind tokens, IDs, cursors, and workers to one session and target node. V1 authorizes node-level metadata inspection; selecting an application is a navigation filter, not a security boundary between applications in the same VM. Never traverse remote PIDs. Concurrent sessions must not share inspector state or credentials. +- **Clean shutdown:** stop listeners, invalidate tokens, cancel workers, and release retained data on controlled termination. A target-side session-owner monitor and renewable lease must stop inspection within 10 seconds without renewal while the VM is schedulable. Renewal must not depend on application processes stopped at breakpoints. Never terminate an attached application merely to stop its inspector. Remove temporary descriptors normally and clean stale descriptors on the next extension startup; immediate file deletion after an OS crash is not guaranteed. +- **No telemetry payloads:** runtime inspection results and authentication material must not enter telemetry. +- **Dependency review:** pin and review any new MCP or HTTP dependency according to project policy. + +Document residual risk: application names, child identifiers, module names, and table names can reveal sensitive information. Metadata-only access does not guarantee zero overhead, atomic observations, or isolation from other code in the same VM. The exclusions and allowlists reduce risk but do not remove it completely. + +## Execution Instructions for Luna or Sonnet + +This is an implementation work order, not a claim that the feature exists. Execute the tasks below in dependency order. Keep the specification in English to match the repository; report progress in the user's language. + +- Read repository instructions and inspect the current working tree before editing. Preserve unrelated changes. Do not commit or create branches unless requested. +- Work on one task at a time. Inspect the owning code, state the expected behavior, add the smallest useful change, and immediately run a focused check. Keep new modules small, but do not create one module per checkbox mechanically. +- Each task has dependencies, starting surfaces, work, and a completion gate. Starting surfaces are navigation hints, not permission to edit all listed files. Verify actual APIs and ownership before choosing new module names. +- Reuse existing test helpers and suites. Add a fixture or suite only where no suitable home exists. Do not refactor vendored JSON/formatting code. +- Record baseline failures separately. Never mark a task complete on an unexecuted check, a mock-only substitute for a required integration test, or an unsupported assumption. +- Update the checkboxes and append a short evidence entry under Implementation Progress after each completed task: changed files, exact commands, results, and remaining risks. Leave blocked tasks unchecked and identify the decision needed. +- Stop for a decision if no maintained transport supports the required OTP range, secure client credential delivery is unavailable, or the current attach topology cannot satisfy local-only access. Do not silently raise the OTP floor, forward a port, implement partial MCP, or weaken authorization. +- Do not delegate, broaden V1, add cloud services, or introduce an agent framework unless separately authorized. All task checks are deterministic; no paid model invocation is required. + +## Implementation Tasks + +### T01: Verify Integration Points and Baseline + +Dependencies: none. + +Starting surfaces: [lib/erlangDebugSession.ts](lib/erlangDebugSession.ts), [lib/ErlangShellDebugger.ts](lib/ErlangShellDebugger.ts), [lib/erlangConnection.ts](lib/erlangConnection.ts), [lib/ErlangConfigurationProvider.ts](lib/ErlangConfigurationProvider.ts), [package.json](package.json), [rebar.config](rebar.config), existing Erlang bridge and test directories. + +- [x] Trace launch, attach, `noDebug`, configuration validation, target readiness, disconnect, target loss, and adapter shutdown. Identify which process owns each resource. +- [x] Inspect target bridge compilation/loading, including its separation from the LSP beam directory and discovery of `src/mcp/mcp_*.erl`. Verify all new owned MCP files follow the isolation/naming rules. Inspect logging, tracing, telemetry if present, and all paths that serialize debug arguments. +- [x] Record supported/tested OTP, Node, and VS Code versions from configuration, docs, and CI. Distinguish claimed compatibility from tested compatibility. +- [x] Run the current TypeScript compile and relevant existing debugger/bridge tests; record pre-existing failures and environment prerequisites. + +Completion gate: append a concise verified integration note to this document with symbols, paths, lifecycle diagram or sequence, baseline commands, and version constraints. Do not invent an MCP dependency or a secret-delivery API. + +### T02: Resolve Transport and Client Integration + +Dependencies: T01. + +Starting surfaces: dependency manifests, existing Node/Erlang HTTP bridge, debugger adapter factories, VS Code extension activation. + +- [ ] Review available maintained MCP/HTTP libraries for license, pinned version, transitive footprint, OTP support, protocol revision, Streamable HTTP behavior, cancellation, authentication hooks, and known security issues. +- [ ] Select one supported MCP protocol revision and real client fixture. Document initialization, notifications, version negotiation, content types, optional streaming behavior, and error handling; do not invent protocol extensions for overload/errors. +- [x] Specify endpoint and credential delivery to an actual supported MCP client using verified client/VS Code APIs: dynamic registration of the bound endpoint, the connection-details command, session selection/unregistration, and fixed-port secure-token setup. Separate non-secret discovery from secret transfer, including DAP tracing redaction and descriptor fallback permissions if needed. +- [ ] Determine where the HTTP client, adapter, and target run in launch and attach. V1 requires the client to reach target loopback without forwarding. Reject unsupported remote/container arrangements with a clear error; preserve ordinary debugging when MCP is optional. +- [x] Document target-side dependency loading and cleanup for launch/attach, protocol session IDs versus authorization tokens, and limits for HTTP connections and protocol sessions. + +Completion gate: the integration note identifies a feasible dependency/client/OTP combination, secret path, and supported deployment topology. Resolve blockers with the user before adding production dependencies. + +### T03: Define Schemas and Mapping Semantics + +Dependencies: T01, T02. + +Starting surfaces: existing configuration/schema conventions and new MCP policy/contract code chosen after T01. + +- [x] Define strict input/output schemas for the seven tools, graph entities/edges, result envelope, pagination, omissions, and sanitized errors. Reject extra tool arguments and unknown tool names. +- [x] Define edge direction, scalar representations, timestamp units, memory units, membership evidence, and absent/restarting supervisor children. Specify the difference between `complete`, `truncated`, and an empty authorized result. +- [x] Define ID identity/expiry rules, collection/cursor retention, bounded identity bookkeeping, and response-size accounting including MCP framing. Use table identity, not its reusable name alone, to detect recreation. +- [x] Specify safe root and callback discovery for supported OTP versions, including explicit unsupported/unknown results. Do not use links as proof of supervision or group leader alone as proof of membership. +- [x] Create minimal contract examples and positive/negative schema tests using existing test infrastructure. + +Completion gate: all MAP-01 through MAP-07 requirements map to schema fields and at least one planned assertion; example results validate and invalid inputs fail deterministically. + +### T04: Integrate LSP Settings and rebar Policy + +Dependencies: T03. + +Starting surfaces: [package.json](package.json), [lib/ErlangConfigurationProvider.ts](lib/ErlangConfigurationProvider.ts), [lib/erlangSettings.ts](lib/erlangSettings.ts), [lib/ErlangShellDebugger.ts](lib/ErlangShellDebugger.ts), existing configuration/debugger tests. New MCP implementation helpers belong only in `apps/erlangbridge/src/mcp/mcp_*.erl`; TypeScript edits stay in existing integration files. + +- [x] Add `erlang.mcp.enabled`, `erlang.mcp.host`, and `erlang.mcp.port` to Erlang settings and folder-aware resolution. Carry validated internal configuration through the provider to DAP without adding a public launch/attach `mcp` block or `required` option. Share defaults/normalization. +- [x] Implement the trusted, bounded, non-evaluating project-term reader and nearest-project lookup in Erlang. TypeScript supplies folder/cwd context and Workspace Trust status, not parsed project policy. Normalize `allowed_tools`, `allowed_ets_tables`, and `limits` in Erlang; enforce disjoint source ownership, missing-file defaults, and explicit errors for malformed/duplicate/unsupported policies. +- [x] Validate booleans, port integers from 0 to 65535, literal/resolved loopback addresses, exact allowlists, unique tool names, limits, and unknown fields. Reject caller-provided credentials, legacy `required`, project activation/bind overrides, and debug-block attempts to bypass the setting. +- [x] Enforce the debug gate independently of JSON schema: no MCP startup for normal LSP, ordinary application launches, or `noDebug: true`. +- [x] Treat all MCP configuration/startup failures as non-fatal to debugging but fatal to that inspector startup. Never silently fall back to an open listener or broaden policy after validation failure. +- [x] Test defaults, empty allowlists, invalid ranges/types, IPv4/IPv6 loopback, wildcard/LAN/public addresses, unsupported topology, and disabled paths. Cover distinct multi-root settings, plain/umbrella projects, no rebar file, malformed/duplicate terms, profile-only policies, script non-execution, trust/path boundaries, parser resource limits, and new-session setting changes. + +Completion gate: focused Erlang policy/configuration tests, TypeScript integration tests, and compile checks pass; absent/disabled MCP preserves existing arguments and behavior. Server-side validation is authoritative in Erlang; VS Code schema checks do not replace it. + +### T05: Build Deterministic OTP Mapping Fixtures + +Dependencies: T03. + +Starting surfaces: [apps/erlangbridge/test](apps/erlangbridge/test), [test/test-fixtures](test/test-fixtures), [test/test-suite](test/test-suite). + +- [x] Reuse or add a small OTP fixture with nested supervisors, workers with callback modules, registered/unregistered processes, a linked but unsupervised process, monitors, and an application with no discoverable root. +- [x] Include approved/unapproved/private named ETS tables and an owner process. Store recognizable test-only sentinels in forbidden data to detect accidental leakage. +- [x] Add deterministic controls for a restarting child, disappearing process, deleted/recreated table, blocked supervisor, and enough children for pagination. These controls belong to tests, never MCP tools. +- [x] Define the expected graph independently of inspector output. Provide reliable teardown of nodes, ports, tables, and temporary files even after assertion failures. + +Completion gate: the fixture starts/stops repeatedly without leaks and supplies an independent expected topology for later assertions. + +### T06: Implement Safe Encoding and Identifiers + +Dependencies: T03, T05. + +Starting surfaces: existing vendored JSON encoder API and new bounded MCP encoder/policy helpers. + +- [x] Encode atoms, tuples, PIDs, ports, references, maps with non-string keys, improper lists, binaries, and unsupported terms with explicit representations. Do not encode executable functions or opaque terms as recoverable execution handles. +- [x] Enforce traversal/encoding depth, item, binary, and full-response byte budgets before unbounded conversion; account for JSON escaping and base64 expansion. +- [x] Resolve names through existing atoms only and validate local PIDs/IDs. Never use untrusted term decoding or client-controlled MFA dispatch. +- [x] Implement bounded session/node-scoped identities and stale-handle handling. Same observed entity must join across tools; process restart/table recreation must not reuse the prior ID. +- [x] Test invalid Unicode, huge/nested terms, non-string map keys, repeated client identifiers, stale/remote PIDs, and cross-session rejection. Warm up runtime dependencies before atom-count assertions. + +Completion gate: focused Erlang tests show bounded output, meaningful truncation, no input-driven atom growth, and correct identity reuse/expiry. + +### T07: Implement Collections, Workers, and Continuations + +Dependencies: T06. + +Starting surfaces: new inspector supervision subtree, collection state, and policy facade. + +- [x] Implement per-session collection TTL/count/byte budgets and identity accounting, with bounded eviction and explicit expiry errors. +- [x] Bind cursors to collection, tool, arguments, and session. Page retained observations deterministically; return narrower-scope guidance when acquisition was incomplete. +- [x] Run collection work in monitored workers with bounded concurrency and no unbounded queue. Enforce deadlines, terminate only inspector workers, and ignore late replies safely. +- [x] Audit runtime calls that can allocate whole lists or continue executing after a caller times out. Define protective limits and document remaining non-interruptible cost rather than claiming strict preemption. +- [x] Test overload, cancellation, timeout cleanup, pagination integrity, retention expiry, memory-budget rejection, and cross-session cursor isolation. + +Completion gate: workers and retained state return to baseline after timeout/expiry/shutdown; partial collections cannot masquerade as complete snapshots. + +### T08: Implement Runtime and Application Discovery + +Dependencies: T05, T07. + +Starting surfaces: new runtime facade and existing safe OTP/runtime helpers. + +- [x] Implement `runtime_summary` with bounded metadata, units, and non-secret session identity; omit/redact node host information by default. +- [x] Implement `application_overview` using safe OTP metadata, without invoking application callbacks. Discover applications, declared dependencies, available roots, modules, and behaviours. +- [x] Distinguish unsupported root discovery from an application with no supervisor. Exclude inspector internals from the selected application's topology and identify them explicitly if shown in node scope. +- [x] Implement graph envelopes, application selection, and paginated inventory. No process name should be required to begin exploration. +- [x] Verify MAP-01 and MAP-07 against the fixture, including absent module metadata and dependencies not currently running. + +Completion gate: a client can discover the fixture application and usable root IDs without preconfigured supervisor names or source access. + +### T09: Implement Supervision Mapping + +Dependencies: T08. + +Starting surfaces: runtime facade, worker deadlines, mapping fixture. + +- [x] Implement `supervision_tree` from discovered application/root IDs, using OTP supervisor APIs with worker deadlines. +- [x] Emit direct parent-child edges, child kind, safe identifier, available module/restart/shutdown metadata, and absent/restarting-child state; omit start arguments. +- [x] Bound traversal, detect loops/repeated nodes, support subtree expansion, and produce continuations or explicit non-resumable omissions. +- [x] Return partial results for blocked or stopped supervisors without resuming them. Use a paused-specific reason only when debugger evidence supports it. +- [x] Test exact expected edges, nested trees, dynamic restarts, stale roots, blocked branches, depth limits, and linked-but-unsupervised processes. + +Completion gate: MAP-02 through MAP-06 pass for supervision mapping; no link or monitor becomes a supervision edge. + +### T10: Implement Process, ETS, and Debugger Metadata + +Dependencies: T09. + +Starting surfaces: runtime facade, existing debugger metadata, configuration allowlists. + +- [x] Implement `registered_processes` and `process_info` with safe fields, application filtering, membership evidence, local-ID validation, and bounded links/monitors. +- [x] Add callback/behaviour correlation only from approved metadata; report unknown membership for unattributed processes instead of guessing. +- [x] Implement `ets_tables` by checking approved named tables only, excluding private tables and returning owner IDs/edges with stable table identity. Never call an ETS object/key read API for this tool. +- [x] Implement `debug_session` from available debugger state through a narrow sanitized metadata channel, not a generic DAP proxy. If authoritative data is unavailable inside the target, return an explicit unavailable status. +- [x] Test safe field allowlists, unknown membership, disappearing processes/tables, owner correlation, metadata freshness, and absence of sentinel secrets. + +Completion gate: all seven tools return contract-valid bounded data through the same policy facade; no `ets_lookup` or generic execution path exists. + +### T11: Implement Authenticated MCP Transport + +Dependencies: T02, T04, T10. + +Starting surfaces: reviewed dependencies, MCP supervisor/server/policy, real client fixture. + +- [x] Add pinned reviewed dependencies and load the dedicated `mcp_sup` supervisor only for a debug session enabled by Erlang settings. Keep it out of the normal LSP supervision tree; verify prefixed modules in `src/mcp` compile and load without dependency name collisions. +- [ ] Implement initialization/version negotiation, advertised capabilities, tool listing, schema validation, invocation, structured results, supported cancellation, and protocol-compliant errors through the selected library. +- [x] Bind explicit loopback only, allocate ephemeral ports, require authorization for every HTTP request, validate `Host` and `Origin`, and disable CORS. Document safe handling of absent `Origin` for non-browser clients. +- [x] Enforce supported methods/content types, request-body limits, connection/session limits, header/body/idle timeouts, and overload rejection before expensive parsing/inspection. Never reflect secrets or stack traces. +- [ ] Test real client negotiation/invocation plus missing/invalid tokens, disallowed tools, malformed JSON/arguments, hostile hosts/origins, oversized bodies, unsupported versions, and resource exhaustion. + +Completion gate: a real MCP client invokes the seven tools only when authorized, and protocol tests exercise the pinned revision without custom partial implementations. + +### T12: Integrate Launch, Attach, and Cleanup + +Dependencies: T11. + +Starting surfaces: [lib/erlangDebugSession.ts](lib/erlangDebugSession.ts), [lib/ErlangShellDebugger.ts](lib/ErlangShellDebugger.ts), [lib/erlangConnection.ts](lib/erlangConnection.ts), target Erlang debugger bridge. + +- [x] Compile/package/load the inspector and approved dependencies into the actual target node using the verified debugger bridge path, not the LSP node. Loading trusted inspector code is debugger bootstrap, never an MCP capability. +- [x] Start only after target readiness; generate fresh credentials and bind the endpoint in Erlang. Register the actual endpoint using the thin T02 client integration. Implement the connection-details command in the existing extension code, using Erlang-provided metadata, and reject fixed-port conflicts without rebinding. Do not expose tokens in process arguments or ordinary DAP output. +- [x] Implement target-side owner monitoring/lease renewal, token invalidation, worker/listener shutdown, retained-data cleanup, and descriptor cleanup where applicable. +- [ ] Cover terminate, disconnect, failed launch/attach, adapter crash, target loss, inspector supervisor failure, and two concurrent sessions. Disabled sessions must not start listeners, timers, or workers. +- [x] Preserve an attached target on detach or any MCP failure. Roll back partial inspector/client-registration resources and report the failure while launch/attach debugging continues. No MCP error may kill an externally owned node. +- [ ] Test launch/attach and all supported adapter modes, application breakpoints, lease expiry, dynamic endpoint registration/removal, static-port conflicts, multi-session selection, and non-fatal MCP failure behavior. + +Completion gate: lifecycle tests prove cleanup within the documented schedulable-VM lease bound and no change to disabled debugging or attached-node ownership. + +### T13: Audit Security and Regression Boundaries + +Dependencies: T12. + +Starting surfaces: all touched entry points, existing logging/tracing, negative tests. + +- [x] Audit that every transport route and runtime operation goes through authorization, allowlists, identifier validation, and limits. Unknown tool names must not become Erlang atoms or function names. +- [ ] Add log/telemetry capture assertions for launch, attach, successful calls, rejections, exceptions, and shutdown. Use sentinel credentials and forbidden runtime data; sanitize existing argument serialization before it can contain secrets. +- [x] Test absent/expired/cross-session credentials and cursors, PID/node escape attempts, hostile table names, repeated unique strings, and no eval/RPC/shell/file/environment/cookie/state/mailbox access. +- [x] Verify no trace, ETS value lookup, mutable debugger operation, or source-content endpoint is advertised or callable. +- [x] Run existing LSP/DAP regression checks with MCP disabled and validate normal LSP startup has no inspector subtree/listener. + +Completion gate: no unexplained security-test failures; residual risks and non-guarantees are explicitly documented, not hidden by skipped assertions. + +### T14: Prove Agent-Oriented End-to-End Mapping + +Dependencies: T13. + +Starting surfaces: real MCP client fixture, OTP graph fixture, debugger integration tests. + +- [ ] Launch a debug target and initialize a real client using the actual endpoint/credential discovery integration, not a test-only secret shortcut. +- [ ] Discover the application without known process names, expand roots across multiple pages, join process and approved ETS-owner IDs, and compare reconstructed edges to the independent expected graph. +- [ ] Include a blocked branch, unknown membership, missing source metadata, and a linked-but-unsupervised process. Assert explicit incompleteness and no fabricated relationships. +- [ ] Collect before/after a deterministic restart and verify identity changes without treating absence from a partial collection as termination. +- [ ] Terminate/detach and prove endpoint closure, removal from client discovery, invalid credentials/cursors, cleared retained state, and survival of an externally owned attached target. Repeat the applicable workflow for attach, port zero, and a configured fixed port. + +Completion gate: deterministic end-to-end tests prove the documented agent workflow without an LLM, preconfigured supervisor names, or source access. + +### T15: Document, Package, and Hand Off + +Dependencies: T14. + +Starting surfaces: [README.md](README.md), [HELP.MD](HELP.MD), [DevelopersReadme.md](DevelopersReadme.md), [CHANGELOG.md](CHANGELOG.md), existing packaging/CI configuration. + +- [x] Document supported OTP/client/topology combinations, folder-scoped Erlang enablement, the optional rebar policy term and precedence, no `required` option, dynamic and fixed-port endpoint/secret setup, seven tool schemas, mapping workflow, example partial graph, units, cursors, and breakpoint behavior in the most suitable existing docs. +- [x] Document node-level authorization versus application filters, exclusions, metadata leakage, local-process threats, no port forwarding, lease assumptions, overhead, and cleanup limitations. Avoid copying credentials into examples. +- [x] Add the changelog entry and ensure prefixed `src/mcp` modules and target dependencies ship in the extension artifact. Assert new project-owned MCP filenames/locations follow the isolation convention, including tests and helpers. Verify startup from a packaged artifact, not only the source tree; do not publish it. +- [x] Integrate deterministic new checks into existing CI conventions. Run supported compatibility checks available in the environment and explicitly report unavailable platforms/OTP versions. +- [ ] Run final compile, Erlang tests, extension tests, and packaging checks. Resolve regressions introduced by this feature; keep baseline failures separate. +- [ ] Produce a final handoff listing changed files, lifecycle/security decisions, exact checks/results, compatibility gaps, and deferred work. Mark only proven tasks complete. + +Completion gate: acceptance criteria below are evidenced, user/developer/security documentation agrees with implemented behavior, and the packaged extension can perform the V1 workflow. + +### Validation Commands + +Use repository-local commands and verify prerequisites first. These are commands for the implementing model, not evidence that they have been run for this document change. + +```sh +npm run compile +./rebar3 compile +./rebar3 ct --suite apps/erlangbridge/test/mcp/mcp__SUITE +npx vscode-test --run out/test/test-suite/mcp/mcp_.test.js +./rebar3 ct +npm test +npm run webpack +``` + +Replace placeholders with test files actually created or extended. Compile TypeScript before targeted VS Code tests. Use the repository's installed VSIX packaging tool for T15 and do not publish. Run focused checks immediately after edits; reserve full suites and artifact checks for relevant integration gates. Record actual exit codes and distinguish compile checks from behavior checks. + +## Implementation Progress + +Decisions taken with the user: own minimal Streamable HTTP MCP transport in Erlang (no maintained Erlang MCP library supports OTP 18+ and none is in the repo; zero new dependencies); client integration through the VS Code MCP provider API (`lm.registerMcpServerDefinitionProvider`, VS Code >= 1.101) plus the connection-details command. + +**T01 (verified integration note).** Launch: `ErlangDebugSession.launchRequest` -> `ErlangConnection.Start` compiles `gen_connection/vscode_connection/vscode_jsone(+decode)/mcp/*` into `_build/default/lib/ebin` (separate from the LSP beams) and starts the Node HTTP receiver; `configurationDone` spawns `erl -pa ebin -s vscode_connection start`; the target posts `/listen` with its command-server port. Attach: helper node (`-hidden`, `vscode_connection:attach/0`) pushes modules to the target and runs `start_attached`. Disconnect: `Quit()` -> `debugger_exit` (launch) or `debugger_detach` (attach, node kept). Target loss: `quitEvent`/`halt_on_nodedown`. Versions: CI OTP 25.3/26.2/27.3/28.1, Node 20, VS Code `^1.136.0`; tested locally only OTP 26.2.5 (erts 14.2.5), Node 25.9, VS Code 1.139.1 headless. Baseline: no failing test before/after (`./rebar3 ct`: 225 passed). + +**T02.** Protocol 2025-06-18, stateless (no Mcp-Session-Id), JSON responses only, GET 405, `notifications/cancelled` accepted but cancellation unsupported (request-scoped by timeout). Endpoint (non-secret) via DAP custom event `erlangMcp`; secret via a 0600 descriptor read+deleted by the extension host. Supported client: VS Code (provider API); others via *Show Connection Details* + fixed port. Not done: library review (superseded by the decision), a real third-party MCP client fixture, rejection of remote/container topologies (only documented). + +**T03-T10.** `apps/erlangbridge/src/mcp/mcp_{policy,encoder,audit,store,tools,runtime,server,sup}.erl`; hooks in `vscode_connection.erl` (`mcp_start/mcp_renew/mcp_stop`, conditional module push via `-vscode_mcp`). Tests: `apps/erlangbridge/test/mcp/` `mcp_{policy,encoder,store,server,bridge}_SUITE` + fixtures `mcp_fixture_*`. Command: `./rebar3 ct --dir apps/erlangbridge/test/mcp`. +Deviation: `proc_lib:initial_call/1` (reads the process dictionary internally, result never returned) gates supervisor API calls so a plain gen_server is never sent `which_children`. + +**T11.** No new dependency. Real MCP client interoperability and protocol-level cancellation NOT done (tests use a raw HTTP/JSON-RPC client). + +**T12.** Verified end-to-end with the real adapter over DAP and a real `erl` target (launch and attach): `test/test-suite/mcp/mcp_debug_adapter.test.ts`. Only `external` adapter mode tested; target loss, inspector supervisor failure, two concurrent sessions and the live VS Code provider registration are NOT tested. + +**T13.** Audit + negative tests done (auth, host/origin, hostile ids, atoms, sentinels, logs). Adapter/DAP log capture for every phase is partial. + +**T14 (not done).** No real MCP client; the adapter-level e2e covers discovery/credential hand-over, policy, lease renewal, cleanup and attach survival. + +**T15.** Docs: HELP.MD, mcp_security.md, DevelopersReadme.md, CHANGELOG.md; CI step added. Not done: startup from a packaged VSIX, OTP 25/27/28 runs. + +Next: T11 real-client test, T12/T14 gaps above. + +## Acceptance Criteria + +The feature is complete when all the following are true: + +- MCP is disabled by default and impossible to start through the normal LSP path. +- Enabling `erlang.mcp.enabled` in the selected folder's Erlang settings starts MCP only when an actual debug session starts, inside its target node. Normal LSP operation and `noDebug` never start it. +- Optional top-level rebar policy controls tools/tables/lower limits without overriding activation, binding, or credentials; malformed policy disables only MCP. +- A supported AI client receives the actual ephemeral endpoint and fresh credential through the documented secure integration. Static clients have a documented fixed-port setup; port conflicts never silently rebind. +- New project-owned MCP files use `mcp_` names in dedicated subfolders, with Erlang production code under `apps/erlangbridge/src/mcp/`; packaged launch/attach resolves these modules correctly. +- The complete MCP server and policy implementation is Erlang. TypeScript contains only thin existing-file VS Code/debugger/client integration hooks, with no dedicated MCP subsystem or production MCP SDK. +- The endpoint accepts connections only through loopback. +- Every request requires session-scoped authorization. +- Only documented read-only tools are advertised and callable. +- A real client reconstructs the fixture topology without prior supervisor/process names; MAP-01 through MAP-07 are verified. +- Results expose typed edges, consistent IDs, evidence, observation intervals, continuation, and explicit coverage limitations. No map claims observed message traffic. +- Topology discovery works without source access; ETS remains metadata-only. +- No generic eval, arbitrary RPC, shell, mutation, full-state dump, or unrestricted ETS/mailbox access exists. +- Results are bounded, sanitized, and safely encoded. +- MCP failure never aborts ordinary debugging; partial inspector resources are cleaned up and an actionable error is reported. No `required` configuration exists. +- The MCP server and credentials are invalidated on shutdown or target-side lease expiry under the documented runtime assumptions; retained data and descriptors follow the documented cleanup policy. +- Existing LSP and debugger behavior remains unchanged when MCP is disabled. +- Unit, integration, security, and end-to-end tests pass. +- User and security documentation is included. + +## Deliverables + +- Implementation following the existing Erlang/OTP project style. +- Updated Erlang settings schema, rebar project-policy reader/schema, and client connection examples. +- Automated tests. +- User documentation. +- A short security note describing threat model, controls, exclusions, and residual risks. +- Changelog entry for the new feature. + +## Final Implementation Guardrails + +- Inspect the repository before choosing module names, dependencies, or integration points. +- Reuse existing configuration, supervision, logging, telemetry, JSON, HTTP, DAP, and test infrastructure. +- Keep the diff focused on this feature. Do not perform unrelated refactoring. +- Prefer small modules with explicit policy boundaries. +- Do not weaken an existing security check to simplify MCP integration. +- If a requirement conflicts with existing architecture, implement the safest compatible design and document the trade-off in the final summary. +- In the final response, list changed files, describe lifecycle and security decisions, and report tests executed and their results. diff --git a/mcp_security.md b/mcp_security.md new file mode 100644 index 00000000..2ad19fd9 --- /dev/null +++ b/mcp_security.md @@ -0,0 +1,58 @@ +# MCP topology inspector: security note + +Scope: the opt-in, read-only MCP server embedded in the Erlang node of a **debug session** +(`erlang.mcp.enabled`, see [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only)). + +## Threat model + +Binding to loopback is *not* an authorization boundary. Assumed attackers: other processes of the +same user, compromised developer tools, browser pages attacking local services (DNS rebinding, +cross-origin requests), a malicious or prompt-injected MCP client, an accidentally forwarded +port. All requests, parameters and identifiers are untrusted. + +## Controls + +| Control | How | +| --- | --- | +| Secure default | `erlang.mcp.enabled=false`; started only by the debug adapter for a real debug session (never LSP, `noDebug`, rebar, eunit); untrusted workspace disables it | +| Loopback only | `127.0.0.0/8` / `::1` literals, or names resolving *only* to loopback; validated in the helper VM and again in the target; wildcard/LAN/public rejected | +| Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | +| Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | +| Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | +| Least privilege | Seven fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | +| No execution | No eval, `rpc:call`, shell, code loading, message sending, `sys` calls, tracing, breakpoint changes, ETS writes or reads | +| Data minimisation | No process dictionary, stack, mailbox, state, child start arguments, application environment, source, code paths, cookies; ETS: metadata of approved, non-private named tables only; node host redacted by default; child identifiers are printed with a depth/size bound | +| Safe identifiers | Registered names are resolved with `binary_to_existing_atom/2` only; pids must be local (`<0.N.M>`); no `binary_to_term/1` on input | +| Bounded work | Per-request timeout, monitored worker processes killed on timeout (late replies dropped), bounded concurrency (excess is rejected with an MCP error), bounded collections/cursors/identity bookkeeping, bounded result size (structured result *and* its text copy) | +| Session isolation | Tokens, ids, cursors (signed with a per-session secret and bound to tool + arguments) and workers belong to one session/node; a new session invalidates everything | +| Clean shutdown | Dedicated supervisor `mcp_sup` (fail-closed: any child exit stops everything); stopped by disconnect/detach; an 8 s renewable lease renewed by the adapter through the target's own command server (independent of application processes stopped at breakpoints) stops the inspector within ~9-10 s if the adapter disappears; an attached node is never stopped by the inspector | +| Secret hand-over | The token is returned by the target only to the adapter, written by the adapter to a `0600` file in a `0700` temp directory, read and deleted at once by the extension host, and then kept in memory only. It is never in a DAP event/output, log, setting, command line or telemetry. Explicit *Copy Authorization header* is the only way it leaves VS Code | +| Logs | The *Erlang MCP* output channel journals request metadata only (method, known tool name, status, durations, sizes, counts); only lifecycle events, tool name, status and duration go to the target's logger; verbose adapter logging redacts secret-like keys and the `-setcookie` value | + +## Residual risks (not removed by the controls) + +- Metadata can be sensitive: application, module, table and child-id names, process counts and + memory sizes. Enable the tools/tables you are comfortable sharing with the agent (rebar policy). +- Node-level authorization: selecting an application is a navigation filter, not a boundary between + applications in the same VM. +- The token is bearer-only over plain HTTP on loopback: another process of the same user that can + read the debugger's memory, the extension host, or the descriptor in the few milliseconds it + exists, can use it. The descriptor's `0600` mode is not enforced on Windows (the per-user temp + directory ACL applies). +- The existing debugger command channel of the target (`vscode_connection`) is an unauthenticated + loopback HTTP endpoint (it can already evaluate expressions for the debugger). The MCP start + command travels on it, so a local process could start or stop the inspector (a local process + could already debug the node). The token is returned in the reply to the caller of that command. +- Optional `erlang.mcp.authToken` (user setting only): a fixed token replaces the per-session random + one (validated 16-128 URL-safe characters). It lives in the user's settings file and, during a + session, in the adapter and target memory; it survives across sessions, so it is exposed for as long + as it is configured. Default is empty (random token). +- Distribution: attach uses the node's Erlang cookie as before. +- Not atomic and not free: collections are observation intervals; `application:which_applications/1`, + `registered/0` and `supervisor:which_children/1` materialise lists in the target and cannot be + interrupted (killing the worker only discards the late reply); `proc_lib:initial_call/1` is used + internally, only as a safety gate (to avoid calling supervisor APIs on non-supervisors), and + copies the target's process dictionary into the inspector's worker without returning it. +- Same-machine only: no port forwarding, remote-SSH/container arrangements are unsupported. +- The MCP modules are also built into the `vscode_lsp` application's `ebin` (rebar compiles + `src/mcp`), but the LSP node never loads or starts them. diff --git a/package.json b/package.json index 3f652cd8..6bcd0782 100644 --- a/package.json +++ b/package.json @@ -229,7 +229,17 @@ } } ], + "mcpServerDefinitionProviders": [ + { + "id": "erlang.mcp", + "label": "Erlang debug session (OTP topology inspector)" + } + ], "commands": [ + { + "command": "erlang.mcp.showConnectionDetails", + "title": "Erlang MCP: Show Connection Details" + }, { "command": "extension.rebarBuild", "title": "Erlang: rebar compile" @@ -525,6 +535,32 @@ "Always spawn through a shell", "Never spawn through a shell" ] + }, + "erlang.mcp.enabled": { + "type": "boolean", + "default": false, + "scope": "resource", + "markdownDescription": "Start a read-only, authenticated MCP server (OTP topology inspector) inside the Erlang node of a **debug session**. Off by default; never active in a normal editor session or with `noDebug`. Changes apply to new debug sessions. Tool/table allowlists and lower limits can be set with a top-level `{mcp, [...]}` term in the project's `rebar.config` (see HELP.MD)." + }, + "erlang.mcp.host": { + "type": "string", + "default": "127.0.0.1", + "scope": "resource", + "markdownDescription": "Loopback address the MCP server binds to (`127.0.0.1`, another `127.x.y.z`, `::1`, or a name that resolves only to loopback). Wildcard, LAN and public addresses are rejected." + }, + "erlang.mcp.authToken": { + "type": "string", + "default": "", + "scope": "machine", + "markdownDescription": "**Development convenience.** Fixed bearer token (16-128 characters of `A-Za-z0-9._~-`) used for every MCP session instead of a fresh random one, so a static client (e.g. `claude mcp add`) is configured once. Empty (default) = random token per session. Only read from user settings (not from the workspace). Anyone who knows the token and can reach the loopback port can read the inspector's metadata: prefer the default." + }, + "erlang.mcp.port": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 65535, + "scope": "resource", + "markdownDescription": "Port of the MCP server. `0` (default) lets the OS pick a free port, which VS Code discovers automatically. Set a fixed port for static MCP clients; if it is busy, MCP is disabled for the session (no silent rebind)." } } }, diff --git a/rebar.config b/rebar.config index aab69304..7b698cf0 100644 --- a/rebar.config +++ b/rebar.config @@ -23,6 +23,8 @@ %% Keep only the logs of the last 5 runs +%% (the MCP inspector suites live in test/mcp and run with: +%% ./rebar3 ct --dir apps/erlangbridge/test/mcp) {ct_opts, [{keep_logs, 2}]}. diff --git a/test/test-suite/mcp/mcp_configuration.test.ts b/test/test-suite/mcp/mcp_configuration.test.ts new file mode 100644 index 00000000..bc9fc3ac --- /dev/null +++ b/test/test-suite/mcp/mcp_configuration.test.ts @@ -0,0 +1,93 @@ +import * as assert from 'assert'; +import { workspace, WorkspaceFolder, DebugConfiguration } from 'vscode'; +import { applyMcpSettings } from '../../../lib/ErlangConfigurationProvider'; +import { redactArgs } from '../../../lib/GenericShell'; +import { sanitizeArguments } from '../../../lib/erlangDebugSession'; +import { ErlangDebugConnection } from '../../../lib/erlangDebugConnection'; + +// erlang.mcp.* is the only activation switch of the MCP inspector; these tests cover the +// TypeScript hooks (Erlang validates authoritatively: see mcp_policy_SUITE / mcp_server_SUITE). +suite('MCP: debug configuration hook', () => { + const folder: WorkspaceFolder = workspace.workspaceFolders![0]; + + async function setMcp(key: string, value: unknown) { + await workspace.getConfiguration('erlang', folder.uri).update(key, value, false); + } + + teardown(async () => { + await setMcp('mcp.enabled', undefined); + await setMcp('mcp.host', undefined); + await setMcp('mcp.port', undefined); + }); + + test('disabled by default: no internal mcp settings reach the adapter', () => { + const cfg: DebugConfiguration = { type: 'erlang', request: 'launch', name: 'x' }; + applyMcpSettings(folder, cfg); + assert.strictEqual(cfg.mcpSettings, undefined); + assert.strictEqual(cfg.mcp, undefined); + }); + + test('enabled for the debug folder: settings are resolved for that folder', async () => { + await setMcp('mcp.enabled', true); + await setMcp('mcp.port', 4321); + const cfg: DebugConfiguration = { type: 'erlang', request: 'launch', name: 'x' }; + applyMcpSettings(folder, cfg); + assert.strictEqual(cfg.mcpSettings.enabled, true); + assert.strictEqual(cfg.mcpSettings.port, 4321); + assert.strictEqual(cfg.mcpSettings.host, '127.0.0.1'); + assert.strictEqual(cfg.mcpSettings.root, folder.uri.fsPath); + assert.strictEqual(typeof cfg.mcpSettings.trusted, 'boolean'); + }); + + test('noDebug never gets MCP settings, even when enabled', async () => { + await setMcp('mcp.enabled', true); + const cfg: DebugConfiguration = { type: 'erlang', request: 'launch', name: 'x', noDebug: true }; + applyMcpSettings(folder, cfg); + assert.strictEqual(cfg.mcpSettings, undefined); + }); + + test('a user-authored mcp block cannot enable or tune the inspector', () => { + const cfg: DebugConfiguration = { + type: 'erlang', request: 'launch', name: 'x', + mcp: { enabled: true, host: '0.0.0.0', authToken: 'secret', required: true }, + mcpSettings: { enabled: true, host: '0.0.0.0', port: 1, trusted: true, root: '/' } + }; + applyMcpSettings(folder, cfg); + assert.strictEqual(cfg.mcp, undefined); + assert.strictEqual(cfg.mcpSettings, undefined); + }); + + test('package.json declares the three settings with the documented defaults', () => { + const pkg = require('../../../../package.json'); + const props = pkg.contributes.configuration.properties; + assert.strictEqual(props['erlang.mcp.enabled'].default, false); + assert.strictEqual(props['erlang.mcp.host'].default, '127.0.0.1'); + assert.strictEqual(props['erlang.mcp.port'].default, 0); + assert.deepStrictEqual(Object.keys(props).filter(k => k.startsWith('erlang.mcp.')).sort(), + ['erlang.mcp.enabled', 'erlang.mcp.host', 'erlang.mcp.port']); + // no `required` option and no token setting in V1 + assert.ok(!('erlang.mcp.required' in props)); + assert.ok(!('erlang.mcp.authToken' in props)); + assert.ok(pkg.contributes.commands.some((c: any) => c.command === 'erlang.mcp.showConnectionDetails')); + }); +}); + +suite('MCP: secrets never reach logs', () => { + test('verbose launch/attach argument logging redacts secret-like keys', () => { + const out = sanitizeArguments({ node: 'a@b', cookie: 'SENTINEL_COOKIE', authToken: 'SENTINEL_TOKEN', cwd: '/x', password: 'p' }); + assert.deepStrictEqual(out, { node: 'a@b', cookie: '', authToken: '', cwd: '/x', password: '' }); + assert.ok(!JSON.stringify(out).includes('SENTINEL')); + }); + + test('the command line log redacts the Erlang cookie', () => { + const out = redactArgs(['-noshell', '-setcookie', 'SENTINEL_COOKIE', '-s', 'x']); + assert.deepStrictEqual(out, ['-noshell', '-setcookie', '', '-s', 'x']); + }); + + test('the debugger bridge compiles the MCP inspector modules, all prefixed mcp_', () => { + const files: string[] = (ErlangDebugConnection.prototype).get_ErlangFiles.call({}); + const mcp = files.filter(f => f.startsWith('mcp/')); + assert.strictEqual(mcp.length, 8); + assert.ok(mcp.every(f => /^mcp\/mcp_[a-z_]+\.erl$/.test(f))); + }); +}); diff --git a/test/test-suite/mcp/mcp_debug_adapter.test.ts b/test/test-suite/mcp/mcp_debug_adapter.test.ts new file mode 100644 index 00000000..c82b8c12 --- /dev/null +++ b/test/test-suite/mcp/mcp_debug_adapter.test.ts @@ -0,0 +1,263 @@ +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as http from 'http'; +import * as net from 'net'; +import * as os from 'os'; +import * as path from 'path'; +import { ChildProcess, spawn } from 'child_process'; + +// End-to-end: the real debug adapter (out/lib/erlangDebug.js) over DAP/stdio launches a real +// `erl` target. Covers the enablement gate, the secret hand-over, the rebar.config policy, fixed +// port conflicts, lease renewal and cleanup. Requires `erl` on the PATH (as the other debugger tests). +const repoRoot = path.join(__dirname, '..', '..', '..', '..'); + +class DapClient { + private buf = Buffer.alloc(0); + private seq = 1; + private listeners: Array<(m: any) => void> = []; + readonly events: any[] = []; + readonly child: ChildProcess; + + constructor() { + this.child = spawn(process.execPath, [path.join(repoRoot, 'out', 'lib', 'erlangDebug.js')], + { stdio: 'pipe', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } }); + this.child.stdout!.on('data', d => this.onData(d)); + } + + private onData(d: Buffer) { + this.buf = Buffer.concat([this.buf, d]); + for (;;) { + const end = this.buf.indexOf('\r\n\r\n'); + if (end < 0) { return; } + const len = parseInt(/Content-Length: (\d+)/.exec(this.buf.slice(0, end).toString())![1]); + if (this.buf.length < end + 4 + len) { return; } + const msg = JSON.parse(this.buf.slice(end + 4, end + 4 + len).toString()); + this.buf = this.buf.slice(end + 4 + len); + if (msg.type === 'event') { this.events.push(msg); } + this.listeners.slice().forEach(l => l(msg)); + } + } + + request(command: string, args: any): Promise { + const seq = this.seq++; + const body = JSON.stringify({ seq, type: 'request', command, arguments: args }); + this.child.stdin!.write(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`); + return new Promise(resolve => { + const l = (m: any) => { + if (m.type === 'response' && m.request_seq === seq) { + this.listeners.splice(this.listeners.indexOf(l), 1); + resolve(m); + } + }; + this.listeners.push(l); + }); + } + + waitEvent(name: string, pred: (b: any) => boolean = () => true, timeout = 40000): Promise { + const found = this.events.find(e => e.event === name && pred(e.body)); + if (found) { return Promise.resolve(found.body); } + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`timeout waiting for '${name}'`)), timeout); + const l = (m: any) => { + if (m.type === 'event' && m.event === name && pred(m.body)) { + clearTimeout(timer); + this.listeners.splice(this.listeners.indexOf(l), 1); + resolve(m.body); + } + }; + this.listeners.push(l); + }); + } + + async attach(project: string, extra: any): Promise { + const init = await this.request('initialize', { adapterID: 'erlang', pathFormat: 'path', linesStartAt1: true, columnsStartAt1: true }); + assert.ok(init.success, init.message); + const attach = this.request('attach', { cwd: project, verbose: false, useShell: false, erlangPath: '', ...extra }); + await this.waitEvent('initialized'); + assert.ok((await attach).success); + assert.ok((await this.request('configurationDone', {})).success); + } + + async launch(project: string, extra: any): Promise { + const init = await this.request('initialize', { adapterID: 'erlang', pathFormat: 'path', linesStartAt1: true, columnsStartAt1: true }); + assert.ok(init.success, init.message); + const launch = this.request('launch', { cwd: project, verbose: false, useShell: false, erlangPath: '', ...extra }); + await this.waitEvent('initialized'); + assert.ok((await launch).success); + assert.ok((await this.request('configurationDone', {})).success); + } + + async close(): Promise { + await this.request('disconnect', { terminateDebuggee: true }).catch(() => undefined); + await new Promise(r => setTimeout(r, 500)); + this.child.kill(); + } +} + +function post(url: string, token: string, body: any): Promise<{ status: number, json: any }> { + return new Promise((resolve, reject) => { + const u = new URL(url); + const req = http.request({ host: u.hostname, port: u.port, path: u.pathname, method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token } }, res => { + let data = ''; + res.on('data', d => data += d); + res.on('end', () => resolve({ status: res.statusCode!, json: data ? JSON.parse(data) : undefined })); + }); + req.on('error', reject); + req.end(JSON.stringify(body)); + }); +} + +function refused(url: string): Promise { + const u = new URL(url); + return new Promise(resolve => { + const s = net.connect(Number(u.port), u.hostname); + s.on('connect', () => { s.destroy(); resolve(false); }); + s.on('error', () => resolve(true)); + }); +} + +const call = (url: string, token: string, name: string, args: any = {}) => + post(url, token, { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: args } }); + +suite('MCP: debug adapter end-to-end (real erl target)', function () { + this.timeout(90000); + let project: string; + let client: DapClient; + let attachTarget: ChildProcess | undefined; + + setup(() => { + project = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-e2e-')); + }); + + teardown(async () => { + attachTarget?.kill(); + attachTarget = undefined; + await client?.close(); + fs.rmSync(project, { recursive: true, force: true }); + }); + + const settings = (over: any = {}) => ({ mcpSettings: { enabled: true, host: '127.0.0.1', port: 0, trusted: true, root: project, ...over } }); + + test('enabled: endpoint announced without the secret, policy honoured, lease renewed, cleaned up', async () => { + fs.writeFileSync(path.join(project, 'rebar.config'), + '{mcp, [{allowed_tools, [<<"runtime_summary">>, <<"debug_session">>]}]}.\n'); + client = new DapClient(); + await client.launch(project, settings()); + const started = await client.waitEvent('erlangMcp', b => b.status !== undefined); + assert.strictEqual(started.status, 'started', JSON.stringify(started)); + assert.match(started.url, /^http:\/\/127\.0\.0\.1:\d+\/mcp$/); + + // the credential is in a 0600 descriptor, never in a DAP event or output + const descriptor = JSON.parse(fs.readFileSync(started.descriptor, 'utf8')); + if (process.platform !== 'win32') { + assert.strictEqual(fs.statSync(started.descriptor).mode & 0o777, 0o600); + } + assert.ok(descriptor.token.length >= 43); + assert.ok(!JSON.stringify(client.events).includes(descriptor.token), 'token leaked in a DAP message'); + + assert.strictEqual((await post(started.url, 'wrong-token', { jsonrpc: '2.0', id: 1, method: 'ping' })).status, 401); + const list = await post(started.url, descriptor.token, { jsonrpc: '2.0', id: 1, method: 'tools/list' }); + assert.deepStrictEqual(list.json.result.tools.map((t: any) => t.name), ['runtime_summary', 'debug_session']); + const dbg = await call(started.url, descriptor.token, 'debug_session'); + assert.strictEqual(dbg.json.result.structuredContent.mode, 'launch'); + assert.strictEqual((await call(started.url, descriptor.token, 'process_info', { name: 'init' })).json.error.code, -32602); + + // the adapter renews the lease (target lease: 8 s) + await new Promise(r => setTimeout(r, 10000)); + assert.strictEqual((await post(started.url, descriptor.token, { jsonrpc: '2.0', id: 1, method: 'ping' })).status, 200); + + // request journal: one erlangMcpCall event per request, metadata only + const journal = await client.waitEvent('erlangMcpCall', b => b.method === 'tools/call' && b.tool === 'debug_session'); + assert.strictEqual(journal.status, 'ok'); + assert.strictEqual(typeof journal.durationMs, 'number'); + assert.ok(!JSON.stringify(client.events.filter(e => e.event === 'erlangMcpCall')).includes(descriptor.token)); + + const dir = path.dirname(started.descriptor); + await client.request('disconnect', { terminateDebuggee: true }); + const stopped = await client.waitEvent('erlangMcp', b => b.status === 'stopped', 15000); + assert.ok(stopped.calls >= 4, JSON.stringify(stopped)); + assert.strictEqual(typeof stopped.serverMs, 'number'); + assert.ok(!fs.existsSync(dir), 'descriptor directory must be removed'); + await new Promise(r => setTimeout(r, 1000)); + assert.ok(await refused(started.url), 'endpoint must be closed'); + }); + + test('disabled (default): no listener, no event, debugging works', async () => { + client = new DapClient(); + await client.launch(project, {}); + await new Promise(r => setTimeout(r, 6000)); + assert.strictEqual(client.events.filter(e => e.event === 'erlangMcp').length, 0); + }); + + test('noDebug never starts the inspector, even when enabled', async () => { + client = new DapClient(); + await client.launch(project, { noDebug: true, ...settings() }); + await new Promise(r => setTimeout(r, 6000)); + assert.strictEqual(client.events.filter(e => e.event === 'erlangMcp').length, 0); + }); + + test('untrusted workspace disables only MCP', async () => { + client = new DapClient(); + await client.launch(project, settings({ trusted: false })); + const err = await client.waitEvent('erlangMcp', b => b.status === 'error', 60000); + assert.match(err.message, /not trusted/); + }); + + test('non-loopback host disables only MCP', async () => { + client = new DapClient(); + await client.launch(project, settings({ host: '0.0.0.0' })); + const err = await client.waitEvent('erlangMcp', b => b.status === 'error', 60000); + assert.match(err.message, /loopback/); + }); + + test('malformed or profile-only rebar.config policy disables MCP with an actionable error', async () => { + fs.writeFileSync(path.join(project, 'rebar.config'), '{mcp, [{allowed_tools, [}.\n'); + client = new DapClient(); + await client.launch(project, settings()); + const err = await client.waitEvent('erlangMcp', b => b.status === 'error'); + assert.match(err.message, /malformed/); + assert.ok(!/token|Bearer/i.test(err.message)); + }); + + test('attach: the inspector runs in the attached node, and a detach leaves that node running', async function () { + const nodeName = `mcp_e2e_${process.pid}`; + const target = spawn('erl', ['-noshell', '-sname', nodeName, '-eval', 'receive stop -> ok end'], { stdio: 'ignore' }); + attachTarget = target; + try { + await new Promise(r => setTimeout(r, 2500)); + const host = os.hostname().split('.')[0]; + client = new DapClient(); + await client.attach(project, { node: `${nodeName}@${host}`, ...settings() }); + const started = await client.waitEvent('erlangMcp', b => b.status !== undefined, 60000); + assert.strictEqual(started.status, 'started', JSON.stringify(started)); + const descriptor = JSON.parse(fs.readFileSync(started.descriptor, 'utf8')); + const summary = await call(started.url, descriptor.token, 'runtime_summary', { redactNodeHost: false }); + assert.strictEqual(summary.json.result.structuredContent.node, `${nodeName}@${host}`); + const dbg = await call(started.url, descriptor.token, 'debug_session'); + assert.strictEqual(dbg.json.result.structuredContent.mode, 'attach'); + + await client.request('disconnect', { terminateDebuggee: false }); + await client.waitEvent('erlangMcp', b => b.status === 'stopped', 15000); + await new Promise(r => setTimeout(r, 1500)); + assert.ok(await refused(started.url), 'endpoint must be closed after the detach'); + assert.strictEqual(target.exitCode, null, 'the attached node must keep running'); + } finally { + target.kill(); + } + }); + + test('fixed port already in use: MCP disabled, no rebinding', async () => { + const busy = net.createServer(); + await new Promise(r => busy.listen(0, '127.0.0.1', () => r())); + const port = (busy.address() as net.AddressInfo).port; + try { + client = new DapClient(); + await client.launch(project, settings({ port })); + const err = await client.waitEvent('erlangMcp', b => b.status === 'error'); + assert.match(err.message, /already in use/); + } finally { + busy.close(); + } + }); +}); From 48602fbd49e64492c025ef9d41633febf7c29048 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 13:01:44 +0200 Subject: [PATCH 02/20] documentation --- CHANGELOG.md | 1 + HELP.MD | 37 +++++++++++++++++++++++++++ README.md | 16 ++++++++++++ images/vscode-erlang-mcp-results.png | Bin 0 -> 271937 bytes 4 files changed, 54 insertions(+) create mode 100644 images/vscode-erlang-mcp-results.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e89a112..2b579510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - **Output > Erlang MCP** journals every MCP request (tool, status, inspector time, size, pages, delay since the previous request) and summarizes the session, to see how an agent uses the inspector; arguments, results and token are never written - Optional user setting `erlang.mcp.authToken` (development convenience): a fixed token so that a static client such as Claude Code (`claude mcp add`) is configured once; default empty = random token per session - The verbose debug log no longer prints the Erlang cookie passed to an attach helper + - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) --- diff --git a/HELP.MD b/HELP.MD index 03dc9d8e..638d754f 100644 --- a/HELP.MD +++ b/HELP.MD @@ -240,6 +240,43 @@ enrich with `process_info`, `registered_processes`, `ets_tables`, `debug_session keep confirmed edges apart from inferences, and report the omissions. Module names can then be correlated with the workspace/LSP tools; the inspector never returns source or code paths. +**Sample prompt**: once the client is connected and the node is paused or running under the +debugger, paste this into the agent chat (Copilot agent mode, Claude Code...), replacing `` with +your application name. It follows the workflow above and asks for a diagram, a table and the +coverage gaps, without guessing beyond what the tools return: + +```text +Using the erlang-otp-topology-inspector MCP server, map the OTP topology of application +in the node I am debugging. + +Steps: +1. runtime_summary, then application_overview to find 's id and its root supervisors. +2. supervision_tree from each root. If an omission says limit_reached or timeout, expand that + subtree with the id it gives. +3. registered_processes(application=) to catch registered workers outside the tree. +4. process_info only for processes that matter: supervisors, registered workers, anything with + a non-zero message queue or many links/monitors. +5. ets_tables and debug_session only if they relate to . + +Output: +- A Mermaid `graph TD` of the supervision tree (edge = supervises; label children with kind + and restart type). +- A table: name/pid, module, kind, restart, msg queue len, membership (confirmed/inferred). +- "Coverage gaps": every truncated/omission/timeout reported by the tools. +- 3–5 observations (single points of failure, deep trees, hot queues, temporary children). + +Rules: use only what the tools returned. Structural edges are not message traffic, so do not +describe runtime call flows. Keep detail=summary unless I ask for the full JSON. +``` + +Example of the agent's answer (supervision tree diagram, then the process table): + +![MCP inspector result](images/vscode-erlang-mcp-results.png) + +Narrower questions work too, e.g. *"Why is `` slow? Use process_info on it and compare +its queue length and reductions with its siblings in the supervision tree"*, or *"Where are my +breakpoints in the supervision tree? Use debug_session and supervision_tree with includeModules"*. + **Request journal**: every MCP request of the session is written to the **Output > Erlang MCP** channel - request number, tool, status, time spent in the inspector, response size, entities / relationships, page and cursor use, and the delay since the previous request (time spent by the diff --git a/README.md b/README.md index fe514803..9a611b9e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ New to the extension? Open **Help > Welcome > Get started with Erlang**: a walkt | Testing | EUnit and Common Test in the Testing view: run, debug, coverage | | Build | rebar3 commands, rebar3 tasks (compile, eunit, ct, dialyzer, release, shell, clean), Dialyzer warnings in Problems with PLT status in the status bar | | Debugger | Launch or attach to a running node; line, function, conditional, hit-count breakpoints, logpoints; variables, call stack, inline values | +| AI agents | Opt-in, read-only MCP server inside the debugged node: an agent maps its applications, supervision trees, processes and ETS metadata | | Status | Language server state and OTP version in the status bar | @@ -180,6 +181,18 @@ The debugger starts a hidden helper node, connects to `node`, loads its bridge t - Cookie: `~/.erlang.cookie` by default, or a `"cookie"` entry. - Project modules must be compiled with `debug_info` (rebar3 default). +### MCP topology inspector (AI agents) + +Set `"erlang.mcp.enabled": true` and start a debug session (launch or attach). The debugged node then serves a read-only [MCP](https://modelcontextprotocol.io) server on loopback, so that an AI agent (Copilot agent mode, Claude Code...) can discover and explain its OTP topology: applications, supervision trees, processes, registered names and approved ETS tables. It stops with the debug session and never runs in a normal editor session. + +![mcpInspector](images/vscode-erlang-mcp-running.png) + +In VS Code the session shows up in the MCP server list on its own; other clients connect with **Erlang MCP: Show Connection Details**. Then ask the agent, e.g. *"map the OTP topology of application ``"*. Setup, project policy, tools and a ready-to-use [sample prompt](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) are in HELP.MD; security model in [mcp_security.md](./mcp_security.md). + +The agent answers with a supervision tree diagram and a process table (here a worker with 18 queued messages stands out): + +![mcpResults](images/vscode-erlang-mcp-results.png) + ## Using this extension in Erlang Docker instance Clone this repo, and try it : @@ -223,6 +236,9 @@ For Erlang files the extension sets 4-space indentation (erlfmt's), enables sema - `erlang.verboseExcludeFilter` - LSP methods excluded from technical traces - `erlang.debuggerRunMode` - How the debug adapter is run (`external`, `server`, `inline`) - `erlang.useShell` - Whether erl/escript/erlc are spawned through a shell (`auto`: Windows only, `always`, `never`) +- `erlang.mcp.enabled` - Start the MCP topology inspector in debug sessions (default `false`) +- `erlang.mcp.host`, `erlang.mcp.port` - Loopback address and port of the inspector (`0` = any free port) +- `erlang.mcp.authToken` - Optional fixed bearer token (user setting, development convenience; empty = random per session) ## Help diff --git a/images/vscode-erlang-mcp-results.png b/images/vscode-erlang-mcp-results.png new file mode 100644 index 0000000000000000000000000000000000000000..c9544f86a5561d12514d581246289a412e1a92ec GIT binary patch literal 271937 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�)fs9Us5ZL6)c2~-3V zIVy-K*+!HCk(`r?fMm&%QCsa6K`oMGBuEA&XG~-PfdT{sR3sT;HWz4-7s(d=|%mVV?<|B%I)L8KAgY) zT|N>l|7xdcdX3Gwh@AX0QSwEOb$Z=hbkoBL%_#<4?qh?4i7sP#7tWrYd1USvQ_Vwf zviFmz7Amlx)G_*k6~7MrPNtI@|bd>w+(`9NR5>^{P^k?Q5ZE zeGTPBo#G{RFN0#V8=xlo8@7q4tE~mopAZOa$9gn@bRw~67nAf#qvtOB@| zrJ(e1y|ZvWfBwBT17Ij;v|{iN3QB;SWTHvq?|1KO2MBO;%X!hd=~>td8uZdT50qv| z&Kz>Re0ySIw7Ij$h4?nw@_GI83C{?8xy0}8y|r%Y9R>FMj5AYXE%Lw=nt*EfSr7WP zm6aU`tAyy8Ox!b#{3-fqcX&-F7sE8$ay&{<3PO#)a?BpFLQQa3k>aIGmvH_?uI%xJ zhe5>S?iTrUvwQmi!BHVJztT>%Of_wdx?8&45G91FI~rHrzTblCwPB%k7vL16o#c@; z7D8__n5>k}vIu@-%e*sjEy=n`kTlYC6_+VFGVp#<8(o%lx6!&L4%K}U$tx%r@($v+ zL%1{YDN8n-kr9-L^#0rkA^R%R+f;P>86a#ujR5)=PIngAB9)MQXUmoc7L8>e&#M;m z`hSVHEr6?P;q9ed&fnty`SazX)y@JLGP!AGZrPM!w}HU3Wz?c(y3AT8@flvbra^rx z(6S_ntXjj$+|A|r@`#0El|{X9Sb0v%_EXUp5f}%9!3{!S3iwT&OefD_Tr*C4lq?&C z&DencbN&*V%C--NXWVf<0LPUMf|fLJXSFB|J>Ew;3e?xFTY2E)lRa0US90Z!>gQ)10d9*a<1;rZ?QeLv zy2dI8E{&HTs;bOuym{Dud2alGnE6vw<~=(`zCcC6$aH^w$Jcd>#@pyL&EG!hI_q-M zs`Hby>@~h1yB>e_{-jc`Fd>gS(WeL7jMKn~IA-Mpy36#J&8v;5Yx95lG~UsuE`fTA zVcKz`b8)p%ica^Mt02U1qTopm5Q|YpC<;M=fEA-jJ({GilJEBNYGyd3ls(LAjvrcG z>g$rhbO8#pmzFWLKc3CYMlC1UJmX+}Uy7wqzw#;^hx48vuko1-`mwhaSVd5F?d};H zO9R7Qb+D9?j*bqNSggZ#R<|wBHtxVF(-`6$LYy2N4Z~+_Q^fQe-4boF0#%^pvh}y$ zGIO&%l(aWzj79T#e@H3OkNaasmShnwWXFYs*~} z3SMuU>Ju2rOQ2ok4r+Nx@Tlw&3`~o&URQpz)51S`;lYd+YR}#WK>qhIn!GvJy4$WspoNWv_2O5Zs`Gm^^ZmT-n0T9ko@PuSJ zYBO{vE`0qfr=#jC_Ji6mp#*fKL??9>(B|ymw$Xchf@jy|Vdc@O4)MohLfRAhnv$2` zv0wdduRzen{K3Q|DqtZXn)~t?!TU-MvCJo9Q%-++F6op;iF=XMN)x8aPHv0o=!j|yo){a+1a8!UzQMj6FR zgPaCow!A9<0UF`38;#RKvgS{o0`r*E;sp*b`eNvBiwc|A#4>uit-hsM=nYl5h`K}- z1i?+onz^dK8#csLHnk}rqpGmFFk8Ycv{qot^M}2V-9?c8ejai#)9UaxpLr&$-1e@8d`Aolb zcXv+@$5g7dpLQ62s^>hv*DD?LzXTBd30o6&#|5ofM904=(^_%Mln(qB?lhz6HTGBK zHKobknkIm)Nt;KIvr6Ars~uoyxN+2d>E%+rjDWqn?4nUCpWA9t&m9-AL(v6C5@ept z{v|^rqrq|3@XSPUa`4L1HW}f*l;<#B_`=((^s5UkB}pos4{W~Wg`F^X?hqLKyD|mN zc+*gnS280PN`+Ld>VgdmI_vUM;r2+pv}n_aG08+PB3teWPC-Cm$hV^taf`|K$g{Ne zC9d;n&ST57DcR#+WqGv=KP%dJ4}l-C^V}J?h0Id>0ugYH7Dfv)^0KQJ7^O=STEtdv z>IJpmoNj92vWWWSZ_F3K$E^mKvN;ibCMUQCeoKBnZ1yQlV+GjuEqw4w0 z38OZmg}+WLDLFZV-C9>iXU)D1G-@j<)^INf>AQ}{21NA5dfH;_E@JL|ysCNQ#tRgO zB{!cqasG^WxUrLHeC^m}xhzee{z)~#3sVWR!yQQo?eNW{0B(UBpCz!#y}nsdWS6jf z_D?Av5E+HbD%hkeloG=7PcSs zBevzblJ8%ga=QP@uG;A))(9KE;qKT$e*_^<>B= zg?&&B5LM6DuSbC>dig2&T;>aU_~b|*AN=Ru5G$_Ujb|W`VzvF?=?_<1p}CdPso)<2 zb+Ex;v(zUA1qG-bva6S(8;J7avFRUIjTeeB^Q~-)uIJ<3Wt@Bs3pK-7;L8)$ybDtqcYAi!WnZuy zZFT14;$pK66JK4Dl^2=!v&yrE_df-O z#$8}^yC~RY7yZkQgV(2LpZfX52(B&$NM6|fv0Ab36taiPabj`R=Y6Pq)gSWk*BEJO z1rs(n={{?p79`nhY;5gH`|DL@m?_Rnxh!H!f0gMvhTYp<@Xk~l`$a-R`Sk1ihx~%H z&T_bK>8^|mZ$3sNuL3KVqWA7&rbz}WS)zrE|miuZ}+m?i_zd3$)h3tPO zDiEOstcq(U?1SW?@3geE2U`9v6e*ukjh$FL!hFzrB%>T0$&sAmfzy{If_M!^9zALSoSe42$SHVYB&tqaz2V8R{GQ$rYP{kxudXmL zH?6Va*>Kcr^LE&Ed;7Pow6%H2W${xCJ&xv3EN&reL(`|QUB0{Qhq!YzV*Ab@c|Y2+!HH--)Cl;0b``F&I#K| zmn=npf1gnryS6jTlw<4c6qn0CR zvRx5Ux9dMgf5UIiGRkz&AquulCjvZ>(rT@~=(Ivi0q4=oW6joJL z?#np|3lB?Ig&Mb3%~qMycJXgoXc3>|J@(_rA%o;Eyt%3LNndkao%|JC79PvM`-^aT zTlaDa2O=4_E_r`P``*K& z)R)oWuBa|6rI8xTUmOnQ2aIM4v`ptB3xklW1_(cU#m=Lc`0oAt)s2mkIIocF>3ViS zJ)dtQBPnXeN{I}ouj@x<-&iRNV& zOGu@o&gffX{F7Zf7}AA1Cf@H)qaS!g7{D$M-tfO#o0?Rg?A(d)Us=ZV0d_Ifxr1k- z^#lkP>Y|->%`of~OZ*l2pWc+(v6wm&$*A!K;&5 zan;f%{qzr*w3UEA6WjvaJ8w)}%f}N{__AmTOzrNydmMNgEWTb48@K;f@x(OGqg*qq zM=Za1szb4O8_Q#*H+b;qfnW;E%JqPNlKa_Pa4^tKmRumj0`BKlnr+ILEK`wpS2-5; zopOI34|oA`Em$$k5R->Cvv3c~D_|wW+?$+KhGsM|(-1Kk8XIdTw;<|7@m`eD;vDwR z#F4MvU%y5nd4kDX2|ySGCFo{;sE=-DC>l(4G2X01BH3#@-P%JU4PW7Wyq>xj8BFJ~ zj=Zvvg+p#jFI5L4c1iw1(;Vg)&M>n0#*5bk4pyz$M$)TIpKr^Ks46H#B5w84@!t#L z`e0`0J(zkps%5UswUn^16i6crXZ@y1z$$pz+>ee(%BG5vh}UUOR@Mt1Y5G<%(FOC* zL#WVfPSGzpi_v&r zFQoV=_+x`D`X0Va4C0D8tYe!O?~`a&hS)-{pVL_M_LOm2s;i_Z{68k`TIK3ja)6^)IoOHN#w!4`um5yjdA$-og3?{Z5`GP`-cR^E!}nnh*nwC zq>5F{7j&_fhj#D;j)vQ?cMmQ6NzcD~eqPn*^(w~e*(oY2*VOd%iYMOnWA6QUDJ+}1 znezVg7s>d@1ZvznciReRI?rlw+j0=ZhX1 zg@uLG@9#agfkb6!WXQh!t~JVezmD-b3JL}>hh3CytTavh_s10qki{d+ zZyI@b5K|A`9=4HpTYCEXgdN1~k39a0REy^;iM;$=HrPYLXr+;S{oJrJ&K84oL3%iRlR@jm*nLY zJhxbVjqi;4VwTwBuS0#TxXvCpzOa6^JUjk5DE=5Drjm?lVusg-f4*$`(vPYV#7umIagof{OxnPXWHm)aeHrHzfU6hEhh zOh8os;kO4#Ff8vIZci4EN-l(I-bi~T>Kli_Xn~n?)^4dz=HHoCM>wnX7r5>rT zuMg&PqLg_T)V4~YsgK@2E{MYYbLUP%sF0xvdI`pKn@FC{|3kNh*eZY69@94DFsW$& zuxZmTs9RCwR7-8p_gFT}(nA`ITA)tEj2vf}r2v9yWe;m34mSt`x8$r`$S*@9nq&P9 zvKQiw>%uuI>RM>Tm*P?ISW#Lk)m{EbxqJtMAEas_MADBICMUDMV5eFfTa3FDdivQT zvGBMP2C1rOY2XL%5ABt&_f)A8l#HQVh>gX2-QPh#%zj9MB0Xp_(q`}Av7&SA9*sIg z6sKzhtM}~N7k@>u@~}%dov~^cEp1{oZ^1=0LsW9t3r8(P(p@hxbq9y+m$kZ% zouH1&FE5H)X}O*P`knL1)pw@6d?+c6R=Y_Ku5y{KpxUjoXWO!DV~UXw{XsE`+hzR1lF|G zv%{^?apCUgDSHm?iOK15mxVh`?A}m8E6qnll|MA<;ujLaNrvpSC*#xOXAvFO|GAm* zWV0DPeG=N2YWfSp4RGwTO|!AF;n30NEt!e}MI;tFvr-o>XfIhJEUJ>0SBF?JSAemx z@leEUM;Zts`>XYw$2XO}d#7Q6Dk0He5yM1;m7F>>drv^kjbLKHeevqa%X4yeXkY?L zB1@s=UP9i8+~Tv}?4=$uSyL5ncN0lpk50!DsSb#0DkDLwwit@u=(M~m`ELyGcHezi zs9xT!HaM3pP|LH`^MG0d4=W3cnu&1C%nu)bx(xGp&(59Cz#}dC{>CeRWJWmibE7=- zUU4ofpwvtzADIK=(S(oyy|2AqFFN@?W5Ky{7g}twb8%ffc0tcHYXps(uY(avT&P8B zx)j9@AtV8q=<{ku5rZqfGUv<0jszYAh_dDi;kiKEa2mv;p}fB5CJ@HJ3|0rwF#Tq- zv05}03Vw1ye43O@mpaCxr7F%~e#ca(AEki1G3XztpQ==ZktON`8e+)4)6*1YN{kw} zHi0p>WjbYm0+oZ;^y-IomqOj}8+#_|&o!_uRE&bTFPq z{$pXT+llhv!ezU$PB|195m0HdX7>B5^u!qVz7NyZDa$==fg~7`_R6T8fT9Zs`KrCv zx^B~|@WqtTU@u&Zzd?!u%8ONvyD<^+Q**5ikpEXn>;!l`MaVBu9ELw7031uglP3rR z5Fx$HW!3oH`7 z=!i^8Ak%=j)rva&aG{{^RMv1;nLr&I>)7+Termul{@#5^tt@EgY2=9X(n@9$!Nsr} z$!fXYo;iZ{@}yVu`=0!mri0#L1rB72^Hl#c;|M1$LeT=y8d?oUqF#64^Xog52Y1#Z z$*83B>6Cy;!#QN2K0xTG=c`iafvUJCkD9VQG*^;J>zL1a+4kv?$#VWo0;bdoQ~E>h zQ%_3kgdIky==H-))-AAm>7+(KDf7&^`HCmU zC=WJB#`;quW`kaVv+^gFrH$<*SFhxg7!ckS4a)S4W2$V4 z{`J|jLdbj-FPOfBQdNOWix*R=MZ3{PMH#M@{L``O8Adz(TbW3a0awobh@0aDX}K44 zbcGz%z)xsGLfZb!qyi)u^-v;Gj8vLovhet`&8TkU&C6?@oqN}O{n3(0^_B*&t=p>g zc*RTJ*YCu}<{J?a(UxTu*JM@|-hBk^9VsU-zanklcRcdFJvcC?^JiW&x`fq===M%P zT+`MkrQ&NJrJe<3(-$uKstKF5_1vZfM91HCPmG(yK)Ct(@Y54;`!^CnTiR7$?^V zg{oK>`k_kJ2!%(ZrN>V1+Pyo=wuL85#7PMd;$Ulrah6|=+N{*#oE%4Q>K$(A3pC6PS$+6$MDpZi2@hD<3ZQnx9(XRp2xita zFT&A2D~uY8DtWbP^aNxk&(Cjy2j=$g1>53$HQ~x4o9|;AFsi+&ix8`!;z{5j1XR_q zKQocX-iN0U#KpN!sHDqitAgD?Nog$Tm>OI(zx_sGGPo3&&U8?S33w~I1HXUzDE8FH z+?ToUz3CS#Kn=ed^EY^PJS9tfVHbrH^aFSurA7V9L0JmysrGjB-fx?dN-wO2;?*pT z@@sqv7Us!VkS1JKorQTdBW0PUhFQtic*3dFJ*-qA(yRKX$9{k@RQlDG7J9Akm9E|e zqe2F)sKt6nRR(TNJLKYg^T6s3bmm-jUrx+&^$mAxe)?UH;}UN#3><7Fu>6eqJmjvW zDLh4Xe;z$nS6lt1yE}Te(@j0XEnfoiZ^^d~WMeN(f@wimYl5slZApj-e5yzEaf;DA zjvAtVu~0W7exBg}oftqwiT=j*iSxp;&376%y}n11?!AQ_e|T^WvP zXvv!fO>-&mKYUxw)igAK9xG@0*O;HGZpnEm79>st|_`QOR%!Bld3{WZf^Kx_h5gNClJ*o1b>%t^&e@~#EidX-2!uBE^z{V7lfC5=O z`I1%f`%RLX&8gaPTXv5IVr_MX39DoH{9%#WGEI(=aT2eWqxZHC*^igfCFz&A4>qS1 zK>jQW{Y-57Dv6X7T%?4dG9OhyuAoF=_V|T|r$U0fj@^C-=?2&NA3&R@bN3w@aVk^? zfW5>6O-52rPA>SQA_^WDJ-(6+u2pU0EsQTRJ}(CP6uXFs4$=V&OJ2O@mhI-J+EwpG z@el2#)x0DWfu}|zwxmG8PLEDiyoSbo1PN-ou}+*G-`L}k8ed`KL8K(%uM(TFg2H2z zTp{%x1-m=&`aX+VVzEq>^C;6qw%d-K>mx+bDCCV5O>!hV3zXj)|t#_tGFZ~=^I|QVqrTrl(X^0_0I=4LHWMyi^6I9hi8XbYhlG!UGD;A^cx=N0^_>;LbZRJ^pUk>B z-hwQV8klh-ZAI-zb@zqxUi((`Z}6x{qP~DbB@Is4?xakHf1QeisZVU#=;vYzZ&5%m z`_DfmRTo?}X;$yk&_Cx)-KYC(K{IER=02mk)KEsUb9|LG3{tB=`GZ#T)bG_rNI=l; zrfwkd9Qd3dH&6Q1-T#*GQ{aQ97VAPik2F@oF^(-59-~k zsQ8umNbu8*qKu^IOZ(4EC>6~^up4T?@R~8(4*U#TFDt_jXnJXI0V2x@pQi7D5eUv+ z!iCvUQ~1h~)xXoyJ_nkY2rF2d>Lb*%Fy6iQzyTRj;z4AXkWG7h|Ne!r&I|U$x3Rtm zVBrSGPw+gl3flh(NOG9YhKmwDBA_?2Y@mBI^pxDDo30wu#Wn|vSp*x^K7Dp;!!OhU zH}WID@J7mu_&L&`$UndL zhOnr5bhKb?%1!AXO2D2Q&<``~FU#Bf#E;++KeN3pRc)r}_Xv4tDPXA#FQK zN6ea28xgK?i@AV_Ndy*zM=NuQOb}*>;s7D21c@!&3OBqsHV6XavOMJiaz!NRk;;O9tmc6J6& z*P5fyRnvU&>oZCCQpU?@(}GAX%i5FTwE%F`VK)_*`~2eg(3tIbsL>q zDj@2l2?w-m-tfzB3SPAJ1V30URVN`g*MfG)Ppl42IS(Aa;Tb$PKk(%OqkpS;_TrMC z<0s~xoVb_^(9${$61Xps&0m_>7;NsbU3Mu@0O&4_z%WIOKeuK(j(7OnOYp<=Q`Q@WU!+kP|tb5 zFs19oYYj16iRkewx5sS}Y~(wPB!a4~<^LMyFrzCd7WJSeZJ7gYie*B~L;wSgRL-ME z-$ec9wlG!yGiay8csb7~Ae?2Y~S4^ z35DIA*=_|L)5dl4mh|*yp=M-ai?g%Kge&pzaCu(&q5b<4Air-m_u`84xBBvjpKB0Y zV5@xfb?)sOzAU)aHnWOP#=g%FK6fi{a2^F)H^RiD*X6WRmCDG+mv1#j;iBbqIm_Z6 z%F?lI^baPbHoUoOeBlH^1jUBS-^_j{KtEtr=I|zoBFUd<;71E|3pxLLPl;7gY!_s_ z3L{LK(qpr&x>9*d7Gwz4ZBL+X>b>nn_I0AO{j~+76qO~3n=lTt6oMTBg*h4gAVCq` zgWJlgIhCTF1b3Peb42o<-{SAiumJ#O6f7;n^*omCN7_sD-`>-W0x2y46$qxy=8Ca> z_T%5b!yMjsUmS5-FkGmMeMQ$rHepI|PAv?dg zXttC)PQ)SVdhVJuVpESxv(eWZfLE+x7Y`EDTTq|r3zwnHhXJXq%~~6kKzWACBfodg zp8naroaO+5TVCP(5X*N9hHTo4nXP1*xh42L8sW!LpD&f%w)CkeOMF>i^ z_=E{hf7|`28i0rwokt3!x|9)GJytpRrzr*<6NT1wAtAfeqEV`dr=edl8hiZnC&zf3 zD9o}cKC5HXLs>>O2V!GlOo8n6j7A^r`Z6)d0#P1lhEND#$ZY&;nxV~!fyAU#C6LjA zg$&aPCKbU41zJRG4rChsmh)Y*-vCLJJpOFN@KSdCDQDtke`PM~ba}^|G1m zD$*7tH$crV)4Gv`+yMGu!iMq9)O1zZy!?E%1U>hqaaMzCwaxz>WFs~ytyl(?A~F{* zQX6B>+b)3{7Q9@`1-CE(S~wtG9CufT^X#%ZFYmQSk3giL56Z^G^zLJ}N~rTi3Z$*g zGeY6Po)=vKi;hEFT%WbEar&LF`~XFkCwrf0V^WJ0B__zK@R|M43R~(VgK(xtm%AJg zCG;{$5v*-pLE-E4hj;fm*Pi=O+94~)H2 zEH%p*zV__UagV4THrBH!oFLxhW{V>L<#&+!3+pmkc zE_LBsV@)@BZu{HoXM%%dT9R#HF#YP!EdckN=l#`(cSK2-zV>Zs4xLX%qSi}xJo;~F zHcC_6mj+n9>~Tll({O5glVL!M4zb3SuxwmU?Gw`S|43d>Hw!VrT1(lXlX0_135hOXBsp* zQ_R%K4R354+`_a2nFr!~5qlHQ@HCq=rgek5K6f*KTh@e|c39R**eZ6OV}I#fa{#&C zYV1QGd(oCG0>G;JlvYHTeC-^(XJW-E;24&9(5`3sjIi& z-I$-nG|;E-o~>aC90Mwv(i9@f8!%U}?n1@O@3HTe<|CvjAenKv9wgm(X7CfcX?kB; z)6pswpWZMj7kc`M+Os;AI1Mo-bhJ{z)(UR$!#s3wd2p!Hva9&*;3PQv4e)E!@`tf8 zrSgOcCn4AyY>4&B58Yz<4&XU-0fEjJa1@O!ExCF)0 zuRPZsI1bikyD6qcPql}Ddo72-gd=&udAud~^!O2M3epiC<98ug4t7Z_aA-61Xz2-< zZBo|<5+lNMDB}D9VWWp=@e~cOCq*Mq$+oYUlMIjUG@HSYNL*|zI{+k9|1_o>b{(kZ zzhqdkvr34tcG0!7@9s0A!A;S(UZbw=jLggj%eto%Eiu9URc5*_RWAMU%=osRMcbau zx;q$8U(T|$u}O#ZgI~aXUU(hppSVL9amxV2Qo%{1PMOpVVE1w8ztapS6}fnh!>_sq z9hoPkRRTbwZ>$qZg8a^4B4?TcQH25T6m1~nB2LK>#AXDAR>}I#M`tpst2(n&?hg}* za;Dg@E$96nZft#V<<5@Qr;iNbO*t^sp}90#FD!4}zWoU>%TyuXX0xV5|Gwp6Ah;)2 z7U)mfRs6nv-v@oTa$w^Sp7GxL|!I=%`jzAFq7;jqos>9N)hR2#lF6KX%if9+C zyN3tCTOCLsSIlICGMyv>*MK+fmE*>8_{-1`C&-fUaSue;qp+mAtkfh0nBBL>lUE;A zQM=}A9Zga#117)O!MmnR)eg5Vprzfz9_+|Q+Rg$LaZS*Zn?I)&$ z#swutmY3mKMMZ{50~DL5It#`IGvw*wmE+PMWpE)hqcC|h3c#Pz6SDdCSLwUYh661* zX|%3<`eU_W1{Z~R$wMu4fhqCv@wsB@8UA5m+$f@C_hr>IHyHG*1m_E3Q0C$@=One1 zJ@oD8LF8~q(GS3aEb7yyU=cBq8QL7#Y)ir!jjInbGNwWfMm2Z1XXI_?*o{1UTbAzv z*D5Z1m4w02o1#4$r8c~~WMx5H$vQEUv+f}{7@(rax%M9+m$HA5;oOp_qf&kTD@j7` z>OXcaIsWq-CojTXcJ2#n&7bXbp{D7Jquq)m8loHc=PaT*0a0uN7F?rQT5%MJiv(v4 zt=o|!rM;HlH-QHu{8_L}%*u|w>QtY;O_wMK*ej_$!W+~A7 zi11rSM~5JPB43sDN8b^sg%lTiGm}9c?{E^&fXW~0a!Oq&pqL0$0DmpBvC>#xTyGKA!F86 zw&Ba$`!G>an7AgRp=Y!fYLvJ&(^XJrta^WD#C|9x=Q{jmY{eKTPX3Yh`HyWEq>A76 zXd-_+J=CsFxaAl>8cgM*)+>j}ZJTD@JyjN52q&gq{ncm{wFwOzP~4fpa2i^?Q$N zxrNnot4lGnNprHJABDm}M&Mc}eQfs4HeS=Qp!#z_1-1fjCn`Hu-#9E}rZ-&i&w2E~ z_5Mgg2+Yqu``vr?#3Su$0c6cL?qZgPV|`Q|fesS+XGpmqG3PxYvQ!bRHW^(w-?hpi zEPR1N%`}1Z#O;lwX>S4n)tRn2tyd|vzi?>-5Ti>V(Gl!% z$|DBlvION*Ew@B{WL;rnHv=Lrc zt+o$J@tj;-IDvj1l~7T}!q~&WkOGhjViExbsgQ4gb5W;lY{(wu!HgWKT-ZYB+bXNI zv65r)g$cAn=D=$Nu6-i9;}LNaI^+0P=`@RROfqE0SzbHEd4{>CaOe~wLBM80H@|;& zYQg4#D8@L?!#7K{qQb>q#4^^$O1m4NZ~PT*t<1+U9FJm-W&Hpbn_h9AR-$}k*u$Ro ze5dLst!1Jt!Evl}G8H|pRK6TOH;~6yWy>e33rC+ba|B4LLs|&B6i2othJrY2uo5td z+;--Ws~@va;>}v0roM}}^;Bn?VNn6A2c9Xvh#6hlACkyscF2Ezhj5}L6VaU(YypRq zkcf!7Ym=lw?w>J}${*y&a~wMYZbobgxVcFxAkSS<&nLs{H)`(p@5jjvNTIE<<2by3 zqA{oZ7*)UOnyQdBUsHztHg>OSmhWIs`quYtBkqppVBDpM=82vX4(92l0pGz4dyl0f zAVnHJQ+;&vq_5!Q0ur>-Jdxo!4ol#jnID8c7py5vtm@fIj z^Dd4p0e(CDq&La}Hyq^uc@m){4xHp62WHhPr=Jc8)zl|Aj-c_x|Mv6l;y4)Co}63Y z#(PwbpXY3PFr`g=r~0Qum-uk)?_7nSX9=Yn$ie6Tx-}~7%e1$4;T@fdA4f=to?Sa* zB>I)%ZBX8$a6^?gDqGgR_Ub2xDE^pq36b@p{sc4?ydJv~#I1PUpQqV=beR@^E9CrS zByaxl_l+w*`OSNOTzKTlPbN?w8*WA=L6Lc*3=Z?$_LKTNt)v+(^W&#J>-@M!oyQz} ziGQx$7Rmg_*&;7pRdU2(B!M5~Ax|wYatE;_|6wRzD%D{oJ~#b2!ijrr{YezBkb(>^ zyz43R0}GF=;_$ugrMULZ!Sz2zxi;zO?gHb$tv-G`0)A&B&Z$86V=NCROhn5t?c`cF zY{++}VC~5qUOPrY^v%0b&&k}IH`%{6xUAv6B$eOSK5S!YovGV@TocV|zb7i?H5ad5Qd?Uc977Xx-;RpOyKP^=-3( zOq=ZbS<^mCN7q@qwsRQM(U1<_TbuX_+f*HjcPZUi_d;EBogX!~Nk^EZB29kvZ7y4N z>-8@Ebqot+jvdK<9Pc+P!p>myH9UE&GnEajMP3G#gKj%_a~i!Pyvc-H(J2e_U{zugq+aQG&!jzjcp z5!3ku(hasBxgP&l6}w1uyVkRj2J`t?ZClHyI)_IHUCRkauA(sw*PZoI{F z+uql^No-}fCjZxKH>dNv%xz}Mat|lQxmKP6Tcx8UOI}OwSqiav#xH zA1OADiHN%%FB2fnaVT1a6iRc9p!O>a;p*Pt+AmLSA*s;f1L~f&*%*J^Wj)Kqi5;XK zVL?x&T{^u&TS$AAcO7TnlIZtP3IG3HVV#2N8Y4*}NV;?rsVRC&QtW&LNn)B!R7UFP z2%UodlQv>)?@)YF&m8l0y=T=CH#!nEnG#7>lev9jb&p92evz2pp3a_mvZPmMevKq~ z+8VOb#a(0?q4P{UOl%FWHpLu`mR7uy))1ry=$#C=|H@}p` z#YBc5^;}Pif6%bbzuP)#OFSv-9QbuigkP!Jwu6`wuQuIPWFdJzU}KPY5`zUAiR_fh z$Gl@I>xHI{MXuM8Al9*`P<43Z7TX{a$^6U66_RK4PK9+m7YRcv5q4Sr!=E?<$>nj` zHj+Yz%z9Sl*M1GvB&z7z=P8mOHj)%{Wgh);#G8&3DRe?;Q|^s5Yk#S4??ID`8|8`5 z8EP5s+FYMsZ%ns^q#%E=D_cgBeaqHsc3TyU*KC+TVqrbYdlTHs4rZ(M#&Jx?w{9YR z-qTvSMJQ9`Rz3Sqy01zSMbMLZDDrP6gUl^7Sx9*vx!QlzX_M5%i>)LD&d`w2q-*;< z+txPn8~e_Oz|?y+BozLNw6tkRgKvG{%6^HFH9D?J+x&Xgho{z&PTx_#5p(q3!@zfc zPLMp?lI)H%M@ovfaYfP|BQm4IYC<=Znq$7-cjpez4XhO>z0#DayH7cg6MPDHlH`B! z+@TZK3UR#g9%cpEwR`E|F!R~nV7HEBdd!R56UckR!J0&RA?sjs+reYz!O?p@tW9)3 zze)>B-2)$K?D>vYK643q&`oFe zkY3&W3Zpz>dEo5vP4R}r6I7t_7ib|5*y(O0z9ThYrzc{*o}}|DPI5VB{%h$e7&_dK zgyNg<~I9I$+DpjY1?x3H-tVU)&Sjc?bo||h4AUGxHndm zpygin->AG^c&hPP&!>WQwZtHLZo8=ITN=1g{?||viDwUNw=6}eTWyUlsuF??s5;v?wVlC@a0%y~X5gQUCDju5=_!<){CSIQIL>Fc z&bUEs!ia|yDR#Qp!oM)&{Nu0New>GT)^L-8k`pH#Zrd+rO6ZadFYfG*2fVNH*2dy~ zD80lP#+=|wC&4jhCl5$1ZYIUcr~B^q3!^A}+R^aKdV#gfo4v#UX{&7n1|x}5kA+9Q zP#Udtv}fcH3P0Wq!iHJqBZ(TqRg&QT z{X=odlUgraDJejv_d?}S|1Bg^nP{|Nw36omB17e=br{Qlm>-`-ZnnbglWNzh%~-d= z6WC?dilu?Y;TuLdjG-Ya1g(Dj=4Wog@q|TbE3auK%q+22WmXwzPtYn>R`@kYPj4c{ zTV3Z|M|z>6w%E6KPXavbw)2tq)(-z=lS4l%Fa7TIzu5H~XFc3;oD^C7ECm&pN}3}P zDhDsJC)LDQY&`ggid&^d!o&{i&EEWEYD`B*@;q=sdkea}4R3uPM{fUBg@&-r)We;> zVVqnu{YfMZnG}f|e>&`mXC&-a{FRx)BX%N|0t}Z)5=Bxt^pz&8jf1z$m6KZ)+}1|^ z?alrTB#HU8i@jDCzo$(;a)G$aS6ixFl>ZVm#~9<qFG5SM5Ezhpc zDrZ3|V|cI?b3$^Xt<@b;jp&mSQUtBPXe?rSbe*FNG;A1oU8bks=Q`-7Bpq(9}lV ztL1UoGZ$t!Y*$%~1F^MuyS7bHF4ndRVyDge!~OgN!kNEuRPd08G2OG;XCoUuw}lvM zJk0P?==*SLJt=Y9|C0I+Vvhe?L5c0Nsj(d%IZ5X;F$sRY{^zSlh!gyKcKrMQ+!9bK zdrsGkM=%(O2LvsLhod5I^DqjjD6_~bA)ywEGDL|@AYqwReIpYpZN_vxxA_NJji>sL zJ;KBwDOg!Q+VW~`LE6h83DT5caN+L#yJrg6;v*SF^OFEI4^m(HW_ZCWM*j7e5ut~( zFg-Ff9HnzZ2#Qc%Jc9_Guz*WNd++k1$^mo}^dCcU!auPQF_JoupUa0l2lROTv*$XH zi3X3a`EA5UB+{weGX*eL!A*I##?W2F8EX6k9R5IVv>CW%hE|$tlG=e2S97C+1))41 z;4s=2Ygk>Zdj9;qmzRLB+qYK8W;s>UGMv7l5U77jY3GwCLce=RjIEA&K!QsNfbdC& z_gUbAP?3q79&zk4!MK#2I{(`tl)1MJg;)i!Lx`k1l|JcJrhu+Sf1VM5K-^|-6fmATX-IjqD9nUf;`dmMk<0+Sh!uI zFI9NV4*=52a)cna7(J?Bv@MItix9SIQuks$Gm1^RulviFXGAvH!oYyYjm>S?oM|^8 zsMfL{DNTYsk2disOH09QN4`W7){#$n)f#bjE+vEnn{#yySR~Cv z*155+qvmwlWI)Cs`K}l3{)lZmb~MWgc1FI7VRBm-FfN;* zOcPy}<|DY`iTK0hlR;bgli8=>loQX|c{*Pjq8wnBNQy7d`GSFSP>&`%)2ihXTIwiy z1#tTzkyWy~pv|eClCD+Pxma4&S^nR0!}zlqfVXp=f5g{YvL7U0klr<1JqoVIzNB$k z;ESEz4~E0K4V&Ztj)-7%|E8EE_AAdtRQ`O|a(WI%1v43?I*xE$RHA94M-|=McE(yQ zH?>M7(2W-LS?aoum#S4OD=AICx&+=Qnf;S>_Kn_xTIO$o`{tQ6KD<;}&07=?4xb5BRvvhJXSMbHq-d7a zjTH&26B@QBP0~TWW+xNU9>b1Cl_>W&)t48W&LqS`zU14ydr59Uq77?pU(yJ~8MW}4 zACa6T2a8*Ui-;V!4lS%fYJzW*L&VLA1evVAQ3v3>IFdtfsUh$NQj7MI7*Ryj;r0ly zx3?itcx78Bm(@ljIlr+hXBm|5FbD!9-042Vjl`c;N(rJjug${pA}ddwXND6Fc`T8U zIZ65c5{?9dTJtanJo47AXFIfgx>M%NJkpa#P>?E>pM!0j=xk1*qQM%WsG3xKi-}t| zUjduY?h0WkqZ`yRVu%NYNAQ0ZK^2odD-I4-~2Mp4H~G=2ys@m zh@$1})kgdfGr7f^J`L#%azpM=YGYQ5UNl78`UjNk@3vL5W~UW~WHp8JcEN_57WF@I zj6cpV$11q|Z8(Z5jD=FCIQtZo{HDyl=UN?f;06s$@8^FQ$eTE+*B48tuW<3=!=?#Q zmznEe#FROuRV-gM9POSNZI45Bl2+hYY+#oA+h24~IA>ZG&$A?TS$%wQmbZ8+z0~PU zOP*U0#f^o9GY`W8Ab`Hx<|s0NE{Iy6Lmc|xwOKC;7bu=M3OPYDt)v9?Uy(pN=g)*=(+do zEw$hrW$V28cDrd;s%~ES%z{}Ox~s?{sS_;f;-lv_A^=7bssjcHG2f5naShXI{s#0g zEj!O?=2(V37hk_oQX;N6Ao9sy9ok`v6@$f-{8LH@S$JQ0zUN zZb(`(g2CNlRkEDg7asB)6+fVY>JvoJZFTa3&D*wYsYha+iJ0il-QFGp`l~BBI}VDl z;TE8#oCIDIg$e)0CX7lDP#(gXHy?71H))JBL;DA?1f}o%vLo3psLsBR!Lb5`4Pebd z=vP?4uu26>q#C(g7@koMivo@=ank7sLQWIxHxMZ`kr>sW5j$Cbv% zT0s9xv?vn>U(ZAmZ`2=!rXwZWy!9vSa!WaZpO&n!<()=?fDQtP#aat&6doDH` z>OMDqfhZlsyE&aopK=oZUM$7xFh%(Ukv`4D*kEdqDWiFPjN}H#E{`;#eh{;Z@<6}h z=HP(wNta9GUT*#a)qmcCmy|N*e5`)6p z{>y*dUYHq)o}1f1c_1u|jJuMI%>$wWfm~{3@H|Yqk+@ERO3;pFo;r= zfI&yyuGjy?+*?Og)wS)T8v|4nL;*<=L?uO98kKI4PU$WwDZ!*c5JXD4ySoJhq#LBA zyBp5Ec;5H>-rqOQZ=7+)IOmMB|9BpCuf1Zfd(L^sbzOJymocaEH}cP$>%DD?8Lg-4 z8=ovd_wMWgEoTK;S=qAPVbcQh;a#QxTDBrTfB}U-RTAT_iwM+%f)WswJp>8s?vA=~ zmq}Ut8(pS+rNYc$R^_g$Xv4GM0mI+Hlt={-8e#TVk9|)?s%vlFDEp#wAZP&MLge4e zZRUV9^aA$aUf_smEv)e`1m07do+^NDk^p^`gu6f(1>hoPRp=D_2ViV}d9lwFQ8Y&8 z4=Bw6h-eHEN)2b(K)PfBK~^^EsrB=q0{vD+Q8}oS?*zaiQk|_VyTB9*ju!hE`cX&3 znF9EAzb|4 z0+G!h2~)){${p5{VCWF(PZQ$Z0&M-#w=Nr0o`{kntVn#K2;YzN=^Oq4zTNNW*hCos zKB$HzXGqt_&o2b3o1mDyJ*XQ%Y*z}Bcsu~ZM4;(Hz69EVQ{q9Ijx!48e+rtP`U9hB z2?5T6Vmqtb=_5Rv^I0LE;OaMmpA38<6aKSPkm3!6q3weLEO7l0M)3!9Y^LrE0T!Px z^2ae(P$ci1e}`8$RRHqwFR0EI6tM7Y&g%+^r2;Qp(#D|rK7h4$pr14N1@^Tt8Twmz_n(mWpWsxL6buObQi-rH)^-dwbRY1nD}O-N+l`~ZP1 z0RW*89EWN8Hbnjxc(;c;#p8cLI;|)-cd;iq09+l4)ctq6rC5(1s{WN_Sa=f6?bxkz zkfqUpP1i0NK`v@NQlv5yu5oO)@nqV-(BinY8-Z@h0p~?$q(FAhV$}ES78LLSa#Eaa zi`Y4$qbEw{vLD#MhWMS<)7b+h8v|;WQSB?fz=kG17)s}4nyL?=Yt7Z2lR`LuLA{0I zcSPkRmimJdqTLRLbTk0B1Ah<0u{i4fp*XZoOr)+_6R-ghQOe38d3eE8~MquRN^u+ z8?0{u0*Jq2sr_stA`JEf{axZ^g(2OguJ|_sjnF`W?sbEBw>U#}9HVY_PxaXena@nJ zbTr$0st5RV`|{x5#1{9BRL<#IR;Sb98Ec8Zry%9D4W`@ydDkES?^mb8{%qb}umld{ z=DJY~6xk!*F``naRvBrNX`1@W>^3R~1Nct#7#TdkQ;c#L&4df(Ux|}X1cp8#Avx@N z05C{DkU`d6KSJlIz_;lSSMRW!=S8-fwG9`-=I5|mFbd0QB$p-WA~x{O_;~_@Z3PgV zENynlbC3uuJ6bKb-5YfPOy2Y%U_X6v@q=su0ml0F&py-E_iu@R(K<@oNvBlDz6|6& z?DQESdX!qCYa!nJ;snrENE;3Ptey;&e6ayF{eVosAJc$c1L28`I*ccN_9HR@6ge^mWh-Z=(sI^wMnD`! z^y;MyfAsH6Hz}zNiH!--&`(G_V&to?nVmy?#MC#zpci|pGRrCICCaMUH1`cWzvjNa zzSWAS=0Hp^#fae5)nd9-T*6kgGWC?`PVU%s@jmZ`>sV-4^;NMD8wQtAe?#+T8Kloq)e6c-~&MyEoP|64yxDX1mmVbs%?vky;C!3B?XsDHim=t0A2D23Qy z&l-)m;K%^5PHCB#^TtGAa|SB`dQimnG%Py vjc4)VLEX) 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

ey!k9;h$}@QhM+TBoF8S@S+N_U@!< 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

#>)1OPEoCIp__4*U$wsP3? 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 In)&Vj1BYziiU0rr literal 0 HcmV?d00001 From 20a99762fdd976996d3ba197a5699e753f6ca8fe Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 15:41:22 +0200 Subject: [PATCH 03/20] Update grammar submodule to latest Co-Authored-By: Claude Sonnet 5.5 --- grammar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar b/grammar index 8e92d1c3..49f7a9ab 160000 --- a/grammar +++ b/grammar @@ -1 +1 @@ -Subproject commit 8e92d1c3bfe1d84b80c200edb65f4f504f3ba7d5 +Subproject commit 49f7a9ab6a69a11e097482a1e885e2321cbb1dd7 From 27d8915ad21b422a69c545950aacb4876eccd0bc Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:18:23 +0200 Subject: [PATCH 04/20] Add top_processes tool, paused-process info and supervisor child counts to MCP inspector - top_processes ranks local processes by queue length, reductions or memory - debug_session lists processes stopped at a breakpoint; process_info gets a debugger field; a paused supervisor yields unavailable_while_paused - supervision_tree supervisors carry count_children totals Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 3 +- HELP.MD | 7 +- apps/erlangbridge/src/mcp/mcp_policy.erl | 2 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 197 ++++++++++++++++-- apps/erlangbridge/src/mcp/mcp_server.erl | 2 +- apps/erlangbridge/src/mcp/mcp_tools.erl | 24 ++- .../test/mcp/mcp_fixture_paused.erl | 16 ++ .../test/mcp/mcp_policy_SUITE.erl | 2 +- .../test/mcp/mcp_server_SUITE.erl | 104 ++++++++- mcp_security.md | 2 +- 10 files changed, 332 insertions(+), 27 deletions(-) create mode 100644 apps/erlangbridge/test/mcp/mcp_fixture_paused.erl diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b579510..292e4d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,14 @@ ## Unreleased * MCP topology inspector for debug sessions (opt-in) - - New settings `erlang.mcp.enabled` (default `false`), `erlang.mcp.host` (loopback only) and `erlang.mcp.port` (`0` = any free port). When enabled, a debug session (launch or attach, not *Run without debugging*) starts a read-only, token-authenticated [MCP](https://modelcontextprotocol.io) server inside the debugged Erlang node, so that an AI agent can map its OTP topology: `runtime_summary`, `application_overview`, `supervision_tree`, `registered_processes`, `process_info`, `ets_tables` (approved tables, metadata only) and `debug_session`. Results are bounded, paginated, with typed edges, evidence, confidence and explicit coverage limitations + - New settings `erlang.mcp.enabled` (default `false`), `erlang.mcp.host` (loopback only) and `erlang.mcp.port` (`0` = any free port). When enabled, a debug session (launch or attach, not *Run without debugging*) starts a read-only, token-authenticated [MCP](https://modelcontextprotocol.io) server inside the debugged Erlang node, so that an AI agent can map its OTP topology: `runtime_summary`, `application_overview`, `supervision_tree`, `registered_processes`, `process_info`, `top_processes` (hottest processes by queue length, reductions or memory), `ets_tables` (approved tables, metadata only) and `debug_session`. Results are bounded, paginated, with typed edges, evidence, confidence and explicit coverage limitations - VS Code registers the live endpoint automatically (MCP server list) and **Erlang MCP: Show Connection Details** helps configure other clients. The session token never appears in logs, DAP events or settings - An optional top-level `{mcp, [...]}` term in the project's `rebar.config` restricts tools, ETS tables and limits. Nothing changes when the setting is off. See [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) and [mcp_security.md](./mcp_security.md) - Agent-sized answers: graph tools default to `detail: "summary"` (compact entities and edges, 100 items per page, modules listed per application only); `detail: "full"` returns the complete JSON (e.g. to save it to a file), `pageSize` tunes the pages - **Output > Erlang MCP** journals every MCP request (tool, status, inspector time, size, pages, delay since the previous request) and summarizes the session, to see how an agent uses the inspector; arguments, results and token are never written - Optional user setting `erlang.mcp.authToken` (development convenience): a fixed token so that a static client such as Claude Code (`claude mcp add`) is configured once; default empty = random token per session - The verbose debug log no longer prints the Erlang cookie passed to an attach helper + - Supervisors in `supervision_tree` carry their child counts (`children`: specs, active, supervisors, workers); `debug_session` lists the processes stopped at a breakpoint (`pausedProcesses`), `process_info` adds a `debugger` field for such a process, and a supervisor that is itself stopped at a breakpoint is reported with the `unavailable_while_paused` omission instead of a plain timeout - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) --- diff --git a/HELP.MD b/HELP.MD index 638d754f..24a41a66 100644 --- a/HELP.MD +++ b/HELP.MD @@ -178,7 +178,7 @@ ceilings: {mcp, [ {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>]}, + <<"debug_session">>, <<"top_processes">>]}, {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>]; private tables never {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, @@ -200,7 +200,8 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `registered_processes` | locally registered names with status/queue length and membership (`confirmed`/`inferred`/`unknown`); `application`, `cursor` | | `process_info` | allowlisted fields of one process (by `id`, `name` or `pid`), links/monitors as ids; never dictionary, stack, mailbox or state | | `ets_tables` | metadata of the approved named tables (protection, type, owner, size, memory in words and bytes) - never keys or values | -| `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line) | +| `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | +| `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line), processes currently stopped at a breakpoint (`pausedProcesses`: process id, module, line) | Graph tools also take `detail` and `pageSize`. `detail: "summary"` (default) is meant for agents: same entities, ids and edges, without rarely needed fields (pids, descriptions, per-edge @@ -236,7 +237,7 @@ a breakpoint or busy): **Agent workflow**: `runtime_summary` + `application_overview` -> pick an application, follow its root ids into `supervision_tree` (expand subtrees with the ids in `limit_reached` omissions) -> -enrich with `process_info`, `registered_processes`, `ets_tables`, `debug_session` -> join by ids, +enrich with `process_info`, `registered_processes`, `top_processes`, `ets_tables`, `debug_session` -> join by ids, keep confirmed edges apart from inferences, and report the omissions. Module names can then be correlated with the workspace/LSP tools; the inspector never returns source or code paths. diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index 9172bf6a..d6a3e846 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -33,7 +33,7 @@ -define(TOOLS, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>]). + <<"debug_session">>, <<"top_processes">>]). %% {internal key, project key, JSON key, default = ceiling} -define(LIMITS, diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index ccfc3b2d..b81a98de 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -12,7 +12,7 @@ %% atomic snapshot. %% %% Cost notes (documented residual risk): application:which_applications/1, -%% registered/0, supervisor:which_children/1 and supervisor:count_children/1 +%% erlang:processes/0 (top_processes, capped at ?MAX_SCANNED entries), registered/0, supervisor:which_children/1 and supervisor:count_children/1 %% materialize lists in the target VM and cannot be interrupted once started; %% killing the worker only discards the late reply. -module(mcp_runtime). @@ -24,6 +24,8 @@ -define(MAX_SPEC_LOOKUPS, 200). %% default page size for agents (detail=summary); detail=full pages use max_items -define(SUMMARY_PAGE, 100). +-define(MAX_SCANNED, 200000). +-define(MAX_TOP, 50). %%------------------------------------------------------------------------------ %% Entry point @@ -60,6 +62,7 @@ run(<<"supervision_tree">>, Args, Ctx) -> supervision_tree(Args, Ctx); run(<<"registered_processes">>, Args, Ctx) -> registered_processes(Args, Ctx); run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); run(<<"ets_tables">>, Args, Ctx) -> ets_tables(Args, Ctx); +run(<<"top_processes">>, Args, Ctx) -> top_processes(Args, Ctx); run(_, _, _) -> {error, <<"unknown_tool">>, <<"unknown tool">>}. %% A deadline-bounded call in a monitored worker. On timeout the worker is @@ -138,6 +141,8 @@ debug_session(#{enc := Enc} = _Ctx) -> Max = maps:get(max_items, Enc), {Interpreted, IUnavailable} = debug_call(fun() -> int:interpreted() end, []), {Breaks, BUnavailable} = debug_call(fun() -> int:all_breaks() end, []), + {Snapshot, SUnavailable} = debug_call(fun() -> int:snapshot() end, []), + Paused = paused_list(Snapshot), Mods = [atom_to_binary(M, utf8) || M <- lists:sublist(lists:sort(Interpreted), Max), is_atom(M)], Bps = [#{<<"module">> => atom_to_binary(M, utf8), <<"line">> => L} || {{M, L}, _} <- lists:sublist(lists:sort(Breaks), Max), is_atom(M), is_integer(L)], @@ -148,11 +153,21 @@ debug_session(#{enc := Enc} = _Ctx) -> <<"nodeConnected">> => true, <<"interpretedModules">> => Mods, <<"breakpoints">> => Bps, - <<"debuggerMetadata">> => case IUnavailable orelse BUnavailable of + <<"pausedProcesses">> => [#{<<"id">> => pid_id(P), + <<"name">> => case erlang:process_info(P, registered_name) of + {registered_name, N} -> bin(N); + _ -> null + end, + <<"status">> => <<"break">>, + <<"module">> => atom_to_binary(M, utf8), + <<"line">> => L} + || {P, M, L} <- lists:sublist(Paused, Max)], + <<"debuggerMetadata">> => case IUnavailable orelse BUnavailable orelse SUnavailable of true -> <<"unavailable">>; false -> <<"available">> end, - <<"truncated">> => length(Interpreted) > Max orelse length(Breaks) > Max}}. + <<"truncated">> => length(Interpreted) > Max orelse length(Breaks) > Max + orelse length(Paused) > Max}}. debug_call(Fun, Default) -> case bounded(Fun, 1500) of @@ -160,6 +175,25 @@ debug_call(Fun, Default) -> _ -> {Default, true} end. +%% int:snapshot/0 -> [{Pid, Mod, Line}] of the interpreted processes stopped at a +%% breakpoint (status `break`). Only local pids and module/line are kept. +paused_list(Snapshot) -> + lists:sort([{P, M, L} || {P, _Init, break, {M, L}} <- Snapshot, + is_pid(P), node(P) =:= node(), is_atom(M), is_integer(L)]). + +%% -> {ok, #{Pid => {Mod, Line}}} | unavailable (debugger evidence only) +paused_map() -> + case bounded(fun() -> int:snapshot() end, 500) of + {ok, L} when is_list(L) -> {ok, maps:from_list([{P, {M, Line}} || {P, M, Line} <- paused_list(L)])}; + _ -> unavailable + end. + +paused_at(Pid) -> + case paused_map() of + {ok, M} -> maps:find(Pid, M); + unavailable -> error + end. + mode_text(launch) -> <<"launch">>; mode_text(attach) -> <<"attach">>; mode_text(_) -> <<"unknown">>. @@ -176,6 +210,13 @@ add_entity(#{ent_ids := Ids, ents := Es} = B, Id, Entity) -> false -> B#{ents := [Entity#{<<"id">> => Id} | Es], ent_ids := Ids#{Id => true}} end. +%% add fields to an entity already in the builder (add_entity keeps the first one) +merge_entity(#{ents := Es} = B, Id, Fields) -> + B#{ents := [case E of + #{<<"id">> := Id} -> maps:merge(E, Fields); + _ -> E + end || E <- Es]}. + add_rel(#{rel_keys := Keys, rels := Rs} = B, Type, From, To, Evidence, Confidence) -> Key = {Type, From, To}, case maps:is_key(Key, Keys) of @@ -614,18 +655,30 @@ walk([{Sup, Depth} | Rest], #{ctx := Ctx} = Cfg, Visited, B) -> {SupId, B1} = process_entity(Sup, #{<<"role">> => <<"supervisor">>, <<"application">> => app_text(Cfg)}, B), case children_of(Sup, Ctx) of - {ok, Children} -> + {ok, Children, Counts} -> + B1c = merge_entity(B1, SupId, #{<<"children">> => counts_map(Counts)}), {B2, Next} = lists:foldl( fun(C, {Acc, Q}) -> child(C, Sup, SupId, Depth, Cfg, Acc, Q) end, - {B1, []}, Children), + {B1c, []}, Children), walk(Rest ++ lists:reverse(Next), Cfg, Visited#{Sup => true}, B2); - {too_many, N} -> - B2 = add_omission(B1, <<"limit_reached">>, N, SupId, + {too_many, N, Counts} -> + B1c = merge_entity(B1, SupId, #{<<"children">> => counts_map(Counts)}), + B2 = add_omission(B1c, <<"limit_reached">>, N, SupId, <<"too many children to list safely; children not listed">>, false), walk(Rest, Cfg, Visited#{Sup => true}, B2); timeout -> - B2 = add_omission(B1, <<"timeout">>, 1, SupId, - <<"the supervisor did not answer in time; its children are missing from this map">>, false), + B2 = case paused_at(Sup) of + {ok, {PM, PL}} -> + add_omission(B1, <<"unavailable_while_paused">>, 1, SupId, + iolist_to_binary( + [<<"the supervisor is stopped at a breakpoint (">>, bin(PM), $:, + integer_to_binary(PL), + <<"); its children are missing from this map until it is resumed">>]), + false); + _ -> + add_omission(B1, <<"timeout">>, 1, SupId, + <<"the supervisor did not answer in time; its children are missing from this map">>, false) + end, walk(Rest, Cfg, Visited#{Sup => true}, B2); gone -> B2 = add_omission(B1, <<"disappeared">>, 1, SupId, @@ -637,16 +690,16 @@ walk([{Sup, Depth} | Rest], #{ctx := Ctx} = Cfg, Visited, B) -> app_text(#{app := undefined}) -> null; app_text(#{app := App}) -> bin(App). -%% -> {ok, [{ChildId, Child, Type, Modules, SpecMeta}]} | {too_many, N} | timeout | gone +%% -> {ok, [{ChildId, Child, Type, Modules, SpecMeta}], Counts} | {too_many, N, Counts} | timeout | gone children_of(Sup, Ctx) -> Fun = fun() -> Counts = supervisor:count_children(Sup), Total = proplists:get_value(specs, Counts, 0), case Total > ?MAX_LISTED_CHILDREN of - true -> {too_many, Total}; + true -> {too_many, Total, Counts}; false -> Cs = supervisor:which_children(Sup), - {ok, with_specs(Sup, Cs, 0, [])} + {ok, with_specs(Sup, Cs, 0, []), Counts} end end, %% one third of the request budget per supervisor: a blocked supervisor @@ -658,6 +711,13 @@ children_of(Sup, Ctx) -> {error, _} -> gone end. +%% supervisor:count_children/1 as an entity field: how many child specs, running +%% children, supervisors and workers (never child arguments). +counts_map(Counts) -> + maps:from_list([{atom_to_binary(K, utf8), V} + || K <- [specs, active, supervisors, workers], + {K2, V} <- Counts, K2 =:= K, is_integer(V)]). + with_specs(_Sup, [], _N, Acc) -> lists:reverse(Acc); with_specs(Sup, [{Id, Child, Type, Mods} | T], N, Acc) -> Meta = case N < ?MAX_SPEC_LOOKUPS of @@ -930,7 +990,13 @@ build_process_info(Pid, Info, Started, Args, #{enc := Enc} = Ctx, Config) -> <<"membership">> => #{<<"confidence">> => Conf, <<"evidence">> => Evidence}, <<"callbackModule">> => <<"unknown">>, <<"behaviour">> => <<"unknown">>}, - {Id, B1} = process_entity(Pid, Fields, new_b()), + Fields1 = case paused_at(Pid) of + {ok, {PM, PL}} -> + Fields#{<<"debugger">> => #{<<"status">> => <<"break">>, + <<"module">> => bin(PM), <<"line">> => PL}}; + _ -> Fields + end, + {Id, B1} = process_entity(Pid, Fields1, new_b()), Links = [L || L <- proplists:get_value(links, Info, []), is_pid(L) orelse is_port(L)], Mons = [M || {process, M} <- proplists:get_value(monitors, Info, []), is_pid(M)], {B2, Omitted1} = peers(Links, <<"linked_to">>, <<"process_info links">>, Id, Max, B1), @@ -945,7 +1011,8 @@ build_process_info(Pid, Info, Started, Args, #{enc := Enc} = Ctx, Config) -> <<"limitations">> => [<<"Links and monitors do not imply supervision or message traffic.">>, <<"Callback module and behaviour are only reported when supervisor child metadata is available (see supervision_tree).">>, - <<"Process dictionary, stack, mailbox and state are never read.">>]}, + <<"Process dictionary, stack, mailbox and state are never read.">>, + <<"The debugger field is only present for a process the debugger reports stopped at a breakpoint.">>]}, finish_graph(<<"process_info">>, Args, Scope, B4, Started, Ctx). peers(Peers, Type, Evidence, FromId, Max, B) -> @@ -1003,6 +1070,108 @@ resolve_process(Name) when is_binary(Name) -> catch _:_ -> {error, <<"not_found">>, <<"no process is registered under this name">>} end. +%%------------------------------------------------------------------------------ +%% top_processes (ranking by queue length / reductions / memory) +%%------------------------------------------------------------------------------ + +top_processes(Args, Ctx) -> + Started = mcp_store:now_ms(), + SortBy = sort_item(maps:get(<<"sortBy">>, Args, <<"message_queue_len">>)), + Limit = min(maps:get(<<"limit">>, Args, 10), ?MAX_TOP), + All = erlang:processes(), + Total = length(All), + {Scan, Cut} = case Total > ?MAX_SCANNED of + true -> {lists:sublist(All, ?MAX_SCANNED), Total - ?MAX_SCANNED}; + false -> {All, 0} + end, + {Keyed, Unscanned} = rank_keys(Scan, SortBy, self(), Ctx, 0, []), + %% a few spare candidates: inspector processes are filtered out below + Ranked = lists:sublist(lists:reverse(lists:sort(Keyed)), Limit + 8), + Masters = masters(Ctx), + {B1, Gone, _} = lists:foldl( + fun({_, Pid}, {Acc, G, Rank}) when Rank =< Limit -> + case top_entry(Pid, SortBy, Rank, Masters, Acc) of + skip -> {Acc, G, Rank}; + gone -> {Acc, G + 1, Rank}; + {ok, Acc1} -> {Acc1, G, Rank + 1} + end; + (_, Done) -> Done + end, {new_b(), 0, 1}, Ranked), + B2 = case Gone of + 0 -> B1; + _ -> add_omission(B1, <<"disappeared">>, Gone, undefined, + <<"ranked processes that exited before they could be described">>, false) + end, + B3 = case Unscanned of + 0 -> B2; + _ -> add_omission(B2, <<"timeout">>, Unscanned, undefined, + <<"the time budget ended the scan; the ranking only covers the processes scanned so far">>, false) + end, + B4 = case Cut of + 0 -> B3; + _ -> add_omission(B3, <<"limit_reached">>, Cut, undefined, + <<"more processes than the scan limit; the ranking only covers the first ones">>, false) + end, + Scope = #{<<"tool">> => <<"top_processes">>, + <<"sortBy">> => atom_to_binary(SortBy, utf8), + <<"limit">> => Limit, + <<"processCount">> => Total, + <<"limitations">> => + [<<"A point-in-time scan, not an atomic snapshot: processes start and exit while it runs.">>, + <<"reductions are cumulative since the process started, not a rate; memory is in bytes.">>, + <<"Only metadata is returned: never mailbox, dictionary, stack or state.">>, + <<"Inspector processes are excluded.">>]}, + finish_graph(<<"top_processes">>, Args, Scope, B4, Started, Ctx). + +sort_item(<<"reductions">>) -> reductions; +sort_item(<<"memory">>) -> memory; +sort_item(_) -> message_queue_len. + +%% -> {[{Value, Pid}], NotScanned}: stops early when the time budget is used up +rank_keys([], _Item, _Self, _Ctx, _N, Acc) -> {Acc, 0}; +rank_keys([Pid | T] = Pids, Item, Self, Ctx, N, Acc) -> + case N rem 1000 =:= 0 andalso N > 0 andalso remaining(Ctx) =:= 0 of + true -> {Acc, length(Pids)}; + false -> + Acc1 = case Pid =/= Self andalso erlang:process_info(Pid, Item) of + {Item, V} when is_integer(V) -> [{V, Pid} | Acc]; + _ -> Acc + end, + rank_keys(T, Item, Self, Ctx, N + 1, Acc1) + end. + +top_entry(Pid, SortBy, Rank, Masters, B) -> + Items = [registered_name, status, current_function, initial_call, reductions, memory, message_queue_len], + case erlang:process_info(Pid, Items) of + undefined -> gone; + Info -> + case lists:keyfind(registered_name, 1, Info) of + {registered_name, N} when is_atom(N) -> + case inspector_name(N) of + true -> skip; + false -> top_entity(Pid, Info, SortBy, Rank, Masters, B) + end; + _ -> top_entity(Pid, Info, SortBy, Rank, Masters, B) + end + end. + +top_entity(Pid, Info, SortBy, Rank, Masters, B) -> + {Conf, App, Evidence} = membership(Pid, #{}, Masters), + Fields = #{<<"role">> => <<"unknown">>, + <<"rank">> => Rank, + <<"rankedBy">> => atom_to_binary(SortBy, utf8), + <<"status">> => bin(proplists:get_value(status, Info, unknown)), + <<"currentFunction">> => mfa_text(proplists:get_value(current_function, Info)), + <<"initialCall">> => mfa_text(proplists:get_value(initial_call, Info)), + <<"reductions">> => proplists:get_value(reductions, Info, 0), + <<"memory">> => proplists:get_value(memory, Info, 0), + <<"memoryUnit">> => <<"bytes">>, + <<"messageQueueLen">> => proplists:get_value(message_queue_len, Info, 0), + <<"application">> => case App of undefined -> null; _ -> bin(App) end, + <<"membership">> => #{<<"confidence">> => Conf, <<"evidence">> => Evidence}}, + {_, B1} = process_entity(Pid, Fields, B), + {ok, B1}. + %%------------------------------------------------------------------------------ %% ets_tables (metadata of approved named tables only) %%------------------------------------------------------------------------------ diff --git a/apps/erlangbridge/src/mcp/mcp_server.erl b/apps/erlangbridge/src/mcp/mcp_server.erl index 85c52a40..ca104a48 100644 --- a/apps/erlangbridge/src/mcp/mcp_server.erl +++ b/apps/erlangbridge/src/mcp/mcp_server.erl @@ -400,7 +400,7 @@ dispatch(<<"initialize">>, Params, Id, _Config) -> "then application_overview (no modules), pick the application and follow its 'roots' " "ids with supervision_tree (expand deeper subtrees with the ids given in 'limit_reached' " "omissions), then process_info / registered_processes(application=id) / ets_tables / " - "debug_session only where needed. Ids are reusable across tools. Default detail=summary " + "debug_session only where needed; top_processes finds hot or stuck processes in one call. Ids are reusable across tools. Default detail=summary " "keeps answers small; follow nextCursor only if you need the remaining items. Use " "detail=full only when the user asks for the complete JSON (e.g. to save it to a file). " "Maps are bounded observation intervals, not atomic snapshots: honour complete/" diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 0308a4a0..35e2fa1d 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -1,4 +1,4 @@ -%% The seven read-only tools: descriptions, strict input schemas, output +%% The eight read-only tools: descriptions, strict input schemas, output %% schemas and the argument validator. Unknown arguments are rejected. -module(mcp_tools). @@ -102,15 +102,33 @@ def(<<"ets_tables">>) -> <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), graph_output()}; +def(<<"top_processes">>) -> + {<<"Top processes">>, + <<"Rank the local processes of the node by message queue length, reductions or memory and return the top ones " + "(default 10, max 50) with status, current function, initial call, registered name and application membership " + "evidence. Use it to find hot, stuck or heavy processes without calling process_info on each one. A bounded " + "point-in-time scan: processes start and exit during it, reductions are cumulative since process start. " + "Never reads mailbox, dictionary, stack or state.">>, + obj(#{<<"sortBy">> => #{<<"type">> => <<"string">>, + <<"enum">> => [<<"message_queue_len">>, <<"reductions">>, <<"memory">>], + <<"description">> => <<"Ranking criterion. Default message_queue_len.">>}, + <<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 50, + <<"description">> => <<"Number of processes to return. Default 10.">>}, + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + graph_output()}; + def(<<"debug_session">>) -> {<<"Debug session">>, <<"Safe metadata of the current debug session: launch or attach, node connection, interpreted modules and " - "active breakpoint locations (module and line). No source, environment or credentials.">>, + "active breakpoint locations (module and line), and the processes currently stopped at a breakpoint (process id, " + "module and line). No source, environment or credentials.">>, obj(#{}, []), obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), <<"mode">> => str(), <<"nodeConnected">> => #{<<"type">> => <<"boolean">>}, <<"interpretedModules">> => #{<<"type">> => <<"array">>}, - <<"breakpoints">> => #{<<"type">> => <<"array">>}}, + <<"breakpoints">> => #{<<"type">> => <<"array">>}, + <<"pausedProcesses">> => #{<<"type">> => <<"array">>}}, [<<"schemaVersion">>, <<"sessionId">>])}. structured_error_schema() -> diff --git a/apps/erlangbridge/test/mcp/mcp_fixture_paused.erl b/apps/erlangbridge/test/mcp/mcp_fixture_paused.erl new file mode 100644 index 00000000..4c30c326 --- /dev/null +++ b/apps/erlangbridge/test/mcp/mcp_fixture_paused.erl @@ -0,0 +1,16 @@ +%% Test-only module interpreted by the debugger so that a process can be stopped +%% at a breakpoint (see mcp_server_SUITE:paused_process_is_reported_from_debugger_evidence). +-module(mcp_fixture_paused). + +-export([run/0, handler/1, break_line/0]). + +run() -> + X = 1, + Y = X + 1, + Y. + +%% called by the debugger when an interpreted process reaches a breakpoint +handler(_Pid) -> ok. + +%% line of `Y = X + 1` in run/0 +break_line() -> 9. diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index ed3086a4..4f2caf0f 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -64,7 +64,7 @@ defaults_without_project_file(Config) -> ?assertEqual(mcp_policy:all_tools(), maps:get(allowed_tools, C)), ?assertEqual([], maps:get(allowed_ets_tables, C)), ?assertEqual(mcp_policy:ceilings(), maps:get(limits, C)), - ?assertEqual(7, length(maps:get(allowed_tools, C))), + ?assertEqual(8, length(maps:get(allowed_tools, C))), %% a project without an mcp term uses the same built-in policy write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), ?assertEqual({ok, C}, mcp_policy:resolve(input(Config, #{}))). diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index f8e332e6..0618532e 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -27,6 +27,9 @@ all() -> process_info_inputs_are_safe, registered_processes_membership_evidence, ets_tables_metadata_only_for_approved_tables, + top_processes_ranks_and_is_bounded, + supervision_tree_reports_child_counts, + paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, collection_byte_budget_is_partial_and_non_resumable, @@ -153,7 +156,7 @@ decode(Bin) -> %% unless the case chooses the detail itself (see agent_summary_is_compact). call(Config, Tool, Args0) when is_map(Args0) -> Graph = [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, - <<"process_info">>, <<"ets_tables">>], + <<"process_info">>, <<"ets_tables">>, <<"top_processes">>], Args = case lists:member(Tool, Graph) andalso not maps:is_key(<<"detail">>, Args0) of true -> Args0#{<<"detail">> => <<"full">>}; false -> Args0 @@ -309,7 +312,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), ?assertEqual(lists:sort(mcp_policy:all_tools()), Names), - ?assertEqual(7, length(Tools)), + ?assertEqual(8, length(Tools)), [begin ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, <<"outputSchema">> := #{<<"type">> := <<"object">>}, @@ -988,6 +991,103 @@ agent_summary_is_compact(Config) -> lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, supervisor:which_children(mcp_fx_dyn_sup)). +top_processes_ranks_and_is_bounded(Config) -> + Hot = spawn(fun() -> register(mcp_fx_hot, self()), receive never -> ok end end), + wait_until(fun() -> whereis(mcp_fx_hot) =:= Hot end), + [Hot ! {msg, I} || I <- lists:seq(1, 600)], + S = ok_call(Config, <<"top_processes">>, #{<<"limit">> => 3}), + Ents = maps:get(<<"entities">>, S), + ?assertEqual(3, length(Ents)), + ?assertEqual([1, 2, 3], [maps:get(<<"rank">>, E) || E <- Ents]), + [First | _] = Ents, + ?assertMatch(#{<<"name">> := <<"mcp_fx_hot">>, <<"messageQueueLen">> := Q, <<"rankedBy">> := <<"message_queue_len">>} + when Q >= 600, First), + Qs = [maps:get(<<"messageQueueLen">>, E) || E <- Ents], + ?assertEqual(lists:reverse(lists:sort(Qs)), Qs), + ?assertMatch(#{<<"sortBy">> := <<"message_queue_len">>, <<"limit">> := 3, <<"processCount">> := _}, + maps:get(<<"scope">>, S)), + %% metadata only: no mailbox content, no relationships, no inspector processes + ?assertEqual([], maps:get(<<"relationships">>, S)), + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [S])), "SENTINEL|\\{msg")), + [?assertEqual(false, lists:member(maps:get(<<"name">>, E), [<<"mcp_server">>, <<"mcp_store">>, <<"mcp_sup">>])) + || E <- Ents], + %% the other criteria rank by their own value + lists:foreach(fun({By, Field}) -> + R = ok_call(Config, <<"top_processes">>, #{<<"sortBy">> => By, <<"limit">> => 5}), + Es = maps:get(<<"entities">>, R), + ?assertEqual(5, length(Es)), + Vs = [maps:get(Field, E) || E <- Es], + ?assertEqual(lists:reverse(lists:sort(Vs)), Vs), + [?assertEqual(By, maps:get(<<"rankedBy">>, E)) || E <- Es] + end, [{<<"reductions">>, <<"reductions">>}, {<<"memory">>, <<"memory">>}]), + %% a default call is bounded to 10 and the ids join with other tools + D = ok_call(Config, <<"top_processes">>, #{}), + ?assertEqual(10, length(maps:get(<<"entities">>, D))), + HotId = id_of(ent(<<"mcp_fx_hot">>, maps:get(<<"entities">>, D))), + ?assertEqual(HotId, id_of(ent(<<"mcp_fx_hot">>, maps:get(<<"entities">>, + ok_call(Config, <<"process_info">>, #{<<"id">> => HotId}))))), + %% strict arguments + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"top_processes">>, #{<<"limit">> => 51})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"top_processes">>, #{<<"limit">> => 0})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"top_processes">>, #{<<"sortBy">> => <<"links">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"top_processes">>, #{<<"mailbox">> => true})), + exit(Hot, kill). + +supervision_tree_reports_child_counts(Config) -> + Pages = all_pages(Config, <<"supervision_tree">>, #{<<"id">> => fixture_root_id(Config)}), + Ents = entities(Pages), + Sub = hd([E || #{<<"childId">> := <<"mcp_fx_sub_sup">>} = E <- Ents]), + ?assertMatch(#{<<"children">> := #{<<"specs">> := 2, <<"active">> := 2, + <<"supervisors">> := 0, <<"workers">> := 2}}, Sub), + Root = hd([E || #{<<"name">> := <<"mcp_fx_root">>} = E <- Ents]), + ?assertMatch(#{<<"children">> := #{<<"specs">> := 3, <<"active">> := 3, + <<"supervisors">> := 2, <<"workers">> := 1}}, Root), + %% workers carry no counts + ?assertEqual([], [E || #{<<"role">> := <<"worker">>} = E <- Ents, maps:is_key(<<"children">>, E)]). + +paused_process_is_reported_from_debugger_evidence(Config) -> + Quiet = ok_call(Config, <<"debug_session">>, #{}), + ?assertEqual([], maps:get(<<"pausedProcesses">>, Quiet)), + Mod = mcp_fixture_paused, + {module, Mod} = code:ensure_loaded(Mod), + case catch int:i(Mod) of + {module, Mod} -> + try + ok = int:auto_attach([break], {Mod, handler, []}), + ok = int:break(Mod, Mod:break_line()), + Pid = spawn(fun() -> Mod:run() end), + Line = Mod:break_line(), + wait_until(fun() -> + case ok_call(Config, <<"debug_session">>, #{}) of + #{<<"pausedProcesses">> := [_ | _]} -> true; + _ -> false + end + end), + #{<<"pausedProcesses">> := [Paused]} = ok_call(Config, <<"debug_session">>, #{}), + ?assertMatch(#{<<"status">> := <<"break">>, <<"module">> := <<"mcp_fixture_paused">>, + <<"line">> := Line, <<"id">> := _}, Paused), + %% the same process id joins with process_info, which names the breakpoint + Info = ok_call(Config, <<"process_info">>, + #{<<"pid">> => list_to_binary(pid_to_list(Pid))}), + PidText = list_to_binary(pid_to_list(Pid)), + [E] = [X || #{<<"pid">> := T} = X <- maps:get(<<"entities">>, Info), T =:= PidText], + ?assertEqual(maps:get(<<"id">>, Paused), id_of(E)), + ?assertMatch(#{<<"debugger">> := #{<<"status">> := <<"break">>, + <<"module">> := <<"mcp_fixture_paused">>, + <<"line">> := Line}}, E), + %% an ordinary process has no debugger field + Other = ok_call(Config, <<"process_info">>, #{<<"name">> => <<"mcp_fx_worker_c">>}), + ?assertEqual(false, maps:is_key(<<"debugger">>, ent(<<"mcp_fx_worker_c">>, maps:get(<<"entities">>, Other)))), + exit(Pid, kill) + after + catch int:auto_attach(false), + catch int:no_break(Mod), + catch int:n(Mod) + end; + Other2 -> + {skip, {debugger_unavailable, Other2}} + end. + collect_journal(0) -> []; collect_journal(N) -> receive {journal, M} -> [M | collect_journal(N - 1)] diff --git a/mcp_security.md b/mcp_security.md index 2ad19fd9..f5bb6d96 100644 --- a/mcp_security.md +++ b/mcp_security.md @@ -19,7 +19,7 @@ port. All requests, parameters and identifiers are untrusted. | Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | | Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | | Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | -| Least privilege | Seven fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | +| Least privilege | Eight fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | | No execution | No eval, `rpc:call`, shell, code loading, message sending, `sys` calls, tracing, breakpoint changes, ETS writes or reads | | Data minimisation | No process dictionary, stack, mailbox, state, child start arguments, application environment, source, code paths, cookies; ETS: metadata of approved, non-private named tables only; node host redacted by default; child identifiers are printed with a depth/size bound | | Safe identifiers | Registered names are resolved with `binary_to_existing_atom/2` only; pids must be local (`<0.N.M>`); no `binary_to_term/1` on input | From 9fa71a589fb8c130e8f28f48fe339d5fee747373 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:20:52 +0200 Subject: [PATCH 05/20] Report run queue and VM resource usage in MCP runtime_summary Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 2 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 17 +++++++++++++++++ apps/erlangbridge/src/mcp/mcp_tools.erl | 5 ++++- apps/erlangbridge/test/mcp/mcp_server_SUITE.erl | 7 +++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 292e4d3c..31db7a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Optional user setting `erlang.mcp.authToken` (development convenience): a fixed token so that a static client such as Claude Code (`claude mcp add`) is configured once; default empty = random token per session - The verbose debug log no longer prints the Erlang cookie passed to an attach helper - Supervisors in `supervision_tree` carry their child counts (`children`: specs, active, supervisors, workers); `debug_session` lists the processes stopped at a breakpoint (`pausedProcesses`), `process_info` adds a `debugger` field for such a process, and a supervisor that is itself stopped at a breakpoint is reported with the `unavailable_while_paused` omission instead of a plain timeout + - `runtime_summary` also reports the run queue length and process/atom/port/ETS counts against their VM limits (`resources`) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) --- diff --git a/HELP.MD b/HELP.MD index 24a41a66..22c6e68d 100644 --- a/HELP.MD +++ b/HELP.MD @@ -194,7 +194,7 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | Tool | Purpose | | --- | --- | -| `runtime_summary` | OTP/ERTS versions, node (host redacted by default), uptime, schedulers, process count, memory, session id | +| `runtime_summary` | OTP/ERTS versions, node (host redacted by default), uptime, schedulers, run queue, process/atom/port/ETS counts against their VM limits (`resources`), memory, session id | | `application_overview` | started applications, versions, declared dependencies, root supervisor ids, optional module names/behaviours; `application`, `includeModules`, `cursor` | | `supervision_tree` | typed `supervises` edges from an application/supervisor id or a named supervisor; child role, modules, restart/shutdown; `id`/`supervisor`, `maxDepth`, `includeModules`, `cursor` | | `registered_processes` | locally registered names with status/queue length and membership (`confirmed`/`inferred`/`unknown`); `application`, `cursor` | diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index b81a98de..960e8369 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -120,14 +120,31 @@ runtime_summary(Args, _Ctx) -> <<"uptimeMs">> => Uptime, <<"schedulers">> => erlang:system_info(schedulers), <<"processCount">> => erlang:system_info(process_count), + <<"schedulersOnline">> => erlang:system_info(schedulers_online), + <<"runQueue">> => erlang:statistics(run_queue), + <<"resources">> => #{<<"processes">> => resource(process_count, process_limit), + <<"atoms">> => resource(atom_count, atom_limit), + <<"ports">> => resource(port_count, port_limit), + <<"ets">> => resource(ets_count, ets_limit)}, <<"memory">> => #{<<"unit">> => <<"bytes">>, <<"total">> => proplists:get_value(total, Mem), + <<"code">> => proplists:get_value(code, Mem), <<"processes">> => proplists:get_value(processes, Mem), <<"system">> => proplists:get_value(system, Mem), <<"atom">> => proplists:get_value(atom, Mem), <<"binary">> => proplists:get_value(binary, Mem), <<"ets">> => proplists:get_value(ets, Mem)}}}. +%% {count, limit, usedPercent}: how close the node is to a VM limit; absent +%% (null) on an OTP release that does not report it. +resource(CountKey, LimitKey) -> + try {erlang:system_info(CountKey), erlang:system_info(LimitKey)} of + {C, L} when is_integer(C), is_integer(L), L > 0 -> + #{<<"count">> => C, <<"limit">> => L, <<"usedPercent">> => round(C * 1000 / L) / 10}; + _ -> null + catch _:_ -> null + end. + node_text(false) -> atom_to_binary(node(), utf8); node_text(true) -> case binary:split(atom_to_binary(node(), utf8), <<"@">>) of diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 35e2fa1d..425a3069 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -33,12 +33,15 @@ tool_def(Name) -> def(<<"runtime_summary">>) -> {<<"Runtime summary">>, <<"Bounded metadata of the debugged Erlang node: OTP/ERTS versions, node name (host redacted by default), " - "uptime, scheduler and process counts, memory summary and the opaque debug session id. " + "uptime, scheduler count, run queue length, process/atom/port/ETS counts against their VM limits, memory " + "summary and the opaque debug session id. " "Start here, then call application_overview.">>, obj(#{<<"redactNodeHost">> => bool(<<"Replace the host part of the node name by . Default true.">>)}, []), obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), <<"otpRelease">> => str(), <<"ertsVersion">> => str(), <<"node">> => str(), <<"uptimeMs">> => int(), <<"schedulers">> => int(), <<"processCount">> => int(), + <<"schedulersOnline">> => int(), <<"runQueue">> => int(), + <<"resources">> => #{<<"type">> => <<"object">>}, <<"memory">> => #{<<"type">> => <<"object">>}}, [<<"schemaVersion">>, <<"sessionId">>])}; diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 0618532e..dcd73d7d 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -370,6 +370,13 @@ runtime_summary_redacts_node_host(Config) -> ?assertEqual(erlang:system_info(schedulers), maps:get(<<"schedulers">>, S)), ?assert(maps:get(<<"processCount">>, S) > 10), ?assert(maps:get(<<"uptimeMs">>, S) >= 0), + ?assert(maps:get(<<"runQueue">>, S) >= 0), + ?assertEqual(erlang:system_info(schedulers_online), maps:get(<<"schedulersOnline">>, S)), + #{<<"processes">> := #{<<"count">> := PC, <<"limit">> := PL, <<"usedPercent">> := PP}, + <<"atoms">> := #{<<"count">> := AC, <<"limit">> := AL}} = maps:get(<<"resources">>, S), + ?assert(PC > 10 andalso PL >= PC andalso PP >= 0 andalso PP < 100), + ?assert(AC > 0 andalso AL > AC), + ?assertMatch(#{<<"code">> := C} when is_integer(C), maps:get(<<"memory">>, S)), ?assertMatch(#{<<"unit">> := <<"bytes">>, <<"total">> := T} when is_integer(T), maps:get(<<"memory">>, S)), %% opaque, non-secret session id shared with the map contract Sid = maps:get(<<"sessionId">>, S), From 999088be9b670b9f9bc51a3ea636cb0c8ff655bd Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:22:15 +0200 Subject: [PATCH 06/20] Add format=mermaid to MCP graph tools Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 3 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 62 ++++++++++++++++++- apps/erlangbridge/src/mcp/mcp_tools.erl | 24 +++++-- .../test/mcp/mcp_server_SUITE.erl | 27 ++++++++ 5 files changed, 107 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31db7a5e..4b73a94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - The verbose debug log no longer prints the Erlang cookie passed to an attach helper - Supervisors in `supervision_tree` carry their child counts (`children`: specs, active, supervisors, workers); `debug_session` lists the processes stopped at a breakpoint (`pausedProcesses`), `process_info` adds a `debugger` field for such a process, and a supervisor that is itself stopped at a breakpoint is reported with the `unavailable_while_paused` omission instead of a plain timeout - `runtime_summary` also reports the run queue length and process/atom/port/ETS counts against their VM limits (`resources`) + - Graph tools accept `format: "mermaid"`: the page is also returned as a ready-to-render Mermaid `graph TD`, built from the reported edges - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) --- diff --git a/HELP.MD b/HELP.MD index 22c6e68d..b61fd2ed 100644 --- a/HELP.MD +++ b/HELP.MD @@ -203,7 +203,8 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | | `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line), processes currently stopped at a breakpoint (`pausedProcesses`: process id, module, line) | -Graph tools also take `detail` and `pageSize`. `detail: "summary"` (default) is meant for agents: +Graph tools also take `detail`, `pageSize` and `format`. `format: "mermaid"` adds a `mermaid` field (a `graph TD` +of the returned page, built from the returned edges only) so that an agent can render the diagram without redrawing it. `detail: "summary"` (default) is meant for agents: same entities, ids and edges, without rarely needed fields (pids, descriptions, per-edge `observedAt`, coverage limitations after the first page), 100 items per page, and module lists only for one selected application. `detail: "full"` returns every field in the largest pages (policy diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index 960e8369..ff689710 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -47,7 +47,12 @@ call(Tool, Args, Ctx0) -> {undefined, full} -> MaxItems; {N, _} -> min(N, MaxItems) end, - Ctx = Ctx0#{enc => maps:with([max_depth, max_items, max_binary_bytes], maps:get(limits, Config)), + Format = case maps:get(<<"format">>, Args, <<"json">>) of + <<"mermaid">> -> mermaid; + _ -> json + end, + Ctx = Ctx0#{format => Format, + enc => maps:with([max_depth, max_items, max_binary_bytes], maps:get(limits, Config)), soft_deadline => now_mono() + (Timeout * 7) div 10, detail => Detail, page_size => PageSize}, case maps:find(<<"cursor">>, Args) of @@ -330,7 +335,7 @@ envelope(Tool, Hash, CollId, Meta, Page, Offset, More, Ctx) -> true -> mcp_store:make_cursor(CollId, Tool, Hash, Offset + length(Page)); false -> null end, - #{<<"schemaVersion">> => ?SV, + Env = #{<<"schemaVersion">> => ?SV, <<"sessionId">> => mcp_store:session_id(), <<"collectionId">> => CollId, <<"startedAt">> => mcp_encoder:iso8601(maps:get(started_at, Meta)), @@ -343,7 +348,58 @@ envelope(Tool, Hash, CollId, Meta, Page, Offset, More, Ctx) -> <<"truncated">> => maps:get(truncated, Meta), <<"omissions">> => maps:get(omissions, Meta), <<"nextCursor">> => Next, - <<"page">> => #{<<"offset">> => Offset, <<"items">> => length(Page)}}. + <<"page">> => #{<<"offset">> => Offset, <<"items">> => length(Page)}}, + case maps:get(format, Ctx, json) of + mermaid -> Env#{<<"mermaid">> => mermaid_graph(Entities, Rels)}; + json -> Env + end. + +%% A `graph TD` of one page, from the entities and relationships it returns. Labels +%% are reduced to a safe character set (they come from atoms of the debugged VM). +mermaid_graph(Entities, Rels) -> + Known = maps:from_list([{maps:get(<<"id">>, E), E} || E <- Entities]), + Missing = lists:usort([Id || R <- Rels, Id <- [maps:get(<<"from">>, R), maps:get(<<"to">>, R)], + not maps:is_key(Id, Known)]), + Nodes = [mermaid_node(E) || E <- Entities] + ++ [[<<" ">>, mermaid_id(Id), <<"[\"">>, mermaid_text(Id), <<"\"]\n">>] || Id <- Missing], + Edges = [mermaid_edge(R) || R <- Rels], + iolist_to_binary([<<"graph TD\n">>, Nodes, Edges]). + +mermaid_node(E) -> + Label = [mermaid_text(mermaid_label(E)), + case mermaid_detail(E) of + <<>> -> []; + D -> [<<"
">>, mermaid_text(D)] + end], + [<<" ">>, mermaid_id(maps:get(<<"id">>, E)), <<"[\"">>, Label, <<"\"]\n">>]. + +mermaid_edge(#{<<"type">> := Type, <<"from">> := F, <<"to">> := T}) -> + Arrow = case Type of + <<"supervises">> -> <<"-->">>; + _ -> <<"-.->">> + end, + [<<" ">>, mermaid_id(F), <<" ">>, Arrow, <<"|">>, mermaid_text(Type), <<"| ">>, mermaid_id(T), <<"\n">>]. + +mermaid_label(E) -> + first_binary([maps:get(K, E, null) || K <- [<<"name">>, <<"childId">>, <<"pid">>, <<"id">>]]). + +mermaid_detail(E) -> + Parts = [V || K <- [<<"role">>, <<"restart">>, <<"childState">>], V <- [maps:get(K, E, null)], + is_binary(V), V =/= <<"unknown">>], + iolist_to_binary(lists:join(<<" / ">>, Parts)). + +first_binary([B | _]) when is_binary(B) -> B; +first_binary([_ | T]) -> first_binary(T); +first_binary([]) -> <<"?">>. + +mermaid_id(Id) -> [<<"n_">>, re:replace(Id, "[^A-Za-z0-9_]", "_", [global, {return, binary}])]. + +mermaid_text(Bin) -> + Clean = re:replace(Bin, "[^A-Za-z0-9_ :/.@#-]", "_", [global, {return, binary}]), + case byte_size(Clean) > 60 of + true -> <<(binary:part(Clean, 0, 57))/binary, "...">>; + false -> Clean + end. %% detail=summary: same entities and edges, without the fields an agent rarely %% needs to reason about the topology (they stay available with detail=full). diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 425a3069..ef576718 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -54,7 +54,8 @@ def(<<"application_overview">>) -> obj(#{<<"application">> => id_prop(<<"Only this application (entity id from a previous call).">>), <<"includeModules">> => bool(<<"Also list module entities (with declared behaviours when the module is already loaded). Default false.">>), <<"cursor">> => cursor_prop(), - <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), graph_output()}; def(<<"supervision_tree">>) -> @@ -69,7 +70,8 @@ def(<<"supervision_tree">>) -> <<"maxDepth">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 16}, <<"includeModules">> => bool(<<"Add module entities and uses_module edges. Default false.">>), <<"cursor">> => cursor_prop(), - <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), graph_output()}; def(<<"registered_processes">>) -> @@ -79,7 +81,8 @@ def(<<"registered_processes">>) -> "never silently assigned to the selected application. Does not enumerate unregistered processes.">>, obj(#{<<"application">> => id_prop(<<"Only processes attributed to this application.">>), <<"cursor">> => cursor_prop(), - <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), graph_output()}; def(<<"process_info">>) -> @@ -93,7 +96,8 @@ def(<<"process_info">>) -> <<"pid">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 64, <<"description">> => <<"Local pid text such as <0.123.0>.">>}, <<"cursor">> => cursor_prop(), - <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), graph_output()}; def(<<"ets_tables">>) -> @@ -102,7 +106,8 @@ def(<<"ets_tables">>) -> "protection, type, owner entity id, size and memory with units. Private tables are excluded. Never reads keys, " "objects or values.">>, obj(#{<<"cursor">> => cursor_prop(), - <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), graph_output()}; def(<<"top_processes">>) -> @@ -118,7 +123,8 @@ def(<<"top_processes">>) -> <<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 50, <<"description">> => <<"Number of processes to return. Default 10.">>}, <<"cursor">> => cursor_prop(), - <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop()}, []), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), graph_output()}; def(<<"debug_session">>) -> @@ -159,6 +165,12 @@ str() -> #{<<"type">> => <<"string">>}. int() -> #{<<"type">> => <<"integer">>}. bool(Desc) -> #{<<"type">> => <<"boolean">>, <<"description">> => Desc}. id_prop(Desc) -> #{<<"type">> => <<"string">>, <<"maxLength">> => 64, <<"description">> => Desc}. +format_prop() -> + #{<<"type">> => <<"string">>, <<"enum">> => [<<"json">>, <<"mermaid">>], + <<"description">> => <<"json (default): entities and relationships only. mermaid: also return a 'mermaid' field " + "with a ready-to-render `graph TD` of this page (built from the returned edges, so it " + "cannot show a relationship the tools did not report).">>}. + detail_prop() -> #{<<"type">> => <<"string">>, <<"enum">> => [<<"summary">>, <<"full">>], <<"description">> => <<"summary (default): compact entities/edges, 100 items per page - best for reasoning. " diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index dcd73d7d..6d3cb662 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -29,6 +29,7 @@ all() -> ets_tables_metadata_only_for_approved_tables, top_processes_ranks_and_is_bounded, supervision_tree_reports_child_counts, + mermaid_format_renders_the_returned_edges, paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, @@ -1052,6 +1053,32 @@ supervision_tree_reports_child_counts(Config) -> %% workers carry no counts ?assertEqual([], [E || #{<<"role">> := <<"worker">>} = E <- Ents, maps:is_key(<<"children">>, E)]). +mermaid_format_renders_the_returned_edges(Config) -> + RootId = fixture_root_id(Config), + Plain = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId}), + ?assertEqual(false, maps:is_key(<<"mermaid">>, Plain)), + S = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId, <<"format">> => <<"mermaid">>, + <<"detail">> => <<"summary">>}), + ?assertEqual(false, maps:get(<<"truncated">>, S)), + Mermaid = maps:get(<<"mermaid">>, S), + [<<"graph TD">> | Lines] = binary:split(Mermaid, <<"\n">>, [global, trim_all]), + Edges = [L || L <- Lines, binary:match(L, <<"-->">>) =/= nomatch], + ?assertEqual(5, length(Edges)), + [?assertNotEqual(nomatch, binary:match(E, <<"|supervises|">>)) || E <- Edges], + ?assertEqual(5, length(supervises_edges([S]))), + [?assertNotEqual(nomatch, binary:match(Mermaid, N)) || N <- [<<"mcp_fx_root">>, <<"mcp_fx_sub_sup">>, <<"supervisor">>]], + %% only safe characters: nothing from the VM can break out of a label + ?assertEqual(nomatch, re:run(binary:replace(Mermaid, <<"
">>, <<" ">>, [global]), "[<>\"{}`;]\\w*[<>{}`;]", + [{capture, none}])), + ?assertEqual(nomatch, binary:match(Mermaid, <<"SENTINEL">>)), + %% a relationship whose endpoint is on another page still gets a node + Small = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId, <<"format">> => <<"mermaid">>, + <<"pageSize">> => 3}), + ?assertNotEqual(nomatch, binary:match(maps:get(<<"mermaid">>, Small), <<"graph TD">>)), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"supervision_tree">>, #{<<"id">> => RootId, <<"format">> => <<"dot">>})), + %% other graph tools accept it too + ?assertMatch(#{<<"mermaid">> := _}, ok_call(Config, <<"top_processes">>, #{<<"format">> => <<"mermaid">>})). + paused_process_is_reported_from_debugger_evidence(Config) -> Quiet = ok_call(Config, <<"debug_session">>, #{}), ?assertEqual([], maps:get(<<"pausedProcesses">>, Quiet)), From 21639a0dfbe2e4992dd1036e689eaafe3b594f98 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:25:19 +0200 Subject: [PATCH 07/20] Add topology_overview composite tool to MCP inspector Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 3 +- apps/erlangbridge/src/mcp/mcp_policy.erl | 2 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 145 ++++++++++++++++-- apps/erlangbridge/src/mcp/mcp_tools.erl | 24 +++ .../test/mcp/mcp_policy_SUITE.erl | 2 +- .../test/mcp/mcp_server_SUITE.erl | 53 ++++++- mcp_security.md | 2 +- 8 files changed, 210 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b73a94f..5f951eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Supervisors in `supervision_tree` carry their child counts (`children`: specs, active, supervisors, workers); `debug_session` lists the processes stopped at a breakpoint (`pausedProcesses`), `process_info` adds a `debugger` field for such a process, and a supervisor that is itself stopped at a breakpoint is reported with the `unavailable_while_paused` omission instead of a plain timeout - `runtime_summary` also reports the run queue length and process/atom/port/ETS counts against their VM limits (`resources`) - Graph tools accept `format: "mermaid"`: the page is also returned as a ready-to-render Mermaid `graph TD`, built from the reported edges + - New tool `topology_overview`: the application, its supervision tree, registered processes and owned approved ETS tables of one application in a single call (each part still subject to the project policy) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) --- diff --git a/HELP.MD b/HELP.MD index b61fd2ed..8e0c3760 100644 --- a/HELP.MD +++ b/HELP.MD @@ -178,7 +178,7 @@ ceilings: {mcp, [ {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>]}, + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>]}, {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>]; private tables never {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, @@ -200,6 +200,7 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `registered_processes` | locally registered names with status/queue length and membership (`confirmed`/`inferred`/`unknown`); `application`, `cursor` | | `process_info` | allowlisted fields of one process (by `id`, `name` or `pid`), links/monitors as ids; never dictionary, stack, mailbox or state | | `ets_tables` | metadata of the approved named tables (protection, type, owner, size, memory in words and bytes) - never keys or values | +| `topology_overview` | one call for one application (`name` or `application` id): the application, its supervision tree with child counts, registered processes and the approved ETS tables they own, as one collection. Parts whose own tool is not allowed by the policy are left out (`policy_denied` omission) | | `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | | `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line), processes currently stopped at a breakpoint (`pausedProcesses`: process id, module, line) | diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index d6a3e846..97cb6930 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -33,7 +33,7 @@ -define(TOOLS, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>]). + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>]). %% {internal key, project key, JSON key, default = ceiling} -define(LIMITS, diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index ff689710..9748192b 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -62,14 +62,24 @@ call(Tool, Args, Ctx0) -> run(<<"runtime_summary">>, Args, Ctx) -> runtime_summary(Args, Ctx); run(<<"debug_session">>, _Args, Ctx) -> debug_session(Ctx); -run(<<"application_overview">>, Args, Ctx) -> application_overview(Args, Ctx); -run(<<"supervision_tree">>, Args, Ctx) -> supervision_tree(Args, Ctx); -run(<<"registered_processes">>, Args, Ctx) -> registered_processes(Args, Ctx); +run(<<"application_overview">>, Args, Ctx) -> graph_tool(<<"application_overview">>, fun application_overview_build/2, Args, Ctx); +run(<<"supervision_tree">>, Args, Ctx) -> graph_tool(<<"supervision_tree">>, fun supervision_tree_build/2, Args, Ctx); +run(<<"registered_processes">>, Args, Ctx) -> graph_tool(<<"registered_processes">>, fun registered_processes_build/2, Args, Ctx); run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); -run(<<"ets_tables">>, Args, Ctx) -> ets_tables(Args, Ctx); +run(<<"ets_tables">>, Args, Ctx) -> graph_tool(<<"ets_tables">>, fun ets_tables_build/2, Args, Ctx); +run(<<"topology_overview">>, Args, Ctx) -> graph_tool(<<"topology_overview">>, fun topology_overview_build/2, Args, Ctx); run(<<"top_processes">>, Args, Ctx) -> top_processes(Args, Ctx); run(_, _, _) -> {error, <<"unknown_tool">>, <<"unknown tool">>}. +%% Graph tools build a Builder (entities, relationships, omissions) and a scope; +%% finish_graph retains it as a collection and renders the first page. +graph_tool(Tool, Build, Args, Ctx) -> + Started = mcp_store:now_ms(), + case Build(Args, Ctx) of + {ok, Scope, B} -> finish_graph(Tool, Args, Scope, B, Started, Ctx); + {error, _, _} = E -> E + end. + %% A deadline-bounded call in a monitored worker. On timeout the worker is %% killed (an inspector-owned process) and any late reply is discarded. %% -> {ok, Result} | timeout | {error, Reason} @@ -521,8 +531,7 @@ root_info(App, Ctx) -> _ -> no_start_module end. -application_overview(Args, #{enc := Enc} = Ctx) -> - Started = mcp_store:now_ms(), +application_overview_build(Args, #{enc := Enc} = Ctx) -> IncludeModules0 = maps:get(<<"includeModules">>, Args, false), %% listing the modules of every application floods an agent: with %% detail=summary, modules are only listed for a selected application @@ -579,7 +588,7 @@ application_overview(Args, #{enc := Enc} = Ctx) -> <<"Declared dependencies (depends_on) are declarations from the application resource, not evidence of runtime communication.">>, <<"Root discovery uses the application master; rootStatus explains when no root is available.">>, <<"Application environment values, code paths and callbacks are never read or invoked.">>]}, - finish_graph(<<"application_overview">>, Args, Scope, B3, Started, Ctx) + {ok, Scope, B3} end. select_application(undefined) -> {ok, all}; @@ -653,8 +662,7 @@ app_entities(App, Desc, Vsn, _StartedNames, IncludeModules, Enc, Ctx, B0) -> %% supervision_tree %%------------------------------------------------------------------------------ -supervision_tree(Args, #{config := Config} = Ctx) -> - Started = mcp_store:now_ms(), +supervision_tree_build(Args, #{config := Config} = Ctx) -> MaxDepth = min(maps:get(<<"maxDepth">>, Args, 16), mcp_policy:limit(max_traversal_depth, Config)), IncludeModules = maps:get(<<"includeModules">>, Args, false), case tree_start(Args, Ctx) of @@ -667,7 +675,7 @@ supervision_tree(Args, #{config := Config} = Ctx) -> [<<"Only 'supervises' edges from supervisor:which_children/1 are emitted; links and monitors are never supervision.">>, <<"Unsupervised, unregistered and transient processes are not part of this map.">>, <<"Child start arguments are never returned.">>]}, - finish_graph(<<"supervision_tree">>, Args, Scope, B1, Started, Ctx) + {ok, Scope, B1} end. %% -> {ok, [RootPid], AppName | undefined, Scope, Builder} | {error, Code, Msg} @@ -913,8 +921,7 @@ module_edges(_, _, B) -> B. %% registered_processes %%------------------------------------------------------------------------------ -registered_processes(Args, #{enc := Enc, config := Config} = Ctx) -> - Started = mcp_store:now_ms(), +registered_processes_build(Args, #{enc := Enc, config := Config} = Ctx) -> case select_application(maps:get(<<"application">>, Args, undefined)) of {error, _, _} = E -> E; {ok, Selected} -> @@ -948,7 +955,7 @@ registered_processes(Args, #{enc := Enc, config := Config} = Ctx) -> [<<"Only locally registered names are listed; unregistered processes are not enumerated.">>, <<"A registered name alone never confirms application membership.">>, <<"Inspector processes are excluded.">>]}, - finish_graph(<<"registered_processes">>, Args, Scope, B3, Started, Ctx) + {ok, Scope, B3} end. masters(Ctx) -> @@ -1143,6 +1150,113 @@ resolve_process(Name) when is_binary(Name) -> catch _:_ -> {error, <<"not_found">>, <<"no process is registered under this name">>} end. +%%------------------------------------------------------------------------------ +%% topology_overview: application + supervision tree + registered processes + +%% owned approved ETS tables of ONE application in a single collection. Each part is +%% included only when its own tool is allowed by the project policy. +%%------------------------------------------------------------------------------ + +topology_overview_build(Args, #{config := Config} = Ctx) -> + case overview_application(Args, Ctx) of + {error, _, _} = E -> E; + {ok, App} -> + AppId = app_id(App), + Allowed = fun(Tool) -> mcp_policy:tool_allowed(Tool, Config) end, + Part = fun(Tool, Build, PArgs) -> + case Allowed(Tool) of + true -> Build(PArgs, Ctx); + false -> denied + end + end, + Tree = Part(<<"supervision_tree">>, fun supervision_tree_build/2, #{<<"id">> => AppId}), + Over = Part(<<"application_overview">>, fun application_overview_build/2, #{<<"application">> => AppId}), + Reg = Part(<<"registered_processes">>, fun registered_processes_build/2, #{<<"application">> => AppId}), + Ets = Part(<<"ets_tables">>, fun ets_tables_build/2, #{}), + Parts = [{<<"supervision_tree">>, Tree}, {<<"application_overview">>, Over}, + {<<"registered_processes">>, Reg}, {<<"ets_tables">>, Ets}], + case [E || {_, {error, _, _} = E} <- Parts] of + [Err | _] -> Err; + [] -> merge_overview(AppId, Parts) + end + end. + +%% -> {ok, App} | {error, Code, Msg}: an entity id or the name of a started application +overview_application(#{<<"application">> := Id}, _Ctx) -> + case select_application(Id) of + {ok, {app, App}} -> {ok, App}; + {error, _, _} = E -> E + end; +overview_application(#{<<"name">> := Name}, Ctx) -> + try binary_to_existing_atom(Name, utf8) of + App -> + case started_apps(Ctx) of + {ok, Apps} -> + case lists:keymember(App, 1, Apps) of + true -> {ok, App}; + false -> {error, <<"not_found">>, <<"no started application has this name">>} + end; + _ -> {error, <<"timeout">>, <<"application_controller did not answer in time">>} + end + catch _:_ -> {error, <<"not_found">>, <<"no started application has this name">>} + end. + +%% The richest entity wins (the tree describes a supervisor better than the overview +%% does), so the tree is merged first; ETS tables only when their owner is in the map. +merge_overview(AppId, Parts) -> + Order = [<<"supervision_tree">>, <<"application_overview">>, <<"registered_processes">>], + Merged = lists:foldl( + fun(Tool, Acc) -> + case proplists:get_value(Tool, Parts) of + {ok, _, B} -> merge_builders(Acc, B); + _ -> Acc + end + end, new_b(), Order), + WithEts = case proplists:get_value(<<"ets_tables">>, Parts) of + {ok, _, EtsB} -> merge_owned_tables(Merged, EtsB); + _ -> Merged + end, + Denied = [Tool || {Tool, denied} <- Parts], + B1 = lists:foldl( + fun(Tool, Acc) -> + add_omission(Acc, <<"policy_denied">>, 1, undefined, + <<"part not included: the project policy does not allow ", Tool/binary>>, false) + end, WithEts, Denied), + Included = [Tool || {Tool, {ok, _, _}} <- Parts], + Limitations = lists:usort(lists:append([maps:get(<<"limitations">>, Sc, []) || {_, {ok, Sc, _}} <- Parts])), + Scope = #{<<"tool">> => <<"topology_overview">>, + <<"application">> => AppId, + <<"parts">> => Included, + <<"limitations">> => + [<<"One collection assembled from several observations made in sequence: it is an interval, not an atomic snapshot.">> + | Limitations]}, + {ok, Scope, B1}. + +merge_builders(Into, From) -> + B1 = lists:foldl(fun(E, Acc) -> add_entity(Acc, maps:get(<<"id">>, E), maps:remove(<<"id">>, E)) end, + Into, lists:reverse(maps:get(ents, From))), + B2 = lists:foldl(fun(#{<<"type">> := T, <<"from">> := F, <<"to">> := To} = R, #{rel_keys := Keys, rels := Rs} = Acc) -> + Key = {T, F, To}, + case maps:is_key(Key, Keys) of + true -> Acc; + false -> Acc#{rel_keys := Keys#{Key => true}, rels := [R | Rs]} + end + end, B1, lists:reverse(maps:get(rels, From))), + B3 = B2#{oms := maps:get(oms, B2) ++ maps:get(oms, From)}, + B3#{pids := maps:merge(maps:get(pids, B3), maps:get(pids, From))}. + +%% keep an approved table (and its owns_table edge) only when its owner is already in the map +merge_owned_tables(B, EtsB) -> + Have = maps:get(ent_ids, B), + Tables = maps:from_list([{maps:get(<<"id">>, E), E} || E <- maps:get(ents, EtsB), + maps:get(<<"kind">>, E) =:= <<"ets_table">>]), + Owned = [R || #{<<"type">> := <<"owns_table">>, <<"from">> := F, <<"to">> := T} = R <- lists:reverse(maps:get(rels, EtsB)), + maps:is_key(F, Have), maps:is_key(T, Tables)], + lists:foldl(fun(#{<<"to">> := T} = R, Acc) -> + Acc1 = add_entity(Acc, T, maps:remove(<<"id">>, maps:get(T, Tables))), + add_rel(Acc1, <<"owns_table">>, maps:get(<<"from">>, R), T, + maps:get(<<"evidence">>, R), maps:get(<<"confidence">>, R)) + end, B, Owned). + %%------------------------------------------------------------------------------ %% top_processes (ranking by queue length / reductions / memory) %%------------------------------------------------------------------------------ @@ -1249,8 +1363,7 @@ top_entity(Pid, Info, SortBy, Rank, Masters, B) -> %% ets_tables (metadata of approved named tables only) %%------------------------------------------------------------------------------ -ets_tables(Args, #{config := Config} = Ctx) -> - Started = mcp_store:now_ms(), +ets_tables_build(_Args, #{config := Config}) -> Names = maps:get(allowed_ets_tables, Config), {B, Denied, Gone} = lists:foldl( fun(Name, {Acc, D, G}) -> ets_entry(Name, Acc, D, G) end, @@ -1271,7 +1384,7 @@ ets_tables(Args, #{config := Config} = Ctx) -> [<<"Only tables approved by allowed_ets_tables are inspected; no node-wide inventory exists.">>, <<"Keys, objects and values are never read.">>, <<"memory is reported in words with memoryBytes derived from the VM word size.">>]}, - finish_graph(<<"ets_tables">>, Args, Scope, B2, Started, Ctx). + {ok, Scope, B2}. ets_entry(NameBin, B, Denied, Gone) -> Atom = try binary_to_existing_atom(NameBin, utf8) catch _:_ -> undefined end, diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index ef576718..60bde298 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -110,6 +110,21 @@ def(<<"ets_tables">>) -> <<"format">> => format_prop()}, []), graph_output()}; +def(<<"topology_overview">>) -> + {<<"Topology overview">>, + <<"One-call map of a single started application: the application with its declared dependencies and root, " + "its supervision tree (with child counts), its registered processes and the approved ETS tables owned by those " + "processes, as one collection of entities and typed relationships. Give the application by entity id or by " + "name. Parts whose own tool is not allowed by the project policy are left out and reported as a policy_denied " + "omission. For several applications, deeper subtrees or exports use the individual tools.">>, + obj(#{<<"application">> => id_prop(<<"Application entity id from a previous call.">>), + <<"name">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 255, + <<"description">> => <<"Name of a started application (must be an existing atom).">>}, + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), + graph_output()}; + def(<<"top_processes">>) -> {<<"Top processes">>, <<"Rank the local processes of the node by message queue length, reductions or memory and return the top ones " @@ -210,6 +225,15 @@ exclusive(<<"process_info">>, Args) -> 1 -> ok; _ -> {error, <<"exactly one of id, name or pid is required">>} end; +exclusive(<<"topology_overview">>, Args) -> + case maps:is_key(<<"cursor">>, Args) of + true -> ok; + false -> + case length([K || K <- [<<"application">>, <<"name">>], maps:is_key(K, Args)]) of + 1 -> ok; + _ -> {error, <<"exactly one of application or name is required">>} + end + end; exclusive(<<"supervision_tree">>, Args) -> case maps:is_key(<<"cursor">>, Args) of true -> ok; diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index 4f2caf0f..b36aa115 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -64,7 +64,7 @@ defaults_without_project_file(Config) -> ?assertEqual(mcp_policy:all_tools(), maps:get(allowed_tools, C)), ?assertEqual([], maps:get(allowed_ets_tables, C)), ?assertEqual(mcp_policy:ceilings(), maps:get(limits, C)), - ?assertEqual(8, length(maps:get(allowed_tools, C))), + ?assertEqual(9, length(maps:get(allowed_tools, C))), %% a project without an mcp term uses the same built-in policy write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), ?assertEqual({ok, C}, mcp_policy:resolve(input(Config, #{}))). diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 6d3cb662..5db36747 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -30,6 +30,8 @@ all() -> top_processes_ranks_and_is_bounded, supervision_tree_reports_child_counts, mermaid_format_renders_the_returned_edges, + topology_overview_maps_one_application_in_one_call, + topology_overview_leaves_out_parts_the_policy_denies, paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, @@ -82,6 +84,8 @@ test_config(Case) -> case Case of allowlist_enforced -> Base1#{allowed_tools => [<<"runtime_summary">>, <<"ets_tables">>]}; + topology_overview_leaves_out_parts_the_policy_denies -> + Base1#{allowed_tools => [<<"topology_overview">>, <<"supervision_tree">>, <<"application_overview">>]}; pagination_cursors_and_retention -> Base1#{limits => L#{max_items => 10, max_collections => 2, collection_ttl_ms => 1500}}; collection_byte_budget_is_partial_and_non_resumable -> @@ -157,7 +161,7 @@ decode(Bin) -> %% unless the case chooses the detail itself (see agent_summary_is_compact). call(Config, Tool, Args0) when is_map(Args0) -> Graph = [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, - <<"process_info">>, <<"ets_tables">>, <<"top_processes">>], + <<"process_info">>, <<"ets_tables">>, <<"top_processes">>, <<"topology_overview">>], Args = case lists:member(Tool, Graph) andalso not maps:is_key(<<"detail">>, Args0) of true -> Args0#{<<"detail">> => <<"full">>}; false -> Args0 @@ -313,7 +317,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), ?assertEqual(lists:sort(mcp_policy:all_tools()), Names), - ?assertEqual(8, length(Tools)), + ?assertEqual(9, length(Tools)), [begin ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, <<"outputSchema">> := #{<<"type">> := <<"object">>}, @@ -1053,6 +1057,51 @@ supervision_tree_reports_child_counts(Config) -> %% workers carry no counts ?assertEqual([], [E || #{<<"role">> := <<"worker">>} = E <- Ents, maps:is_key(<<"children">>, E)]). +topology_overview_maps_one_application_in_one_call(Config) -> + Pages = all_pages(Config, <<"topology_overview">>, #{<<"name">> => <<"mcp_fixture_app">>}), + Ents = entities(Pages), + %% the application, its tree (same edges as supervision_tree), registered workers and the owned table + ?assertMatch(#{<<"kind">> := <<"application">>, <<"version">> := <<"9.9.9">>}, ent(<<"mcp_fixture_app">>, Ents)), + ?assertEqual(lists:sort([{<<"mcp_fx_root">>, <<"worker_a">>}, {<<"mcp_fx_root">>, <<"mcp_fx_sub_sup">>}, + {<<"mcp_fx_root">>, <<"mcp_fx_dyn_sup">>}, {<<"mcp_fx_sub_sup">>, <<"worker_b">>}, + {<<"mcp_fx_sub_sup">>, <<"worker_c">>}]), supervises_edges(Pages)), + Sub = hd([E || #{<<"childId">> := <<"mcp_fx_sub_sup">>} = E <- Ents]), + ?assertMatch(#{<<"children">> := #{<<"specs">> := 2}}, Sub), + ?assertMatch(#{<<"kind">> := <<"ets_table">>, <<"protection">> := <<"public">>}, ent(<<"mcp_fx_orders">>, Ents)), + %% a private or unrelated table is not in the application's map + ?assertEqual([], by_name(<<"mcp_fx_private">>, Ents)), + Owns = [R || #{<<"type">> := <<"owns_table">>} = R <- relationships(Pages)], + ?assertEqual(1, length(Owns)), + ?assertEqual(<<"belongs_to">>, maps:get(<<"type">>, hd([R || #{<<"type">> := <<"belongs_to">>} = R <- relationships(Pages)]))), + %% the orphan (linked, unsupervised, registered) is at most an inferred member, never supervised + ?assertMatch(#{<<"membership">> := #{<<"confidence">> := <<"inferred">>}}, ent(<<"mcp_fx_orphan">>, Ents)), + OrphanId = id_of(ent(<<"mcp_fx_orphan">>, Ents)), + ?assertEqual([], [R || #{<<"type">> := <<"supervises">>, <<"to">> := T} = R <- relationships(Pages), T =:= OrphanId]), + %% unrelated registered processes are reported as omitted, never merged in + Reasons = lists:usort([maps:get(<<"reason">>, O) || P <- Pages, O <- maps:get(<<"omissions">>, P)]), + ?assertEqual([], Reasons -- [<<"unknown_membership">>]), + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [Pages])), "SENTINEL")), + ?assertMatch(#{<<"tool">> := <<"topology_overview">>, <<"parts">> := [_, _, _, _]}, maps:get(<<"scope">>, hd(Pages))), + %% by id gives the same collection content; exactly one selector, strict arguments + AppId = fixture_app_id(Config), + ?assertEqual(supervises_edges(Pages), supervises_edges(all_pages(Config, <<"topology_overview">>, #{<<"application">> => AppId}))), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"topology_overview">>, #{})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"topology_overview">>, #{<<"name">> => <<"a">>, <<"application">> => AppId})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"topology_overview">>, #{<<"name">> => <<"no_such_application_zz">>})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"topology_overview">>, #{<<"name">> => <<"kernel_not_started_zz">>})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"invalid_id">>}}}, + call(Config, <<"topology_overview">>, #{<<"application">> => id_of(hd([E || #{<<"role">> := <<"supervisor">>} = E <- Ents]))})). + +topology_overview_leaves_out_parts_the_policy_denies(Config) -> + S = ok_call(Config, <<"topology_overview">>, #{<<"name">> => <<"mcp_fixture_app">>}), + ?assertMatch(#{<<"parts">> := [<<"supervision_tree">>, <<"application_overview">>]}, maps:get(<<"scope">>, S)), + Denied = [maps:get(<<"message">>, O) || #{<<"reason">> := <<"policy_denied">>} = O <- maps:get(<<"omissions">>, S)], + ?assertEqual(2, length(Denied)), + ?assertEqual([], [E || #{<<"kind">> := <<"ets_table">>} = E <- maps:get(<<"entities">>, S)]), + ?assertEqual(false, maps:get(<<"complete">>, S)). + mermaid_format_renders_the_returned_edges(Config) -> RootId = fixture_root_id(Config), Plain = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId}), diff --git a/mcp_security.md b/mcp_security.md index f5bb6d96..c28690c8 100644 --- a/mcp_security.md +++ b/mcp_security.md @@ -19,7 +19,7 @@ port. All requests, parameters and identifiers are untrusted. | Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | | Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | | Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | -| Least privilege | Eight fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | +| Least privilege | Nine fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | | No execution | No eval, `rpc:call`, shell, code loading, message sending, `sys` calls, tracing, breakpoint changes, ETS writes or reads | | Data minimisation | No process dictionary, stack, mailbox, state, child start arguments, application environment, source, code paths, cookies; ETS: metadata of approved, non-private named tables only; node host redacted by default; child identifiers are printed with a depth/size bound | | Safe identifiers | Registered names are resolved with `binary_to_existing_atom/2` only; pids must be local (`<0.N.M>`); no `binary_to_term/1` on input | From e4e6fe9c4d8beebe8d44f3e175aea686f565f7f5 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:27:20 +0200 Subject: [PATCH 08/20] Serve the sample prompt as MCP prompt map_application Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 4 +- apps/erlangbridge/src/mcp/mcp_server.erl | 15 ++++- apps/erlangbridge/src/mcp/mcp_tools.erl | 63 +++++++++++++++++++ .../test/mcp/mcp_server_SUITE.erl | 25 +++++++- 5 files changed, 101 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f951eb2..a96e2472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - `runtime_summary` also reports the run queue length and process/atom/port/ETS counts against their VM limits (`resources`) - Graph tools accept `format: "mermaid"`: the page is also returned as a ready-to-render Mermaid `graph TD`, built from the reported edges - New tool `topology_overview`: the application, its supervision tree, registered processes and owned approved ETS tables of one application in a single call (each part still subject to the project policy) + - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) --- diff --git a/HELP.MD b/HELP.MD index 8e0c3760..3e48f131 100644 --- a/HELP.MD +++ b/HELP.MD @@ -166,7 +166,7 @@ messages or business flows. Security model and residual risks: [mcp_security.md] automatically). Set a fixed `erlang.mcp.port` if the client needs a stable URL: the token still changes at every session and must be supplied as `Authorization: Bearer `. If the fixed port is busy MCP is disabled for the session (no silent rebinding). -- Protocol: MCP `2025-06-18`, Streamable HTTP with JSON responses (no SSE, no prompts, no resources). +- Protocol: MCP `2025-06-18`, Streamable HTTP with JSON responses (no SSE, no resources). One prompt, `map_application` (argument `application`), carries the sample prompt below, so a client that lists MCP prompts (e.g. VS Code: `/mcp..map_application`) can run it directly; it grants nothing. **Project policy (optional)** - a literal top-level term in the *target project's* `rebar.config` (nearest file from the debug `cwd`, never above the workspace folder; no `rebar.config.script`, @@ -243,7 +243,7 @@ enrich with `process_info`, `registered_processes`, `top_processes`, `ets_tables keep confirmed edges apart from inferences, and report the omissions. Module names can then be correlated with the workspace/LSP tools; the inspector never returns source or code paths. -**Sample prompt**: once the client is connected and the node is paused or running under the +**Sample prompt** (also served as the MCP prompt `map_application`): once the client is connected and the node is paused or running under the debugger, paste this into the agent chat (Copilot agent mode, Claude Code...), replacing `` with your application name. It follows the workflow above and asks for a diagram, a table and the coverage gaps, without guessing beyond what the tools return: diff --git a/apps/erlangbridge/src/mcp/mcp_server.erl b/apps/erlangbridge/src/mcp/mcp_server.erl index ca104a48..ef68d10d 100644 --- a/apps/erlangbridge/src/mcp/mcp_server.erl +++ b/apps/erlangbridge/src/mcp/mcp_server.erl @@ -3,8 +3,8 @@ %% %% Pinned protocol revision : 2025-06-18. Stateless: no Mcp-Session-Id is %% issued, no SSE stream (GET -> 405). Implemented methods: initialize, -%% notifications/* (202), ping, tools/list, tools/call. No prompts, no -%% resources. Batches are rejected (removed in this revision). +%% notifications/* (202), ping, tools/list, tools/call, prompts/list and +%% prompts/get (one static, argument-checked prompt; it grants nothing). No resources. Batches are rejected (removed in this revision). %% %% Security: explicit loopback bind, Host/Origin validation, no CORS, bearer %% token required on every request *before* the body is read, body size cap, @@ -336,6 +336,7 @@ journal_meta(Method, Params, Obj) -> known_method(M) -> Known = [<<"initialize">>, <<"ping">>, <<"tools/list">>, <<"tools/call">>, + <<"prompts/list">>, <<"prompts/get">>, <<"notifications/initialized">>, <<"notifications/cancelled">>], case lists:member(M, Known) of true -> M; @@ -391,7 +392,8 @@ dispatch(<<"initialize">>, Params, Id, _Config) -> V when is_binary(V) -> %% version negotiation: the server answers with the revision it implements result_obj(Id, #{<<"protocolVersion">> => ?PROTOCOL_VERSION, - <<"capabilities">> => #{<<"tools">> => #{<<"listChanged">> => false}}, + <<"capabilities">> => #{<<"tools">> => #{<<"listChanged">> => false}, + <<"prompts">> => #{<<"listChanged">> => false}}, <<"serverInfo">> => #{<<"name">> => <<"erlang-otp-topology-inspector">>, <<"title">> => <<"Erlang OTP topology inspector">>, <<"version">> => <<"1.0.0">>}, @@ -414,6 +416,13 @@ dispatch(<<"tools/list">>, _, Id, Config) -> result_obj(Id, #{<<"tools">> => mcp_tools:list(Config)}); dispatch(<<"tools/call">>, Params, Id, Config) -> tools_call(Params, Id, Config); +dispatch(<<"prompts/list">>, _, Id, _) -> + result_obj(Id, #{<<"prompts">> => mcp_tools:prompts()}); +dispatch(<<"prompts/get">>, Params, Id, Config) -> + case mcp_tools:prompt(maps:get(<<"name">>, Params, undefined), maps:get(<<"arguments">>, Params, #{}), Config) of + {ok, Result} -> result_obj(Id, Result); + {error, Msg} -> error_obj(Id, -32602, Msg) + end; dispatch(_, _, Id, _) -> error_obj(Id, -32601, <<"method not found">>). diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 60bde298..83a13ca0 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -3,6 +3,7 @@ -module(mcp_tools). -export([list/1, known/1, validate_args/2, structured_error_schema/0]). +-export([prompts/0, prompt/3]). -define(SCHEMA_VERSION, <<"1.0">>). -export([schema_version/0]). @@ -26,6 +27,68 @@ tool_def(Name) -> <<"idempotentHint">> => true, <<"openWorldHint">> => false}}. +%%------------------------------------------------------------------------------ +%% Prompts: one static prompt that tells an agent how to use the tools. It carries +%% no data and grants nothing; the argument is checked before it is embedded. +%%------------------------------------------------------------------------------ + +prompts() -> + [#{<<"name">> => <<"map_application">>, + <<"title">> => <<"Map an OTP application">>, + <<"description">> => <<"Map the supervision topology of one application of the debugged node and report " + "coverage gaps and observations, using only what the inspector tools return.">>, + <<"arguments">> => [#{<<"name">> => <<"application">>, + <<"description">> => <<"Name of a started OTP application.">>, + <<"required">> => true}]}]. + +%% -> {ok, GetPromptResult} | {error, Message} +prompt(<<"map_application">>, Args, Config) when is_map(Args) -> + case maps:get(<<"application">>, Args, undefined) of + App when is_binary(App), byte_size(App) > 0, byte_size(App) =< 255 -> + case re:run(App, "^[A-Za-z0-9_@.-]+$", [{capture, none}]) of + match -> + {ok, #{<<"description">> => <<"Map the OTP topology of ", App/binary>>, + <<"messages">> => [#{<<"role">> => <<"user">>, + <<"content">> => #{<<"type">> => <<"text">>, + <<"text">> => map_application_text(App, Config)}}]}}; + nomatch -> {error, <<"invalid argument 'application': not an application name">>} + end; + undefined -> {error, <<"missing required argument 'application'">>}; + _ -> {error, <<"invalid argument 'application'">>} + end; +prompt(<<"map_application">>, _, _) -> + {error, <<"arguments must be an object">>}; +prompt(_, _, _) -> + {error, <<"unknown prompt">>}. + +map_application_text(App, Config) -> + Allowed = fun(Tool) -> mcp_policy:tool_allowed(Tool, Config) end, + Steps = case Allowed(<<"topology_overview">>) of + true -> + ["1. topology_overview(name=\"", App, "\"): the application, its supervision tree, registered " + "processes and owned approved ETS tables in one call. If an omission says limit_reached or " + "timeout, expand that subtree with supervision_tree and the id it gives.\n"]; + false -> + ["1. runtime_summary, then application_overview to find the id and root supervisors of \"", App, + "\"; supervision_tree from each root (expand subtrees named in limit_reached or timeout " + "omissions); registered_processes(application=) for registered workers outside the tree.\n"] + end, + iolist_to_binary( + ["Using the erlang-otp-topology-inspector MCP server, map the OTP topology of application ", App, + " in the node I am debugging. Use only the tools that tools/list returns.\n\nSteps:\n", Steps, + "2. top_processes (or process_info) only for processes that matter: supervisors, registered workers, " + "anything with a non-zero message queue. If debug_session reports processes stopped at a breakpoint, " + "say so: their supervisors may be reported as unavailable_while_paused.\n" + "3. ets_tables and debug_session only if they relate to the application.\n\n" + "Output:\n" + "- A Mermaid `graph TD` of the supervision tree (edge = supervises; label children with kind and restart " + "type). Tools take format=mermaid and return the diagram from the reported edges.\n" + "- A table: name/pid, module, kind, restart, msg queue len, membership (confirmed/inferred).\n" + "- \"Coverage gaps\": every truncated/omission/timeout reported by the tools.\n" + "- 3-5 observations (single points of failure, deep trees, hot queues, temporary children).\n\n" + "Rules: use only what the tools returned. Structural edges are not message traffic, so do not describe " + "runtime call flows. Keep detail=summary unless I ask for the full JSON."]). + %%------------------------------------------------------------------------------ %% Definitions %%------------------------------------------------------------------------------ diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 5db36747..6e62c3a1 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -32,6 +32,7 @@ all() -> mermaid_format_renders_the_returned_edges, topology_overview_maps_one_application_in_one_call, topology_overview_leaves_out_parts_the_policy_denies, + prompt_guides_an_agent_and_checks_its_argument, paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, @@ -308,7 +309,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> <<"capabilities">> => #{}, <<"clientInfo">> => #{<<"name">> => <<"t">>, <<"version">> => <<"1">>}}), ?assertEqual(<<"2025-06-18">>, maps:get(<<"protocolVersion">>, Init)), - ?assertEqual([<<"tools">>], maps:keys(maps:get(<<"capabilities">>, Init))), + ?assertEqual([<<"prompts">>, <<"tools">>], lists:sort(maps:keys(maps:get(<<"capabilities">>, Init)))), %% an older client version is answered with the implemented revision #{<<"result">> := Init2} = rpc(Config, <<"initialize">>, #{<<"protocolVersion">> => <<"2024-11-05">>}), ?assertEqual(<<"2025-06-18">>, maps:get(<<"protocolVersion">>, Init2)), @@ -330,7 +331,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> [?assertEqual(nomatch, re:run(N, "eval|exec|rpc|shell|lookup|trace|kill|load|write|send", [{capture, none}])) || N <- Names], [?assertMatch(#{<<"error">> := #{<<"code">> := -32601}}, rpc(Config, M, #{})) - || M <- [<<"resources/list">>, <<"prompts/list">>, <<"resources/read">>, <<"tools/eval">>, + || M <- [<<"resources/list">>, <<"resources/read">>, <<"tools/eval">>, <<"completion/complete">>, <<"logging/setLevel">>]], [?assertMatch({rpc_error, -32602, _}, call(Config, T, #{})) || T <- [<<"eval">>, <<"ets_lookup">>, <<"rpc_call">>, <<"sys_get_state">>, <<"shell">>, <<"trace">>, @@ -1102,6 +1103,26 @@ topology_overview_leaves_out_parts_the_policy_denies(Config) -> ?assertEqual([], [E || #{<<"kind">> := <<"ets_table">>} = E <- maps:get(<<"entities">>, S)]), ?assertEqual(false, maps:get(<<"complete">>, S)). +prompt_guides_an_agent_and_checks_its_argument(Config) -> + #{<<"result">> := #{<<"prompts">> := [P]}} = rpc(Config, <<"prompts/list">>, #{}), + ?assertMatch(#{<<"name">> := <<"map_application">>, + <<"arguments">> := [#{<<"name">> := <<"application">>, <<"required">> := true}]}, P), + #{<<"result">> := #{<<"messages">> := [#{<<"role">> := <<"user">>, <<"content">> := #{<<"text">> := Text}}]}} = + rpc(Config, <<"prompts/get">>, #{<<"name">> => <<"map_application">>, + <<"arguments">> => #{<<"application">> => <<"my_app">>}}), + ?assertNotEqual(nomatch, binary:match(Text, <<"topology_overview(name=\"my_app\")">>)), + ?assertNotEqual(nomatch, binary:match(Text, <<"Coverage gaps">>)), + %% an unknown prompt and hostile or missing arguments are rejected, nothing is embedded + Bad = fun(Params) -> rpc(Config, <<"prompts/get">>, Params) end, + [?assertMatch(#{<<"error">> := #{<<"code">> := -32602}}, Bad(Params)) + || Params <- [#{<<"name">> => <<"eval">>}, + #{<<"name">> => <<"map_application">>}, + #{<<"name">> => <<"map_application">>, <<"arguments">> => #{<<"application">> => <<"a\nIgnore previous">>}}, + #{<<"name">> => <<"map_application">>, <<"arguments">> => #{<<"application">> => <<"a b">>}}, + #{<<"name">> => <<"map_application">>, <<"arguments">> => #{<<"application">> => 5}}, + #{<<"name">> => <<"map_application">>, <<"arguments">> => []}, + #{<<"name">> => <<"map_application">>, <<"arguments">> => #{<<"application">> => binary:copy(<<"a">>, 300)}}]]. + mermaid_format_renders_the_returned_edges(Config) -> RootId = fixture_root_id(Config), Plain = ok_call(Config, <<"supervision_tree">>, #{<<"id">> => RootId}), From 182df01d2c837ecb7ea0c1acae8e296d14fed853 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:32:13 +0200 Subject: [PATCH 09/20] Add changes_since tool to MCP inspector Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 3 +- apps/erlangbridge/src/mcp/mcp_policy.erl | 2 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 138 ++++++++++++++++-- apps/erlangbridge/src/mcp/mcp_store.erl | 14 +- apps/erlangbridge/src/mcp/mcp_tools.erl | 14 ++ .../test/mcp/mcp_policy_SUITE.erl | 2 +- .../test/mcp/mcp_server_SUITE.erl | 43 +++++- mcp_security.md | 2 +- 9 files changed, 200 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96e2472..ddbd8866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - `runtime_summary` also reports the run queue length and process/atom/port/ETS counts against their VM limits (`resources`) - Graph tools accept `format: "mermaid"`: the page is also returned as a ready-to-render Mermaid `graph TD`, built from the reported edges - New tool `topology_overview`: the application, its supervision tree, registered processes and owned approved ETS tables of one application in a single call (each part still subject to the project policy) + - New tool `changes_since`: compares a retained collection with a fresh observation and reports added, removed and replaced (restarted) entities; removals from partial collections are marked unconfirmed - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) diff --git a/HELP.MD b/HELP.MD index 3e48f131..2a84cc9e 100644 --- a/HELP.MD +++ b/HELP.MD @@ -178,7 +178,7 @@ ceilings: {mcp, [ {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>]}, + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>]}, {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>]; private tables never {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, @@ -201,6 +201,7 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `process_info` | allowlisted fields of one process (by `id`, `name` or `pid`), links/monitors as ids; never dictionary, stack, mailbox or state | | `ets_tables` | metadata of the approved named tables (protection, type, owner, size, memory in words and bytes) - never keys or values | | `topology_overview` | one call for one application (`name` or `application` id): the application, its supervision tree with child counts, registered processes and the approved ETS tables they own, as one collection. Parts whose own tool is not allowed by the policy are left out (`policy_denied` omission) | +| `changes_since` | what changed since an earlier collection (`collectionId`): added, removed and replaced (restarted: same child, new id) entities, with `absence: unconfirmed` for removals from a partial collection. The baseline is only retained for `collection_ttl_ms` (30 s by default) and 2 collections: compare soon after the first call, else `baseline_expired` | | `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | | `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line), processes currently stopped at a breakpoint (`pausedProcesses`: process id, module, line) | diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index 97cb6930..9efb0bc3 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -33,7 +33,7 @@ -define(TOOLS, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>]). + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>]). %% {internal key, project key, JSON key, default = ceiling} -define(LIMITS, diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index 9748192b..7ac58c0f 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -62,15 +62,24 @@ call(Tool, Args, Ctx0) -> run(<<"runtime_summary">>, Args, Ctx) -> runtime_summary(Args, Ctx); run(<<"debug_session">>, _Args, Ctx) -> debug_session(Ctx); -run(<<"application_overview">>, Args, Ctx) -> graph_tool(<<"application_overview">>, fun application_overview_build/2, Args, Ctx); -run(<<"supervision_tree">>, Args, Ctx) -> graph_tool(<<"supervision_tree">>, fun supervision_tree_build/2, Args, Ctx); -run(<<"registered_processes">>, Args, Ctx) -> graph_tool(<<"registered_processes">>, fun registered_processes_build/2, Args, Ctx); +run(<<"application_overview">>, Args, Ctx) -> graph_tool(<<"application_overview">>, builder(<<"application_overview">>), Args, Ctx); +run(<<"supervision_tree">>, Args, Ctx) -> graph_tool(<<"supervision_tree">>, builder(<<"supervision_tree">>), Args, Ctx); +run(<<"registered_processes">>, Args, Ctx) -> graph_tool(<<"registered_processes">>, builder(<<"registered_processes">>), Args, Ctx); run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); -run(<<"ets_tables">>, Args, Ctx) -> graph_tool(<<"ets_tables">>, fun ets_tables_build/2, Args, Ctx); -run(<<"topology_overview">>, Args, Ctx) -> graph_tool(<<"topology_overview">>, fun topology_overview_build/2, Args, Ctx); -run(<<"top_processes">>, Args, Ctx) -> top_processes(Args, Ctx); +run(<<"ets_tables">>, Args, Ctx) -> graph_tool(<<"ets_tables">>, builder(<<"ets_tables">>), Args, Ctx); +run(<<"changes_since">>, Args, Ctx) -> graph_tool(<<"changes_since">>, builder(<<"changes_since">>), Args, Ctx); +run(<<"topology_overview">>, Args, Ctx) -> graph_tool(<<"topology_overview">>, builder(<<"topology_overview">>), Args, Ctx); +run(<<"top_processes">>, Args, Ctx) -> graph_tool(<<"top_processes">>, builder(<<"top_processes">>), Args, Ctx); run(_, _, _) -> {error, <<"unknown_tool">>, <<"unknown tool">>}. +builder(<<"application_overview">>) -> fun application_overview_build/2; +builder(<<"supervision_tree">>) -> fun supervision_tree_build/2; +builder(<<"registered_processes">>) -> fun registered_processes_build/2; +builder(<<"ets_tables">>) -> fun ets_tables_build/2; +builder(<<"top_processes">>) -> fun top_processes_build/2; +builder(<<"topology_overview">>) -> fun topology_overview_build/2; +builder(<<"changes_since">>) -> fun changes_since_build/2. + %% Graph tools build a Builder (entities, relationships, omissions) and a scope; %% finish_graph retains it as a collection and renders the first page. graph_tool(Tool, Build, Args, Ctx) -> @@ -280,12 +289,15 @@ finish_graph(Tool, Args, Scope, B, Started, Ctx) -> Truncated = lists:any(fun(#{<<"reason">> := R}) -> R =:= <<"limit_reached">> end, Oms), Meta = #{scope => Scope, started_at => Started, finished_at => mcp_store:now_ms(), complete => Oms =:= [], truncated => Truncated, omissions => Oms}, - Items = [{entity, E} || E <- lists:reverse(maps:get(ents, B))] - ++ [{relationship, R} || R <- lists:reverse(maps:get(rels, B))], + Items = builder_items(B), Hash = args_hash(maps:remove(<<"cursor">>, Args)), - {CollId, Meta1, Stored} = mcp_store:put_collection(Tool, Hash, Meta, Items, #{}), + {CollId, Meta1, Stored} = mcp_store:put_collection(Tool, Hash, Meta, Items, #{args => Args}), render_page(Tool, Hash, CollId, Meta1, Stored, 0, Ctx). +builder_items(B) -> + [{entity, E} || E <- lists:reverse(maps:get(ents, B))] + ++ [{relationship, R} || R <- lists:reverse(maps:get(rels, B))]. + page_from_cursor(Tool, Args, Cursor, Ctx) -> Hash = args_hash(maps:remove(<<"cursor">>, Args)), case mcp_store:parse_cursor(Cursor, Tool, Hash) of @@ -1257,12 +1269,114 @@ merge_owned_tables(B, EtsB) -> maps:get(<<"evidence">>, R), maps:get(<<"confidence">>, R)) end, B, Owned). +%%------------------------------------------------------------------------------ +%% changes_since: what differs between a retained collection (the baseline) and a +%% fresh observation of the same tool and arguments. Identity is the entity id, so a +%% restarted process is `replaced` (same logical child, new id), never "the same". +%% Absence from a partial collection is not proof of termination: such removals are +%% marked `unconfirmed`. The baseline must still be retained (collection_ttl_ms). +%%------------------------------------------------------------------------------ + +-define(DIFFABLE, [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, + <<"ets_tables">>, <<"top_processes">>, <<"topology_overview">>]). + +changes_since_build(Args, #{config := Config} = Ctx) -> + case mcp_store:fetch_collection_by_id(maps:get(<<"collectionId">>, Args)) of + {error, _} -> + {error, <<"baseline_expired">>, + <<"the baseline collection is unknown or no longer retained (collection_ttl_ms, max_collections); " + "call the tool again to get a new baseline">>}; + {ok, #{tool := OldTool, ctx := #{args := OldArgs}, meta := OldMeta, items := OldItems}} -> + case lists:member(OldTool, ?DIFFABLE) andalso mcp_policy:tool_allowed(OldTool, Config) of + false -> + {error, <<"invalid_baseline">>, <<"this collection cannot be used as a baseline">>}; + true -> + %% a fresh observation that is compared, not retained: only the diff takes a slot + case (builder(OldTool))(OldArgs, Ctx) of + {ok, _Scope, NB} -> + Oms = lists:reverse(maps:get(oms, NB)), + NewMeta = #{complete => Oms =:= [], omissions => Oms}, + diff_collections(OldTool, maps:get(<<"collectionId">>, Args), + OldMeta, OldItems, NewMeta, builder_items(NB)); + {error, _, _} = E -> E + end + end; + {ok, _} -> + {error, <<"invalid_baseline">>, <<"this collection cannot be used as a baseline">>} + end. + +diff_collections(Tool, BaseId, OldMeta, OldItems, NewMeta, NewItems) -> + Old = [E || {entity, E} <- OldItems], + New = [E || {entity, E} <- NewItems], + OldIds = maps:from_list([{maps:get(<<"id">>, E), true} || E <- Old]), + NewIds = maps:from_list([{maps:get(<<"id">>, E), true} || E <- New]), + Removed0 = [E || E <- Old, not maps:is_key(maps:get(<<"id">>, E), NewIds)], + Added0 = [E || E <- New, not maps:is_key(maps:get(<<"id">>, E), OldIds)], + RemByKey = by_key(Removed0), + AddByKey = by_key(Added0), + %% a logical child that vanished once and appeared once under a new id was replaced + Pairs = [{R, A} || {Key, [R]} <- maps:to_list(RemByKey), {ok, [A]} <- [maps:find(Key, AddByKey)]], + ReplacedOld = maps:from_list([{maps:get(<<"id">>, R), true} || {R, _} <- Pairs]), + ReplacedNew = maps:from_list([{maps:get(<<"id">>, A), maps:get(<<"id">>, R)} || {R, A} <- Pairs]), + Removed = [E || E <- Removed0, not maps:is_key(maps:get(<<"id">>, E), ReplacedOld)], + Added = [E || E <- Added0, not maps:is_key(maps:get(<<"id">>, E), ReplacedNew)], + Absence = case maps:get(complete, NewMeta) of + true -> <<"observed">>; + false -> <<"unconfirmed">> + end, + Changed = [{A, #{<<"change">> => <<"added">>}} || A <- Added] + ++ [{A, #{<<"change">> => <<"replaced">>, <<"previousId">> => maps:get(maps:get(<<"id">>, A), ReplacedNew)}} + || {_, A} <- Pairs] + ++ [{R, #{<<"change">> => <<"removed">>, <<"absence">> => Absence}} || R <- Removed], + B0 = lists:foldl(fun({E, Mark}, Acc) -> + add_entity(Acc, maps:get(<<"id">>, E), maps:merge(maps:remove(<<"id">>, E), Mark)) + end, new_b(), Changed), + %% the edges of the fresh observation that lead to a new or replaced entity + Fresh = maps:from_list([{maps:get(<<"id">>, A), true} || A <- Added] ++ [{I, true} || I <- maps:keys(ReplacedNew)]), + B1 = lists:foldl( + fun({relationship, #{<<"type">> := T, <<"from">> := F, <<"to">> := To} = R}, Acc) -> + case maps:is_key(To, Fresh) of + true -> add_rel(Acc, T, F, To, maps:get(<<"evidence">>, R), maps:get(<<"confidence">>, R)); + false -> Acc + end; + (_, Acc) -> Acc + end, B0, NewItems), + Unchanged = length([E || E <- New, maps:is_key(maps:get(<<"id">>, E), OldIds)]), + B2 = B1#{oms := lists:reverse(maps:get(omissions, NewMeta, [])) ++ maps:get(oms, B1)}, + B3 = case maps:get(complete, OldMeta) of + true -> B2; + false -> add_omission(B2, <<"limit_reached">>, 1, undefined, + <<"the baseline collection was partial: entities reported as added may already have existed">>, + false) + end, + Scope = #{<<"tool">> => <<"changes_since">>, + <<"baselineCollectionId">> => BaseId, + <<"baselineTool">> => Tool, + <<"baselineFinishedAt">> => mcp_encoder:iso8601(maps:get(finished_at, OldMeta)), + <<"summary">> => #{<<"added">> => length(Added), <<"removed">> => length(Removed), + <<"replaced">> => length(Pairs), <<"unchanged">> => Unchanged}, + <<"limitations">> => + [<<"Compares two observation intervals, not atomic snapshots; the entities of this answer are only those that changed.">>, + <<"replaced = the same logical child (name or child id) now has a new id, i.e. it was restarted or recreated.">>, + <<"A removal from a partial collection is marked absence=unconfirmed: absence is not proof of termination.">>, + <<"The baseline must still be retained (collection_ttl_ms, max_collections).">>]}, + {ok, Scope, B3}. + +%% logical identity of a child across restarts: its registered name, else its child id; +%% only keys that are unique on their side can identify a replacement +by_key(Ents) -> + Keyed = [{K, E} || E <- Ents, K <- [change_key(E)], K =/= undefined], + lists:foldl(fun({K, E}, Acc) -> maps:update_with(K, fun(L) -> L ++ [E] end, [E], Acc) end, #{}, Keyed). + +change_key(#{<<"kind">> := Kind, <<"name">> := N}) when is_binary(N) -> {Kind, name, N}; +change_key(#{<<"kind">> := Kind, <<"childId">> := C}) when is_binary(C) -> {Kind, child, C}; +change_key(_) -> undefined. + %%------------------------------------------------------------------------------ %% top_processes (ranking by queue length / reductions / memory) %%------------------------------------------------------------------------------ -top_processes(Args, Ctx) -> - Started = mcp_store:now_ms(), +top_processes_build(Args, Ctx) -> SortBy = sort_item(maps:get(<<"sortBy">>, Args, <<"message_queue_len">>)), Limit = min(maps:get(<<"limit">>, Args, 10), ?MAX_TOP), All = erlang:processes(), @@ -1308,7 +1422,7 @@ top_processes(Args, Ctx) -> <<"reductions are cumulative since the process started, not a rate; memory is in bytes.">>, <<"Only metadata is returned: never mailbox, dictionary, stack or state.">>, <<"Inspector processes are excluded.">>]}, - finish_graph(<<"top_processes">>, Args, Scope, B4, Started, Ctx). + {ok, Scope, B4}. sort_item(<<"reductions">>) -> reductions; sort_item(<<"memory">>) -> memory; diff --git a/apps/erlangbridge/src/mcp/mcp_store.erl b/apps/erlangbridge/src/mcp/mcp_store.erl index f642eee3..f40192ed 100644 --- a/apps/erlangbridge/src/mcp/mcp_store.erl +++ b/apps/erlangbridge/src/mcp/mcp_store.erl @@ -9,7 +9,7 @@ -export([start_link/2, start_link/3, stop/0]). -export([session_id/0, mode/0, config/0, started_at/0, now_ms/0]). -export([entity_id/2, resolve_id/1]). --export([put_collection/5, fetch_collection/3, make_cursor/4, parse_cursor/3]). +-export([put_collection/5, fetch_collection/3, fetch_collection_by_id/1, make_cursor/4, parse_cursor/3]). -export([acquire_slot/1, release_slot/1]). -export([note_member/3, member/1]). -export([stats/0, notify/1]). @@ -74,6 +74,11 @@ put_collection(Tool, ArgsHash, Meta, Items, Ctx) -> fetch_collection(CollId, Tool, ArgsHash) -> gen_server:call(?SERVER, {fetch_collection, CollId, Tool, ArgsHash}). +%% A retained collection by its id alone (used to diff against a baseline). The id is +%% an unguessable per-session value; an expired or unknown id is {error, cursor_expired}. +fetch_collection_by_id(CollId) when is_binary(CollId) -> gen_server:call(?SERVER, {fetch_by_id, CollId}); +fetch_collection_by_id(_) -> {error, cursor_expired}. + make_cursor(CollId, Tool, ArgsHash, Offset) -> gen_server:call(?SERVER, {make_cursor, CollId, Tool, ArgsHash, Offset}). @@ -133,6 +138,13 @@ handle_call({fetch_collection, CollId, Tool, ArgsHash}, _, S0) -> end, {reply, Reply, S}; +handle_call({fetch_by_id, CollId}, _, S0) -> + S = sweep_collections(S0), + Reply = case maps:find(CollId, S#state.collections) of + {ok, C} -> {ok, C}; + error -> {error, cursor_expired} + end, + {reply, Reply, S}; handle_call({make_cursor, CollId, Tool, ArgsHash, Offset}, _, S) -> {reply, sign_cursor(CollId, Tool, ArgsHash, Offset, S), S}; handle_call({parse_cursor, Cursor, Tool, ArgsHash}, _, S) -> diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 83a13ca0..6c709cad 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -188,6 +188,20 @@ def(<<"topology_overview">>) -> <<"format">> => format_prop()}, []), graph_output()}; +def(<<"changes_since">>) -> + {<<"Changes since a collection">>, + <<"Observe again the tool and arguments of an earlier collection (collectionId of a previous graph result) and " + "return only what changed: added, removed and replaced entities (replaced = the same logical child with a new " + "id, i.e. restarted or recreated; previousId names the old one), plus the edges leading to new entities. " + "A removal from a partial collection is marked absence=unconfirmed. The baseline is only retained for a short " + "time (collection_ttl_ms, few collections): compare soon after the first call, otherwise baseline_expired.">>, + obj(#{<<"collectionId">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 64, + <<"description">> => <<"collectionId of the baseline (from a previous graph result).">>}, + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, [<<"collectionId">>]), + graph_output()}; + def(<<"top_processes">>) -> {<<"Top processes">>, <<"Rank the local processes of the node by message queue length, reductions or memory and return the top ones " diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index b36aa115..6ad03cfa 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -64,7 +64,7 @@ defaults_without_project_file(Config) -> ?assertEqual(mcp_policy:all_tools(), maps:get(allowed_tools, C)), ?assertEqual([], maps:get(allowed_ets_tables, C)), ?assertEqual(mcp_policy:ceilings(), maps:get(limits, C)), - ?assertEqual(9, length(maps:get(allowed_tools, C))), + ?assertEqual(10, length(maps:get(allowed_tools, C))), %% a project without an mcp term uses the same built-in policy write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), ?assertEqual({ok, C}, mcp_policy:resolve(input(Config, #{}))). diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 6e62c3a1..2b6052e8 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -33,6 +33,7 @@ all() -> topology_overview_maps_one_application_in_one_call, topology_overview_leaves_out_parts_the_policy_denies, prompt_guides_an_agent_and_checks_its_argument, + changes_since_reports_replaced_and_removed_children, paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, @@ -162,7 +163,7 @@ decode(Bin) -> %% unless the case chooses the detail itself (see agent_summary_is_compact). call(Config, Tool, Args0) when is_map(Args0) -> Graph = [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, - <<"process_info">>, <<"ets_tables">>, <<"top_processes">>, <<"topology_overview">>], + <<"process_info">>, <<"ets_tables">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>], Args = case lists:member(Tool, Graph) andalso not maps:is_key(<<"detail">>, Args0) of true -> Args0#{<<"detail">> => <<"full">>}; false -> Args0 @@ -318,7 +319,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), ?assertEqual(lists:sort(mcp_policy:all_tools()), Names), - ?assertEqual(9, length(Tools)), + ?assertEqual(10, length(Tools)), [begin ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, <<"outputSchema">> := #{<<"type">> := <<"object">>}, @@ -1103,6 +1104,44 @@ topology_overview_leaves_out_parts_the_policy_denies(Config) -> ?assertEqual([], [E || #{<<"kind">> := <<"ets_table">>} = E <- maps:get(<<"entities">>, S)]), ?assertEqual(false, maps:get(<<"complete">>, S)). +changes_since_reports_replaced_and_removed_children(Config) -> + ok = mcp_fixture_sup:add_dynamic(2), + RootId = fixture_root_id(Config), + Args = #{<<"id">> => RootId}, + Base = ok_call(Config, <<"supervision_tree">>, Args), + BaseId = maps:get(<<"collectionId">>, Base), + BaseEnts = entities([Base]), + OldB = hd([E || #{<<"childId">> := <<"worker_b">>} = E <- BaseEnts]), + %% nothing happened: no change, and the diff itself is complete + Same = ok_call(Config, <<"changes_since">>, #{<<"collectionId">> => BaseId}), + ?assertMatch(#{<<"summary">> := #{<<"added">> := 0, <<"removed">> := 0, <<"replaced">> := 0}}, maps:get(<<"scope">>, Same)), + ?assertEqual([], maps:get(<<"entities">>, Same)), + %% a restarted worker is `replaced` (new id), a terminated dynamic worker is `removed` + exit(hd([P || {worker_b, P, _, _} <- supervisor:which_children(mcp_fx_sub_sup), is_pid(P)]), kill), + wait_until(fun() -> [P || {worker_b, P, _, _} <- supervisor:which_children(mcp_fx_sub_sup), is_pid(P)] =/= [] end), + [{_, Dyn, _, _} | _] = supervisor:which_children(mcp_fx_dyn_sup), + ok = supervisor:terminate_child(mcp_fx_dyn_sup, Dyn), + D = ok_call(Config, <<"changes_since">>, #{<<"collectionId">> => BaseId}), + ?assertMatch(#{<<"summary">> := #{<<"replaced">> := 1, <<"removed">> := 1, <<"added">> := 0}}, maps:get(<<"scope">>, D)), + Ents = maps:get(<<"entities">>, D), + [Replaced] = [E || #{<<"change">> := <<"replaced">>} = E <- Ents], + ?assertMatch(#{<<"childId">> := <<"worker_b">>, <<"previousId">> := _}, Replaced), + ?assertEqual(id_of(OldB), maps:get(<<"previousId">>, Replaced)), + ?assertNotEqual(id_of(OldB), id_of(Replaced)), + [Removed] = [E || #{<<"change">> := <<"removed">>} = E <- Ents], + ?assertMatch(#{<<"absence">> := <<"observed">>}, Removed), + %% the edge that leads to the replacement is included; unchanged children are not listed + ?assertEqual([<<"worker_b">>], [maps:get(<<"childId">>, E) || #{<<"childId">> := C} = E <- Ents, C =:= <<"worker_b">>]), + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [D])), "SENTINEL")), + %% unknown baselines, strict arguments, and a changes_since collection is not a baseline + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"baseline_expired">>}}}, + call(Config, <<"changes_since">>, #{<<"collectionId">> => <<"c_000000000000">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"changes_since">>, #{})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := C}}} when C =:= <<"invalid_baseline">> orelse C =:= <<"baseline_expired">>, + call(Config, <<"changes_since">>, #{<<"collectionId">> => maps:get(<<"collectionId">>, D)})), + lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, + supervisor:which_children(mcp_fx_dyn_sup)). + prompt_guides_an_agent_and_checks_its_argument(Config) -> #{<<"result">> := #{<<"prompts">> := [P]}} = rpc(Config, <<"prompts/list">>, #{}), ?assertMatch(#{<<"name">> := <<"map_application">>, diff --git a/mcp_security.md b/mcp_security.md index c28690c8..2f50978b 100644 --- a/mcp_security.md +++ b/mcp_security.md @@ -19,7 +19,7 @@ port. All requests, parameters and identifiers are untrusted. | Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | | Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | | Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | -| Least privilege | Nine fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | +| Least privilege | Ten fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | | No execution | No eval, `rpc:call`, shell, code loading, message sending, `sys` calls, tracing, breakpoint changes, ETS writes or reads | | Data minimisation | No process dictionary, stack, mailbox, state, child start arguments, application environment, source, code paths, cookies; ETS: metadata of approved, non-private named tables only; node host redacted by default; child identifiers are printed with a depth/size bound | | Safe identifiers | Registered names are resolved with `binary_to_existing_atom/2` only; pids must be local (`<0.N.M>`); no `binary_to_term/1` on input | From 78b224ecceb2a4a3a5b1b3d02c5c0d280bf6d59a Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:40:48 +0200 Subject: [PATCH 10/20] Add top_ports, ets_summary and connected nodes to MCP inspector Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 9 +- apps/erlangbridge/src/mcp/mcp_policy.erl | 3 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 182 +++++++++++++++++- apps/erlangbridge/src/mcp/mcp_tools.erl | 30 +++ .../test/mcp/mcp_policy_SUITE.erl | 2 +- .../test/mcp/mcp_server_SUITE.erl | 79 +++++++- mcp_security.md | 2 +- 8 files changed, 297 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbd8866..9a3d048d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Graph tools accept `format: "mermaid"`: the page is also returned as a ready-to-render Mermaid `graph TD`, built from the reported edges - New tool `topology_overview`: the application, its supervision tree, registered processes and owned approved ETS tables of one application in a single call (each part still subject to the project policy) - New tool `changes_since`: compares a retained collection with a fresh observation and reports added, removed and replaced (restarted) entities; removals from partial collections are marked unconfirmed + - New tools `top_ports` (local ports by queue size or traffic counters: driver name and owner only, never addresses or command lines) and `ets_summary` (ETS memory per owner process, no table names or contents); `runtime_summary` lists the connected nodes (host redacted by default); new edge type `owns_port` - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) diff --git a/HELP.MD b/HELP.MD index 2a84cc9e..1cba6e0e 100644 --- a/HELP.MD +++ b/HELP.MD @@ -178,7 +178,8 @@ ceilings: {mcp, [ {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>]}, + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, + <<"top_ports">>, <<"ets_summary">>]}, {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>]; private tables never {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, @@ -194,7 +195,7 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | Tool | Purpose | | --- | --- | -| `runtime_summary` | OTP/ERTS versions, node (host redacted by default), uptime, schedulers, run queue, process/atom/port/ETS counts against their VM limits (`resources`), memory, session id | +| `runtime_summary` | OTP/ERTS versions, node (host redacted by default), uptime, schedulers, run queue, process/atom/port/ETS counts against their VM limits (`resources`), connected node names (host redacted by default), memory, session id | | `application_overview` | started applications, versions, declared dependencies, root supervisor ids, optional module names/behaviours; `application`, `includeModules`, `cursor` | | `supervision_tree` | typed `supervises` edges from an application/supervisor id or a named supervisor; child role, modules, restart/shutdown; `id`/`supervisor`, `maxDepth`, `includeModules`, `cursor` | | `registered_processes` | locally registered names with status/queue length and membership (`confirmed`/`inferred`/`unknown`); `application`, `cursor` | @@ -202,6 +203,8 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `ets_tables` | metadata of the approved named tables (protection, type, owner, size, memory in words and bytes) - never keys or values | | `topology_overview` | one call for one application (`name` or `application` id): the application, its supervision tree with child counts, registered processes and the approved ETS tables they own, as one collection. Parts whose own tool is not allowed by the policy are left out (`policy_denied` omission) | | `changes_since` | what changed since an earlier collection (`collectionId`): added, removed and replaced (restarted: same child, new id) entities, with `absence: unconfirmed` for removals from a partial collection. The baseline is only retained for `collection_ttl_ms` (30 s by default) and 2 collections: compare soon after the first call, else `baseline_expired` | +| `top_ports` | local ports ranked by queue size, bytes in or bytes out (`sortBy`, `limit` up to 50): driver name, counters, queue size and the owner process (`owns_port` edge). Never addresses, command lines, paths or data; a non-driver port (e.g. a spawned program) is `` | +| `ets_summary` | ETS memory per owner process, largest first (`limit` up to 50): table count and memory in words/bytes, plus node totals. No table name, key or value, no table inventory (use `ets_tables` for approved tables) | | `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | | `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line), processes currently stopped at a breakpoint (`pausedProcesses`: process id, module, line) | @@ -216,7 +219,7 @@ Graph tools return a versioned envelope: `schemaVersion`, `sessionId`, `collecti `startedAt`, `finishedAt`, `scope` (with `limitations`), `entities`, `relationships`, `complete`, `truncated`, `omissions`, `nextCursor`. Entity kinds: `application`, `process`, `module`, `ets_table` (a supervisor is a process with `role: supervisor`). Edge types: -`depends_on`, `supervises`, `owns_table`, `linked_to`, `monitors`, `belongs_to`, `uses_module`, each +`depends_on`, `supervises`, `owns_table`, `owns_port`, `linked_to`, `monitors`, `belongs_to`, `uses_module`, each with `evidence`, `observedAt` and `confidence` (`confirmed`/`inferred`/`unknown`). Links and monitors never imply supervision or message traffic. IDs are opaque, valid only within the session and change when a process restarts or a table is recreated. A collection is an observation diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index 9efb0bc3..01751973 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -33,7 +33,8 @@ -define(TOOLS, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>]). + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, + <<"top_ports">>, <<"ets_summary">>]). %% {internal key, project key, JSON key, default = ceiling} -define(LIMITS, diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index 7ac58c0f..0738277f 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -26,6 +26,7 @@ -define(SUMMARY_PAGE, 100). -define(MAX_SCANNED, 200000). -define(MAX_TOP, 50). +-define(MAX_ETS_SCAN, 50000). %%------------------------------------------------------------------------------ %% Entry point @@ -67,6 +68,8 @@ run(<<"supervision_tree">>, Args, Ctx) -> graph_tool(<<"supervision_tree">>, bui run(<<"registered_processes">>, Args, Ctx) -> graph_tool(<<"registered_processes">>, builder(<<"registered_processes">>), Args, Ctx); run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); run(<<"ets_tables">>, Args, Ctx) -> graph_tool(<<"ets_tables">>, builder(<<"ets_tables">>), Args, Ctx); +run(<<"top_ports">>, Args, Ctx) -> graph_tool(<<"top_ports">>, builder(<<"top_ports">>), Args, Ctx); +run(<<"ets_summary">>, Args, Ctx) -> graph_tool(<<"ets_summary">>, builder(<<"ets_summary">>), Args, Ctx); run(<<"changes_since">>, Args, Ctx) -> graph_tool(<<"changes_since">>, builder(<<"changes_since">>), Args, Ctx); run(<<"topology_overview">>, Args, Ctx) -> graph_tool(<<"topology_overview">>, builder(<<"topology_overview">>), Args, Ctx); run(<<"top_processes">>, Args, Ctx) -> graph_tool(<<"top_processes">>, builder(<<"top_processes">>), Args, Ctx); @@ -78,7 +81,9 @@ builder(<<"registered_processes">>) -> fun registered_processes_build/2; builder(<<"ets_tables">>) -> fun ets_tables_build/2; builder(<<"top_processes">>) -> fun top_processes_build/2; builder(<<"topology_overview">>) -> fun topology_overview_build/2; -builder(<<"changes_since">>) -> fun changes_since_build/2. +builder(<<"changes_since">>) -> fun changes_since_build/2; +builder(<<"top_ports">>) -> fun top_ports_build/2; +builder(<<"ets_summary">>) -> fun ets_summary_build/2. %% Graph tools build a Builder (entities, relationships, omissions) and a scope; %% finish_graph retains it as a collection and renders the first page. @@ -144,6 +149,7 @@ runtime_summary(Args, _Ctx) -> <<"uptimeMs">> => Uptime, <<"schedulers">> => erlang:system_info(schedulers), <<"processCount">> => erlang:system_info(process_count), + <<"connectedNodes">> => connected_nodes(Redact), <<"schedulersOnline">> => erlang:system_info(schedulers_online), <<"runQueue">> => erlang:statistics(run_queue), <<"resources">> => #{<<"processes">> => resource(process_count, process_limit), @@ -159,6 +165,20 @@ runtime_summary(Args, _Ctx) -> <<"binary">> => proplists:get_value(binary, Mem), <<"ets">> => proplists:get_value(ets, Mem)}}}. +%% Names of the visible connected nodes (hidden nodes such as the debugger helper are +%% not listed by nodes/0); the host part is redacted like the node's own name. +connected_nodes(Redact) -> + Nodes = nodes(), + #{<<"count">> => length(Nodes), + <<"names">> => [peer_text(N, Redact) || N <- lists:sublist(lists:sort(Nodes), 50)]}. + +peer_text(Node, false) -> atom_to_binary(Node, utf8); +peer_text(Node, true) -> + case binary:split(atom_to_binary(Node, utf8), <<"@">>) of + [Name, _Host] -> <">>; + [Name] -> Name + end. + %% {count, limit, usedPercent}: how close the node is to a VM limit; absent %% (null) on an OTP release that does not report it. resource(CountKey, LimitKey) -> @@ -403,7 +423,7 @@ mermaid_edge(#{<<"type">> := Type, <<"from">> := F, <<"to">> := T}) -> [<<" ">>, mermaid_id(F), <<" ">>, Arrow, <<"|">>, mermaid_text(Type), <<"| ">>, mermaid_id(T), <<"\n">>]. mermaid_label(E) -> - first_binary([maps:get(K, E, null) || K <- [<<"name">>, <<"childId">>, <<"pid">>, <<"id">>]]). + first_binary([maps:get(K, E, null) || K <- [<<"name">>, <<"driver">>, <<"childId">>, <<"pid">>, <<"id">>]]). mermaid_detail(E) -> Parts = [V || K <- [<<"role">>, <<"restart">>, <<"childState">>], V <- [maps:get(K, E, null)], @@ -1434,13 +1454,16 @@ rank_keys([Pid | T] = Pids, Item, Self, Ctx, N, Acc) -> case N rem 1000 =:= 0 andalso N > 0 andalso remaining(Ctx) =:= 0 of true -> {Acc, length(Pids)}; false -> - Acc1 = case Pid =/= Self andalso erlang:process_info(Pid, Item) of + Acc1 = case Pid =/= Self andalso rank_value(Pid, Item) of {Item, V} when is_integer(V) -> [{V, Pid} | Acc]; _ -> Acc end, rank_keys(T, Item, Self, Ctx, N + 1, Acc1) end. +rank_value(Port, Item) when is_port(Port) -> erlang:port_info(Port, Item); +rank_value(Pid, Item) -> erlang:process_info(Pid, Item). + top_entry(Pid, SortBy, Rank, Masters, B) -> Items = [registered_name, status, current_function, initial_call, reductions, memory, message_queue_len], case erlang:process_info(Pid, Items) of @@ -1473,6 +1496,159 @@ top_entity(Pid, Info, SortBy, Rank, Masters, B) -> {_, B1} = process_entity(Pid, Fields, B), {ok, B1}. +%%------------------------------------------------------------------------------ +%% top_ports: local ports ranked by queue size / bytes in / bytes out. Only the +%% driver name (never an address, command line or path), counters and the owner. +%%------------------------------------------------------------------------------ + +top_ports_build(Args, Ctx) -> + SortBy = case maps:get(<<"sortBy">>, Args, <<"queue_size">>) of + <<"input">> -> input; + <<"output">> -> output; + _ -> queue_size + end, + Limit = min(maps:get(<<"limit">>, Args, 10), ?MAX_TOP), + All = erlang:ports(), + Total = length(All), + {Scan, Cut} = case Total > ?MAX_SCANNED of + true -> {lists:sublist(All, ?MAX_SCANNED), Total - ?MAX_SCANNED}; + false -> {All, 0} + end, + {Keyed, Unscanned} = rank_keys(Scan, SortBy, self(), Ctx, 0, []), + Ranked = lists:sublist(lists:reverse(lists:sort(Keyed)), Limit), + {B1, Gone, _} = lists:foldl( + fun({_, Port}, {Acc, G, Rank}) -> + case port_entry(Port, SortBy, Rank, Acc) of + gone -> {Acc, G + 1, Rank}; + {ok, Acc1} -> {Acc1, G, Rank + 1} + end + end, {new_b(), 0, 1}, Ranked), + B2 = scan_omissions(B1, Gone, Unscanned, Cut, <<"ports">>), + Scope = #{<<"tool">> => <<"top_ports">>, + <<"sortBy">> => atom_to_binary(SortBy, utf8), + <<"limit">> => Limit, + <<"portCount">> => Total, + <<"limitations">> => + [<<"A point-in-time scan, not an atomic snapshot: ports open and close while it runs.">>, + <<"Only the driver name, byte counters, queue size and the owner process are reported; addresses, command lines, paths and data are never read.">>, + <<"driver is for ports that are not a plain driver name (for example a spawned program).">>, + <<"owns_port links a port to its connected process; it is not evidence of traffic.">>]}, + {ok, Scope, B2}. + +port_entry(Port, SortBy, Rank, B) -> + Items = [name, connected, input, output, queue_size, registered_name], + %% port_info/2 takes one item at a time; a closed port answers undefined + Info = [I || Item <- Items, I <- [erlang:port_info(Port, Item)], I =/= undefined], + case erlang:port_info(Port, name) of + undefined -> gone; + _ -> + PId = mcp_store:entity_id(process, {port, Port}), + Fields = #{<<"kind">> => <<"process">>, <<"role">> => <<"port">>, <<"pid">> => null, + <<"name">> => case lists:keyfind(registered_name, 1, Info) of + {registered_name, N} when is_atom(N) -> bin(N); + _ -> null + end, + <<"alive">> => true, + <<"rank">> => Rank, + <<"rankedBy">> => atom_to_binary(SortBy, utf8), + <<"driver">> => driver_name(proplists:get_value(name, Info)), + <<"input">> => proplists:get_value(input, Info, 0), + <<"output">> => proplists:get_value(output, Info, 0), + <<"queueSize">> => proplists:get_value(queue_size, Info, 0), + <<"bytesUnit">> => <<"bytes">>}, + B1 = add_entity(B, PId, Fields), + case proplists:get_value(connected, Info) of + Owner when is_pid(Owner), node(Owner) =:= node() -> + {OwnerId, B2} = process_entity(Owner, #{<<"role">> => <<"unknown">>}, B1), + {ok, add_rel(B2, <<"owns_port">>, OwnerId, PId, <<"port_info connected">>, <<"confirmed">>)}; + _ -> {ok, B1} + end + end. + +driver_name(Name) when is_list(Name) -> + case re:run(Name, "^[a-z][a-z0-9_]{0,31}$", [{capture, none}]) of + match -> list_to_binary(Name); + nomatch -> <<"">> + end; +driver_name(_) -> <<"">>. + +%% omissions shared by the scanning tools +scan_omissions(B, Gone, Unscanned, Cut, What) -> + B1 = case Gone of + 0 -> B; + _ -> add_omission(B, <<"disappeared">>, Gone, undefined, + <<"ranked ", What/binary, " that ended before they could be described">>, false) + end, + B2 = case Unscanned of + 0 -> B1; + _ -> add_omission(B1, <<"timeout">>, Unscanned, undefined, + <<"the time budget ended the scan; the ranking only covers the ", What/binary, + " scanned so far">>, false) + end, + case Cut of + 0 -> B2; + _ -> add_omission(B2, <<"limit_reached">>, Cut, undefined, + <<"more ", What/binary, " than the scan limit; the ranking only covers the first ones">>, false) + end. + +%%------------------------------------------------------------------------------ +%% ets_summary: ETS memory per owner process, without any table name, key or value +%%------------------------------------------------------------------------------ + +ets_summary_build(Args, Ctx) -> + Limit = min(maps:get(<<"limit">>, Args, 10), ?MAX_TOP), + All = ets:all(), + Total = length(All), + {Scan, Cut} = case Total > ?MAX_ETS_SCAN of + true -> {lists:sublist(All, ?MAX_ETS_SCAN), Total - ?MAX_ETS_SCAN}; + false -> {All, 0} + end, + {Owners, Unscanned} = ets_owners(Scan, Ctx, 0, #{}), + WordSize = erlang:system_info(wordsize), + TotalWords = lists:sum([W || {_, W} <- maps:values(Owners)]), + Ranked = lists:sublist(lists:reverse(lists:sort([{W, C, O} || {O, {C, W}} <- maps:to_list(Owners)])), Limit), + Masters = masters(Ctx), + {B1, _} = lists:foldl( + fun({Words, Count, Owner}, {Acc, Rank}) -> + {Conf, App, Evidence} = membership(Owner, #{}, Masters), + Fields = #{<<"role">> => <<"unknown">>, + <<"rank">> => Rank, + <<"application">> => case App of undefined -> null; _ -> bin(App) end, + <<"membership">> => #{<<"confidence">> => Conf, <<"evidence">> => Evidence}, + <<"ets">> => #{<<"tables">> => Count, <<"memory">> => Words, + <<"memoryUnit">> => <<"words">>, + <<"memoryBytes">> => Words * WordSize}}, + {_, Acc1} = process_entity(Owner, Fields, Acc), + {Acc1, Rank + 1} + end, {new_b(), 1}, Ranked), + B2 = scan_omissions(B1, 0, Unscanned, Cut, <<"tables">>), + Scope = #{<<"tool">> => <<"ets_summary">>, + <<"limit">> => Limit, + <<"tableCount">> => Total, + <<"ownerCount">> => maps:size(Owners), + <<"totalMemory">> => #{<<"words">> => TotalWords, <<"bytes">> => TotalWords * WordSize}, + <<"limitations">> => + [<<"Per-owner aggregates only: no table name, key, object or value is read or returned, and no table inventory is listed.">>, + <<"Private tables are counted in the aggregates (their owner and size, nothing else).">>, + <<"A point-in-time scan, not an atomic snapshot; tables are created and deleted while it runs.">>, + <<"For the metadata of a specific table use ets_tables (approved names only).">>]}, + {ok, Scope, B2}. + +%% -> {#{OwnerPid => {Tables, Words}}, NotScanned} +ets_owners([], _Ctx, _N, Acc) -> {Acc, 0}; +ets_owners([Tab | T] = Tabs, Ctx, N, Acc) -> + case N rem 500 =:= 0 andalso N > 0 andalso remaining(Ctx) =:= 0 of + true -> {Acc, length(Tabs)}; + false -> + Acc1 = try {ets:info(Tab, owner), ets:info(Tab, memory)} of + {Owner, Words} when is_pid(Owner), node(Owner) =:= node(), is_integer(Words) -> + maps:update_with(Owner, fun({C, W}) -> {C + 1, W + Words} end, {1, Words}, Acc); + _ -> Acc + catch _:_ -> Acc + end, + ets_owners(T, Ctx, N + 1, Acc1) + end. + %%------------------------------------------------------------------------------ %% ets_tables (metadata of approved named tables only) %%------------------------------------------------------------------------------ diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 6c709cad..6c8278b6 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -104,6 +104,7 @@ def(<<"runtime_summary">>) -> <<"otpRelease">> => str(), <<"ertsVersion">> => str(), <<"node">> => str(), <<"uptimeMs">> => int(), <<"schedulers">> => int(), <<"processCount">> => int(), <<"schedulersOnline">> => int(), <<"runQueue">> => int(), + <<"connectedNodes">> => #{<<"type">> => <<"object">>}, <<"resources">> => #{<<"type">> => <<"object">>}, <<"memory">> => #{<<"type">> => <<"object">>}}, [<<"schemaVersion">>, <<"sessionId">>])}; @@ -202,6 +203,35 @@ def(<<"changes_since">>) -> <<"format">> => format_prop()}, [<<"collectionId">>]), graph_output()}; +def(<<"top_ports">>) -> + {<<"Top ports">>, + <<"Rank the local ports of the node (sockets, files, spawned programs) by queue size, bytes in or bytes out and " + "return the top ones (default 10, max 50) with their driver name, byte counters, queue size and the owner " + "process (owns_port edge). Addresses, command lines, paths and data are never read; a port that is not a plain " + "driver name is reported as . Point-in-time scan.">>, + obj(#{<<"sortBy">> => #{<<"type">> => <<"string">>, + <<"enum">> => [<<"queue_size">>, <<"input">>, <<"output">>], + <<"description">> => <<"Ranking criterion. Default queue_size.">>}, + <<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 50, + <<"description">> => <<"Number of ports to return. Default 10.">>}, + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), + graph_output()}; + +def(<<"ets_summary">>) -> + {<<"ETS summary">>, + <<"ETS memory aggregated per owner process, largest first (default 10 owners, max 50): number of tables and " + "memory in words and bytes, with application membership evidence, plus node totals in the scope. No table " + "name, key, object or value is read and no table inventory is listed; use ets_tables for the metadata of a " + "specific approved table.">>, + obj(#{<<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 50, + <<"description">> => <<"Number of owners to return. Default 10.">>}, + <<"cursor">> => cursor_prop(), + <<"detail">> => detail_prop(), <<"pageSize">> => page_size_prop(), + <<"format">> => format_prop()}, []), + graph_output()}; + def(<<"top_processes">>) -> {<<"Top processes">>, <<"Rank the local processes of the node by message queue length, reductions or memory and return the top ones " diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index 6ad03cfa..27e5dcac 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -64,7 +64,7 @@ defaults_without_project_file(Config) -> ?assertEqual(mcp_policy:all_tools(), maps:get(allowed_tools, C)), ?assertEqual([], maps:get(allowed_ets_tables, C)), ?assertEqual(mcp_policy:ceilings(), maps:get(limits, C)), - ?assertEqual(10, length(maps:get(allowed_tools, C))), + ?assertEqual(12, length(maps:get(allowed_tools, C))), %% a project without an mcp term uses the same built-in policy write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), ?assertEqual({ok, C}, mcp_policy:resolve(input(Config, #{}))). diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 2b6052e8..cf5e87bc 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -34,6 +34,8 @@ all() -> topology_overview_leaves_out_parts_the_policy_denies, prompt_guides_an_agent_and_checks_its_argument, changes_since_reports_replaced_and_removed_children, + top_ports_reports_drivers_and_owners_only, + ets_summary_aggregates_per_owner_without_names, paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, @@ -163,7 +165,7 @@ decode(Bin) -> %% unless the case chooses the detail itself (see agent_summary_is_compact). call(Config, Tool, Args0) when is_map(Args0) -> Graph = [<<"application_overview">>, <<"supervision_tree">>, <<"registered_processes">>, - <<"process_info">>, <<"ets_tables">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>], + <<"process_info">>, <<"ets_tables">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, <<"top_ports">>, <<"ets_summary">>], Args = case lists:member(Tool, Graph) andalso not maps:is_key(<<"detail">>, Args0) of true -> Args0#{<<"detail">> => <<"full">>}; false -> Args0 @@ -319,7 +321,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), ?assertEqual(lists:sort(mcp_policy:all_tools()), Names), - ?assertEqual(10, length(Tools)), + ?assertEqual(12, length(Tools)), [begin ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, <<"outputSchema">> := #{<<"type">> := <<"object">>}, @@ -378,6 +380,7 @@ runtime_summary_redacts_node_host(Config) -> ?assert(maps:get(<<"processCount">>, S) > 10), ?assert(maps:get(<<"uptimeMs">>, S) >= 0), ?assert(maps:get(<<"runQueue">>, S) >= 0), + ?assertMatch(#{<<"count">> := N, <<"names">> := L} when is_integer(N) andalso is_list(L), maps:get(<<"connectedNodes">>, S)), ?assertEqual(erlang:system_info(schedulers_online), maps:get(<<"schedulersOnline">>, S)), #{<<"processes">> := #{<<"count">> := PC, <<"limit">> := PL, <<"usedPercent">> := PP}, <<"atoms">> := #{<<"count">> := AC, <<"limit">> := AL}} = maps:get(<<"resources">>, S), @@ -1142,6 +1145,78 @@ changes_since_reports_replaced_and_removed_children(Config) -> lists:foreach(fun({_, P, _, _}) -> supervisor:terminate_child(mcp_fx_dyn_sup, P) end, supervisor:which_children(mcp_fx_dyn_sup)). +top_ports_reports_drivers_and_owners_only(Config) -> + {ok, L} = gen_tcp:listen(0, [binary, {active, false}, {ip, {127, 0, 0, 1}}]), + {ok, LPort} = inet:port(L), + {ok, C} = gen_tcp:connect({127, 0, 0, 1}, LPort, [binary, {active, false}]), + {ok, A} = gen_tcp:accept(L, 2000), + ok = gen_tcp:send(C, binary:copy(<<"SENTINEL_PORT_DATA">>, 2000)), + Cat = open_port({spawn_executable, os:find_executable("cat")}, [binary]), + try + S = ok_call(Config, <<"top_ports">>, #{<<"sortBy">> => <<"output">>, <<"limit">> => 50}), + Ents = maps:get(<<"entities">>, S), + Ports = [E || #{<<"role">> := <<"port">>} = E <- Ents], + ?assert(length(Ports) >= 3), + ?assertEqual(lists:seq(1, length(Ports)), [maps:get(<<"rank">>, E) || E <- Ports]), + Outs = [maps:get(<<"output">>, E) || E <- Ports], + ?assertEqual(lists:reverse(lists:sort(Outs)), Outs), + Drivers = [maps:get(<<"driver">>, E) || E <- Ports], + ?assert(lists:member(<<"tcp_inet">>, Drivers)), + %% a spawned program is never named: no command line or path in the result + ?assert(lists:member(<<"">>, Drivers)), + Dump = iolist_to_binary(io_lib:format("~p", [S])), + [?assertEqual(nomatch, binary:match(Dump, X)) + || X <- [list_to_binary(os:find_executable("cat")), <<"SENTINEL">>, <<"127.0.0.1">>]], + %% owner edge: the test process owns the sockets and the program port + Owns = [R || #{<<"type">> := <<"owns_port">>} = R <- maps:get(<<"relationships">>, S)], + ?assert(length(Owns) >= 3), + [?assertMatch(#{<<"evidence">> := <<"port_info connected">>, <<"confidence">> := <<"confirmed">>}, R) || R <- Owns], + Allowed = [<<"id">>, <<"kind">>, <<"role">>, <<"pid">>, <<"name">>, <<"alive">>, <<"rank">>, <<"rankedBy">>, + <<"driver">>, <<"input">>, <<"output">>, <<"queueSize">>, <<"bytesUnit">>], + [?assertEqual([], maps:keys(E) -- Allowed) || E <- Ports], + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"top_ports">>, #{<<"sortBy">> => <<"peer">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"top_ports">>, #{<<"limit">> => 99})) + after + catch port_close(Cat), + [catch gen_tcp:close(X) || X <- [A, C, L]] + end. + +ets_summary_aggregates_per_owner_without_names(Config) -> + Self = self(), + Big = spawn(fun() -> + ets:new(mcp_fx_hidden_big, [named_table, public]), + [ets:insert(mcp_fx_hidden_big, {I, "SENTINEL_BIG_VALUE", lists:seq(1, 20)}) || I <- lists:seq(1, 5000)], + Self ! ready, + receive stop -> ok end + end), + receive ready -> ok after 5000 -> ct:fail(no_big_table) end, + try + S = ok_call(Config, <<"ets_summary">>, #{<<"limit">> => 5}), + Ents = maps:get(<<"entities">>, S), + [First | _] = Ents, + ?assertEqual(5, length(Ents)), + %% the biggest owner is first, with one table + ?assertEqual(list_to_binary(pid_to_list(Big)), maps:get(<<"pid">>, First)), + #{<<"tables">> := 1, <<"memory">> := W, <<"memoryUnit">> := <<"words">>, <<"memoryBytes">> := Bytes} = + maps:get(<<"ets">>, First), + ?assert(W > 1000), + ?assertEqual(W * erlang:system_info(wordsize), Bytes), + ?assertEqual(1, maps:get(<<"rank">>, First)), + Mems = [maps:get(<<"memory">>, maps:get(<<"ets">>, E)) || E <- Ents], + ?assertEqual(lists:reverse(lists:sort(Mems)), Mems), + #{<<"tableCount">> := T, <<"ownerCount">> := O, <<"totalMemory">> := #{<<"words">> := TW, <<"bytes">> := _}} = + maps:get(<<"scope">>, S), + ?assert(T >= 3 andalso O >= 2 andalso TW >= W), + %% aggregates only: no table name (named, private or hidden), key or value anywhere + Dump = iolist_to_binary(io_lib:format("~p", [S])), + [?assertEqual(nomatch, binary:match(Dump, X)) + || X <- [<<"mcp_fx_hidden_big">>, <<"mcp_fx_orders">>, <<"mcp_fx_private">>, <<"SENTINEL">>]], + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"ets_summary">>, #{<<"limit">> => 51})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"ets_summary">>, #{<<"table">> => <<"x">>})) + after + exit(Big, kill) + end. + prompt_guides_an_agent_and_checks_its_argument(Config) -> #{<<"result">> := #{<<"prompts">> := [P]}} = rpc(Config, <<"prompts/list">>, #{}), ?assertMatch(#{<<"name">> := <<"map_application">>, diff --git a/mcp_security.md b/mcp_security.md index 2f50978b..52102f07 100644 --- a/mcp_security.md +++ b/mcp_security.md @@ -19,7 +19,7 @@ port. All requests, parameters and identifiers are untrusted. | Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | | Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | | Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | -| Least privilege | Ten fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | +| Least privilege | Twelve fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | | No execution | No eval, `rpc:call`, shell, code loading, message sending, `sys` calls, tracing, breakpoint changes, ETS writes or reads | | Data minimisation | No process dictionary, stack, mailbox, state, child start arguments, application environment, source, code paths, cookies; ETS: metadata of approved, non-private named tables only; node host redacted by default; child identifiers are printed with a depth/size bound | | Safe identifiers | Registered names are resolved with `binary_to_existing_atom/2` only; pids must be local (`<0.N.M>`); no `binary_to_term/1` on input | From de6cbc57ea39514e196720af0680f8d22bf6f3a8 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 16:56:53 +0200 Subject: [PATCH 11/20] Document post-V1 MCP additions in mcp-tasks and align the sample prompt Co-Authored-By: Claude Sonnet 5.5 --- HELP.MD | 26 ++++++++++++++------------ mcp-tasks.md | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/HELP.MD b/HELP.MD index 1cba6e0e..35e36b3e 100644 --- a/HELP.MD +++ b/HELP.MD @@ -254,23 +254,25 @@ coverage gaps, without guessing beyond what the tools return: ```text Using the erlang-otp-topology-inspector MCP server, map the OTP topology of application -in the node I am debugging. +in the node I am debugging. Use only the tools that tools/list returns. Steps: -1. runtime_summary, then application_overview to find 's id and its root supervisors. -2. supervision_tree from each root. If an omission says limit_reached or timeout, expand that - subtree with the id it gives. -3. registered_processes(application=) to catch registered workers outside the tree. -4. process_info only for processes that matter: supervisors, registered workers, anything with - a non-zero message queue or many links/monitors. -5. ets_tables and debug_session only if they relate to . +1. topology_overview(name=""): the application, its supervision tree, registered processes + and owned approved ETS tables in one call. If an omission says limit_reached or timeout, + expand that subtree with supervision_tree and the id it gives. (Without topology_overview: + runtime_summary, application_overview, supervision_tree from each root, + registered_processes(application=).) +2. top_processes (or process_info) only for processes that matter: supervisors, registered + workers, anything with a non-zero message queue. If debug_session reports processes stopped + at a breakpoint, say so: their supervisors may be reported as unavailable_while_paused. +3. ets_tables and debug_session only if they relate to the application. Output: -- A Mermaid `graph TD` of the supervision tree (edge = supervises; label children with kind - and restart type). +- A Mermaid `graph TD` of the supervision tree (edge = supervises; label children with kind and + restart type). Tools take format=mermaid and return the diagram from the reported edges. - A table: name/pid, module, kind, restart, msg queue len, membership (confirmed/inferred). - "Coverage gaps": every truncated/omission/timeout reported by the tools. -- 3–5 observations (single points of failure, deep trees, hot queues, temporary children). +- 3-5 observations (single points of failure, deep trees, hot queues, temporary children). Rules: use only what the tools returned. Structural edges are not message traffic, so do not describe runtime call flows. Keep detail=summary unless I ask for the full JSON. @@ -280,7 +282,7 @@ Example of the agent's answer (supervision tree diagram, then the process table) ![MCP inspector result](images/vscode-erlang-mcp-results.png) -Narrower questions work too, e.g. *"Why is `` slow? Use process_info on it and compare +Narrower questions work too, e.g. *"Which processes are hot? Use top_processes by message_queue_len and say which application each belongs to"*, *"Why is `` slow? Use process_info on it and compare its queue length and reductions with its siblings in the supervision tree"*, or *"Where are my breakpoints in the supervision tree? Use debug_session and supervision_tree with includeModules"*. diff --git a/mcp-tasks.md b/mcp-tasks.md index 14552031..b411d8be 100644 --- a/mcp-tasks.md +++ b/mcp-tasks.md @@ -99,7 +99,7 @@ The policy example shows the defaults. For an approved named table, use a binary ### Read-Only Tools -Implement exactly the seven tools below, with discoverable input/output schemas and descriptions. Do not expose prompts or resources in V1. +Implement exactly the seven tools below, with discoverable input/output schemas and descriptions. Do not expose prompts or resources in V1. *(Superseded after V1: five more tools, one prompt and one edge type were added deliberately; see "Post-V1 additions" under Implementation Progress. The text below keeps describing the original V1 contract.)* #### `application_overview` @@ -577,7 +577,17 @@ Deviation: `proc_lib:initial_call/1` (reads the process dictionary internally, r **T15.** Docs: HELP.MD, mcp_security.md, DevelopersReadme.md, CHANGELOG.md; CI step added. Not done: startup from a packaged VSIX, OTP 25/27/28 runs. -Next: T11 real-client test, T12/T14 gaps above. +Next: T11 real-client test, T12/T14 gaps above (still open; deliberately not scheduled). + +### Post-V1 additions + +Decided with the user after the V1 work order, all within the read-only/metadata-only guardrails (no new write, eval, mailbox, ETS-value or source access). The toolset is now twelve tools; the original seven are unchanged apart from the additive fields noted. + +- `top_processes` (queue length / reductions / memory ranking), `top_ports` (queue size / traffic counters; driver name and owner only, never addresses or command lines), `ets_summary` (ETS memory per owner, no table names), `topology_overview` (one application in one collection; each part obeys its own tool's allowlist entry), `changes_since` (diff against a retained collection; baseline limited by `collection_ttl_ms` / `max_collections`). +- Additive fields: `children` counts on supervisors (`supervisor:count_children`), `pausedProcesses` in `debug_session` and a `debugger` field in `process_info` (from `int:snapshot/0`; a supervisor that is itself stopped at a breakpoint is reported as `unavailable_while_paused`), run queue / VM resource usage / connected node names in `runtime_summary`, `format: "mermaid"` on graph tools. +- New edge type `owns_port` (MAP-03 extension). New MCP prompt `map_application` (the only exception to "no prompts"; static text, argument-checked, grants nothing). +- Not added: restart intensity/period (no safe API), distributed traversal, tracing, any mutation. +- Tests: `./rebar3 ct --dir apps/erlangbridge/test/mcp` (74 cases). Plain `./rebar3 ct` does not include this directory. ## Acceptance Criteria From d5436f7cec352dfc0c9224d920e2e622dcb3a365 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 17:08:32 +0200 Subject: [PATCH 12/20] Test all MCP tools and the prompt against a real launched target Co-Authored-By: Claude Sonnet 5.5 --- test/test-suite/mcp/mcp_debug_adapter.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/test-suite/mcp/mcp_debug_adapter.test.ts b/test/test-suite/mcp/mcp_debug_adapter.test.ts index c82b8c12..de9f2585 100644 --- a/test/test-suite/mcp/mcp_debug_adapter.test.ts +++ b/test/test-suite/mcp/mcp_debug_adapter.test.ts @@ -183,6 +183,38 @@ suite('MCP: debug adapter end-to-end (real erl target)', function () { assert.ok(await refused(started.url), 'endpoint must be closed'); }); + test('all tools and the prompt answer in a real target (default policy)', async () => { + client = new DapClient(); + await client.launch(project, settings()); + const started = await client.waitEvent('erlangMcp', b => b.status !== undefined); + assert.strictEqual(started.status, 'started', JSON.stringify(started)); + const token = JSON.parse(fs.readFileSync(started.descriptor, 'utf8')).token; + const tools = (await post(started.url, token, { jsonrpc: '2.0', id: 1, method: 'tools/list' })).json.result.tools.map((t: any) => t.name); + assert.strictEqual(tools.length, 12, tools.join(',')); + const ok = async (name: string, args: any = {}) => { + const r = await call(started.url, token, name, args); + assert.ok(r.json.result && r.json.result.isError === false, name + ': ' + JSON.stringify(r.json)); + return r.json.result.structuredContent; + }; + const rt = await ok('runtime_summary'); + assert.ok(rt.resources.processes.count > 0 && rt.resources.atoms.limit > 0); + const top = await ok('top_processes', { limit: 3, format: 'mermaid' }); + assert.strictEqual(top.entities.length, 3); + assert.ok(top.mermaid.startsWith('graph TD')); + assert.ok((await ok('top_ports', { limit: 3 })).entities.length > 0); + assert.ok((await ok('ets_summary', { limit: 3 })).entities.length > 0); + const topo = await ok('topology_overview', { name: 'kernel', format: 'mermaid' }); + assert.ok(topo.relationships.some((r: any) => r.type === 'supervises'), 'kernel supervision tree'); + assert.ok(topo.mermaid.includes('-->')); + const changes = await ok('changes_since', { collectionId: topo.collectionId }); + assert.strictEqual(changes.scope.baselineTool, 'topology_overview'); + const prompt = await post(started.url, token, { jsonrpc: '2.0', id: 1, method: 'prompts/get', + params: { name: 'map_application', arguments: { application: 'kernel' } } }); + assert.ok(prompt.json.result.messages[0].content.text.includes('kernel')); + assert.ok(!JSON.stringify(client.events).includes(token), 'token leaked in a DAP message'); + await client.request('disconnect', { terminateDebuggee: true }); + }); + test('disabled (default): no listener, no event, debugging works', async () => { client = new DapClient(); await client.launch(project, {}); From 7abe054154bb396cffa5f4df6082fdbe429ca93e Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 17:29:55 +0200 Subject: [PATCH 13/20] MCP inspector: review fixes and opt-in developer tier Fixes: ets_summary now counts only approved non-private tables, top_ports names only well-known drivers, changes_since observes with the baseline's detail, debug_session shares one time budget, topology_overview walks the tree once, inspector-owned processes and ports are excluded by module, child ids and states are redacted (secret-like keys, URL credentials), the bearer token and cursor secret are redacted from crash reports, non-ASCII workspace paths resolve the project policy, \z anchors. Developer tier (named in allowed_tools, never default): process_state, mailbox_sample, ets_sample; allowed_ets_tables may be `all`; limits can be raised above their defaults up to higher ceilings. Co-Authored-By: Claude Sonnet 5.5 --- apps/erlangbridge/src/mcp/mcp_encoder.erl | 40 ++- apps/erlangbridge/src/mcp/mcp_policy.erl | 95 ++++-- apps/erlangbridge/src/mcp/mcp_runtime.erl | 318 +++++++++++++++--- apps/erlangbridge/src/mcp/mcp_server.erl | 9 + apps/erlangbridge/src/mcp/mcp_store.erl | 9 + apps/erlangbridge/src/mcp/mcp_tools.erl | 47 ++- .../test/mcp/mcp_encoder_SUITE.erl | 17 + .../test/mcp/mcp_policy_SUITE.erl | 51 ++- .../test/mcp/mcp_server_SUITE.erl | 113 ++++++- .../erlangbridge/test/mcp/mcp_store_SUITE.erl | 2 +- 10 files changed, 604 insertions(+), 97 deletions(-) diff --git a/apps/erlangbridge/src/mcp/mcp_encoder.erl b/apps/erlangbridge/src/mcp/mcp_encoder.erl index 7be2b47a..03c49195 100644 --- a/apps/erlangbridge/src/mcp/mcp_encoder.erl +++ b/apps/erlangbridge/src/mcp/mcp_encoder.erl @@ -3,7 +3,7 @@ %% exposes an executable handle: funs, ports, references and pids become text. -module(mcp_encoder). --export([term/2, text/2, safe_binary/2, hex/1, iso8601/1, pid_text/1]). +-export([term/2, text/2, safe_binary/2, hex/1, iso8601/1, pid_text/1, scrub/1]). -define(SECRET_WORDS, ["password", "passwd", "secret", "token", "cookie", "credential", "apikey", "api_key", "private_key", "auth"]). @@ -16,10 +16,34 @@ term(Term, Limits) -> text(Term, Limits) -> Max = maps:get(max_binary_bytes, Limits, 4096), Depth = maps:get(max_depth, Limits, 16), - Str = try lists:flatten(io_lib:format("~0P", [Term, Depth])) + Str = try lists:flatten(io_lib:format("~0P", [redact(Term, Depth), Depth])) catch _:_ -> "" end, - safe_binary(unicode:characters_to_binary(Str, unicode, utf8), Max). + safe_binary(scrub(unicode:characters_to_binary(Str, unicode, utf8)), Max). + +%% Best-effort redaction of what looks like a credential: the value of a key/value +%% pair or map entry whose key names a secret (password, token, ...). Applied to the +%% printed form of terms (child ids, ...); a record without keys cannot be redacted. +redact(_, D) when D =< 0 -> ''; +redact({K, V}, D) when is_atom(K); is_binary(K) -> + case secret_key(K) of + true -> {K, ''}; + false -> {K, redact(V, D - 1)} + end; +redact(T, D) when is_tuple(T) -> list_to_tuple([redact(E, D - 1) || E <- lists:sublist(tuple_to_list(T), 64)]); +redact(M, D) when is_map(M) -> + maps:map(fun(K, V) -> case secret_key(K) of true -> ''; false -> redact(V, D - 1) end end, M); +redact([H | T], D) -> [redact(H, D - 1) | redact_tail(T, D - 1, 64)]; +redact(T, _) -> T. + +redact_tail(_, _, 0) -> []; +redact_tail([H | T], D, N) -> [redact(H, D) | redact_tail(T, D, N - 1)]; +redact_tail(Tail, _, _) -> Tail. + +%% user:password@ inside a URL-like text +scrub(Bin) when is_binary(Bin) -> + re:replace(Bin, "://[^/@\\s:]+:[^@\\s]+@", "://@", [global, {return, binary}]); +scrub(Other) -> Other. enc(_, Depth, #{max_depth := Max}) when Depth > Max -> <<"">>; @@ -31,7 +55,10 @@ enc(undefined, _, _) -> null; enc(T, _, _) when is_atom(T) -> safe_binary(atom_to_binary(T, utf8), 255); enc(T, _, #{max_binary_bytes := Max}) when is_binary(T) -> - binary(T, Max); + case binary(T, Max) of + B when is_binary(B) -> scrub(B); + Other -> Other + end; enc(T, _, _) when is_pid(T) -> pid_text(T); enc(T, _, _) when is_port(T) -> #{<<"port">> => list_bin(erlang:port_to_list(T))}; enc(T, _, _) when is_reference(T) -> #{<<"ref">> => list_bin(erlang:ref_to_list(T))}; @@ -41,6 +68,9 @@ enc(T, _, _) when is_function(T) -> {module, M} -> #{<<"fun">> => safe_binary(atom_to_binary(M, utf8), 255)}; _ -> #{<<"fun">> => <<"unknown">>} end; +enc({K, V}, Depth, Limits) when is_atom(K); is_binary(K) -> + %% {password, "x"} style pairs (proplists, keyword options) + #{<<"tuple">> => [enc(K, Depth + 1, Limits), enc_value(K, V, Depth + 1, Limits)]}; enc(T, Depth, Limits) when is_tuple(T) -> #{<<"tuple">> => enc_list(tuple_to_list(T), Depth + 1, Limits)}; enc(T, Depth, Limits) when is_map(T) -> @@ -52,7 +82,7 @@ enc(T, Depth, Limits) when is_map(T) -> enc([], _, _) -> []; enc(T, Depth, Limits) when is_list(T) -> case printable(T) of - true -> safe_binary(unicode:characters_to_binary(T, unicode, utf8), maps:get(max_binary_bytes, Limits)); + true -> safe_binary(scrub(unicode:characters_to_binary(T, unicode, utf8)), maps:get(max_binary_bytes, Limits)); false -> enc_list(T, Depth + 1, Limits) end; enc(_, _, _) -> diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index 01751973..46f10eaf 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -4,7 +4,7 @@ %% * VS Code settings (erlang.mcp.enabled/host/port): activation and binding; %% * the optional top-level `{mcp, [...]}` term of the target project's %% rebar.config: tool/table allowlists and *lower* resource limits. -%% Neither can relax the built-in ceilings. +%% Neither can exceed the built-in ceilings. %% %% The project file is read by a bounded, non-evaluating reader (no %% rebar.config.script, no profile merging, no includes). It interns the atoms @@ -21,7 +21,8 @@ -include_lib("kernel/include/file.hrl"). --export([all_tools/0, defaults/0, ceilings/0, limit/2, tool_allowed/2]). +-export([all_tools/0, default_tools/0, defaults/0, default_limits/0, ceilings/0, limit/2, tool_allowed/2]). +-export([ets_table_allowed/2]). -export([resolve/1, read_project_policy/2, normalize_project_term/1]). -export([validate_host/1, validate_config/1, to_json_map/1, from_json_map/1, validate_token/1]). -export([cli/0]). @@ -30,25 +31,32 @@ -define(MAX_PROJECT_TOKENS, 20000). -define(PROJECT_FILE, "rebar.config"). --define(TOOLS, [<<"runtime_summary">>, <<"application_overview">>, - <<"supervision_tree">>, <<"process_info">>, - <<"registered_processes">>, <<"ets_tables">>, - <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, - <<"top_ports">>, <<"ets_summary">>]). - -%% {internal key, project key, JSON key, default = ceiling} +%% Enabled unless the project policy narrows them (metadata only). +-define(DEFAULT_TOOLS, [<<"runtime_summary">>, <<"application_overview">>, + <<"supervision_tree">>, <<"process_info">>, + <<"registered_processes">>, <<"ets_tables">>, + <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, + <<"top_ports">>, <<"ets_summary">>]). +%% Developer tier: they return data of the debugged application (a bounded process +%% state, a few mailbox messages, a few table rows), so a project must name them in +%% allowed_tools; they are never enabled by default. +-define(DEV_TOOLS, [<<"process_state">>, <<"mailbox_sample">>, <<"ets_sample">>]). +-define(TOOLS, (?DEFAULT_TOOLS ++ ?DEV_TOOLS)). + +%% {internal key, project key, JSON key, default, ceiling}: a project may raise a +%% limit above its default up to the ceiling, never above. -define(LIMITS, - [{max_request_bytes, max_request_bytes, <<"maxRequestBytes">>, 16384}, - {max_result_bytes, max_result_bytes, <<"maxResultBytes">>, 262144}, - {max_items, max_items, <<"maxItems">>, 500}, - {max_depth, max_depth, <<"maxDepth">>, 16}, - {max_binary_bytes, max_binary_bytes, <<"maxBinaryBytes">>, 4096}, - {max_traversal_depth, max_traversal_depth, <<"maxTraversalDepth">>, 8}, - {max_concurrency, max_concurrency, <<"maxConcurrency">>, 2}, - {request_timeout_ms, request_timeout_ms, <<"requestTimeoutMs">>, 3000}, - {collection_ttl_ms, collection_ttl_ms, <<"collectionTtlMs">>, 30000}, - {max_collection_bytes, max_collection_bytes, <<"maxCollectionBytes">>, 1048576}, - {max_collections, max_collections, <<"maxCollections">>, 2}]). + [{max_request_bytes, max_request_bytes, <<"maxRequestBytes">>, 16384, 65536}, + {max_result_bytes, max_result_bytes, <<"maxResultBytes">>, 262144, 2097152}, + {max_items, max_items, <<"maxItems">>, 500, 5000}, + {max_depth, max_depth, <<"maxDepth">>, 16, 32}, + {max_binary_bytes, max_binary_bytes, <<"maxBinaryBytes">>, 4096, 65536}, + {max_traversal_depth, max_traversal_depth, <<"maxTraversalDepth">>, 8, 16}, + {max_concurrency, max_concurrency, <<"maxConcurrency">>, 2, 8}, + {request_timeout_ms, request_timeout_ms, <<"requestTimeoutMs">>, 3000, 15000}, + {collection_ttl_ms, collection_ttl_ms, <<"collectionTtlMs">>, 30000, 600000}, + {max_collection_bytes, max_collection_bytes, <<"maxCollectionBytes">>, 1048576, 8388608}, + {max_collections, max_collections, <<"maxCollections">>, 2, 16}]). %% Keys a project term must never carry (owned by VS Code / the runtime). -define(FORBIDDEN_PROJECT_KEYS, @@ -56,16 +64,24 @@ credentials, cookie, authorization, bearer]). all_tools() -> ?TOOLS. +default_tools() -> ?DEFAULT_TOOLS. + +default_limits() -> + maps:from_list([{K, D} || {K, _, _, D, _} <- ?LIMITS]). ceilings() -> - maps:from_list([{K, D} || {K, _, _, D} <- ?LIMITS]). + maps:from_list([{K, C} || {K, _, _, _, C} <- ?LIMITS]). defaults() -> #{host => "127.0.0.1", port => 0, - allowed_tools => ?TOOLS, + allowed_tools => ?DEFAULT_TOOLS, allowed_ets_tables => [], - limits => ceilings()}. + limits => default_limits()}. + +%% Is the named table approved? allowed_ets_tables is a list of exact names, or `all`. +ets_table_allowed(_Name, #{allowed_ets_tables := all}) -> true; +ets_table_allowed(Name, #{allowed_ets_tables := Names}) -> lists:member(Name, Names). limit(Key, #{limits := Limits}) -> maps:get(Key, Limits). @@ -169,7 +185,11 @@ finish(Host, Port, Project) -> valid_port(P) -> is_integer(P) andalso P >= 0 andalso P =< 65535. -to_list(B) when is_binary(B) -> binary_to_list(B); +to_list(B) when is_binary(B) -> + case unicode:characters_to_list(B) of + L when is_list(L) -> L; + _ -> binary_to_list(B) + end; to_list(L) when is_list(L) -> L. %%------------------------------------------------------------------------------ @@ -322,6 +342,8 @@ project_key({allowed_tools, Tools}, Acc) -> length(Names) =:= length(lists:usort(Names)) orelse throw("allowed_tools has duplicates"), lists:foreach(fun(N) -> lists:member(N, ?TOOLS) orelse throw("allowed_tools has an unknown tool name") end, Names), Acc#{allowed_tools => Names}; +project_key({allowed_ets_tables, all}, Acc) -> + Acc#{allowed_ets_tables => all}; project_key({allowed_ets_tables, Tables}, Acc) -> Names = binary_list(Tables, "allowed_ets_tables"), length(Names) =:= length(lists:usort(Names)) orelse throw("allowed_ets_tables has duplicates"), @@ -337,7 +359,7 @@ project_key({limits, Limits}, Acc) -> fun({K, V}, M) -> case lists:keyfind(K, 2, ?LIMITS) of false -> throw(io_lib:format("unknown limit '~w'", [K])); - {Internal, _, _, Ceiling} -> + {Internal, _, _, _, Ceiling} -> (is_integer(V) andalso V > 0 andalso V =< Ceiling) orelse throw(io_lib:format("limit '~w' must be a positive integer <= ~w", [K, Ceiling])), M#{Internal => V} @@ -365,8 +387,9 @@ validate_config(#{host := Host, port := Port, allowed_tools := Tools, fun() -> (is_list(Tools) andalso lists:all(fun(T) -> lists:member(T, ?TOOLS) end, Tools) andalso length(Tools) =:= length(lists:usort(Tools))) orelse {error, "invalid MCP tool allowlist"} end, - fun() -> (is_list(Tables) andalso lists:all(fun is_binary/1, Tables) - andalso length(Tables) =:= length(lists:usort(Tables))) + fun() -> Tables =:= all orelse + (is_list(Tables) andalso lists:all(fun is_binary/1, Tables) + andalso length(Tables) =:= length(lists:usort(Tables))) orelse {error, "invalid MCP ETS table allowlist"} end, fun() -> validate_limits(Limits) end], run_checks(Checks); @@ -389,11 +412,11 @@ literal_loopback(Host) when is_list(Host) -> literal_loopback(_) -> false. validate_limits(Limits) when is_map(Limits) -> - Expected = [K || {K, _, _, _} <- ?LIMITS], + Expected = [K || {K, _, _, _, _} <- ?LIMITS], case lists:sort(maps:keys(Limits)) =:= lists:sort(Expected) of false -> {error, "MCP limits are incomplete or unknown"}; true -> - Bad = [K || {K, _, _, Ceiling} <- ?LIMITS, + Bad = [K || {K, _, _, _, Ceiling} <- ?LIMITS, V <- [maps:get(K, Limits)], not (is_integer(V) andalso V > 0 andalso V =< Ceiling)], case Bad of @@ -424,8 +447,8 @@ to_json_map(#{host := Host, port := Port, allowed_tools := Tools, #{<<"host">> => list_to_binary(Host), <<"port">> => Port, <<"allowedTools">> => Tools, - <<"allowedETSTables">> => Tables, - <<"limits">> => maps:from_list([{J, maps:get(K, Limits)} || {K, _, J, _} <- ?LIMITS])}. + <<"allowedETSTables">> => case Tables of all -> <<"*">>; _ -> Tables end, + <<"limits">> => maps:from_list([{J, maps:get(K, Limits)} || {K, _, J, _, _} <- ?LIMITS])}. from_json_map(#{<<"host">> := Host, <<"port">> := Port, <<"allowedTools">> := Tools, <<"allowedETSTables">> := Tables, <<"limits">> := Limits} = M) @@ -437,13 +460,14 @@ from_json_map(#{<<"host">> := Host, <<"port">> := Port, <<"allowedTools">> := To case token_of(M) of {error, _} = TE -> TE; _ -> - KnownLimits = [J || {_, _, J, _} <- ?LIMITS], + KnownLimits = [J || {_, _, J, _, _} <- ?LIMITS], case maps:keys(maps:without(KnownLimits, Limits)) of [] -> Internal = maps:from_list([{K, maps:get(J, Limits)} - || {K, _, J, _} <- ?LIMITS, maps:is_key(J, Limits)]), + || {K, _, J, _, _} <- ?LIMITS, maps:is_key(J, Limits)]), Config = #{host => binary_to_list(Host), port => Port, - allowed_tools => Tools, allowed_ets_tables => Tables, + allowed_tools => Tools, + allowed_ets_tables => case Tables of <<"*">> -> all; _ -> Tables end, limits => Internal}, case validate_config(Config) of ok -> {ok, Config}; @@ -460,7 +484,7 @@ from_json_map(_) -> %% Optional fixed token (development convenience, erlang.mcp.authToken): when %% absent the session gets a fresh random token. 16..128 URL-safe characters. validate_token(T) when is_binary(T), byte_size(T) >= 16, byte_size(T) =< 128 -> - case re:run(T, "^[A-Za-z0-9._~-]+$", [{capture, none}]) of + case re:run(T, "^[A-Za-z0-9._~-]+\\z", [{capture, none}]) of match -> ok; nomatch -> {error, "erlang.mcp.authToken may only contain letters, digits and ._~-"} end; @@ -477,6 +501,7 @@ token_of(_) -> ok. cli() -> Reply = try + catch io:setopts(standard_io, [{encoding, unicode}]), case io:get_line("") of Line when is_list(Line), length(Line) < 8192 -> {ok, Input, _} = vscode_jsone_decode:decode(unicode:characters_to_binary(Line)), diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index 0738277f..0f751885 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -27,6 +27,8 @@ -define(MAX_SCANNED, 200000). -define(MAX_TOP, 50). -define(MAX_ETS_SCAN, 50000). +-define(MAX_MAILBOX_COPY, 20000). +-define(MAX_SAMPLE, 20). %%------------------------------------------------------------------------------ %% Entry point @@ -68,6 +70,9 @@ run(<<"supervision_tree">>, Args, Ctx) -> graph_tool(<<"supervision_tree">>, bui run(<<"registered_processes">>, Args, Ctx) -> graph_tool(<<"registered_processes">>, builder(<<"registered_processes">>), Args, Ctx); run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); run(<<"ets_tables">>, Args, Ctx) -> graph_tool(<<"ets_tables">>, builder(<<"ets_tables">>), Args, Ctx); +run(<<"process_state">>, Args, Ctx) -> process_state(Args, Ctx); +run(<<"mailbox_sample">>, Args, Ctx) -> mailbox_sample(Args, Ctx); +run(<<"ets_sample">>, Args, Ctx) -> ets_sample(Args, Ctx); run(<<"top_ports">>, Args, Ctx) -> graph_tool(<<"top_ports">>, builder(<<"top_ports">>), Args, Ctx); run(<<"ets_summary">>, Args, Ctx) -> graph_tool(<<"ets_summary">>, builder(<<"ets_summary">>), Args, Ctx); run(<<"changes_since">>, Args, Ctx) -> graph_tool(<<"changes_since">>, builder(<<"changes_since">>), Args, Ctx); @@ -198,11 +203,13 @@ node_text(true) -> %% Safe debugger metadata only: mode, connection state, interpreted modules and %% breakpoint locations (module + line). Source and options are not returned. -debug_session(#{enc := Enc} = _Ctx) -> +debug_session(#{enc := Enc} = Ctx) -> Max = maps:get(max_items, Enc), - {Interpreted, IUnavailable} = debug_call(fun() -> int:interpreted() end, []), - {Breaks, BUnavailable} = debug_call(fun() -> int:all_breaks() end, []), - {Snapshot, SUnavailable} = debug_call(fun() -> int:snapshot() end, []), + %% three debugger calls share the request budget instead of 1.5 s each + Budget = max(100, min(1500, remaining(Ctx) div 3)), + {Interpreted, IUnavailable} = debug_call(fun() -> int:interpreted() end, [], Budget), + {Breaks, BUnavailable} = debug_call(fun() -> int:all_breaks() end, [], Budget), + {Snapshot, SUnavailable} = debug_call(fun() -> int:snapshot() end, [], Budget), Paused = paused_list(Snapshot), Mods = [atom_to_binary(M, utf8) || M <- lists:sublist(lists:sort(Interpreted), Max), is_atom(M)], Bps = [#{<<"module">> => atom_to_binary(M, utf8), <<"line">> => L} @@ -230,8 +237,8 @@ debug_session(#{enc := Enc} = _Ctx) -> <<"truncated">> => length(Interpreted) > Max orelse length(Breaks) > Max orelse length(Paused) > Max}}. -debug_call(Fun, Default) -> - case bounded(Fun, 1500) of +debug_call(Fun, Default, Budget) -> + case bounded(Fun, Budget) of {ok, R} when is_list(R) -> {R, false}; _ -> {Default, true} end. @@ -311,7 +318,7 @@ finish_graph(Tool, Args, Scope, B, Started, Ctx) -> complete => Oms =:= [], truncated => Truncated, omissions => Oms}, Items = builder_items(B), Hash = args_hash(maps:remove(<<"cursor">>, Args)), - {CollId, Meta1, Stored} = mcp_store:put_collection(Tool, Hash, Meta, Items, #{args => Args}), + {CollId, Meta1, Stored} = mcp_store:put_collection(Tool, Hash, Meta, Items, #{args => Args, detail => maps:get(detail, Ctx, summary)}), render_page(Tool, Hash, CollId, Meta1, Stored, 0, Ctx). builder_items(B) -> @@ -490,6 +497,28 @@ bin(B) when is_binary(B) -> B. inspector_name(Name) -> lists:member(Name, [mcp_holder, mcp_sup, mcp_server, mcp_store]). +-define(INSPECTOR_MODULES, [mcp_runtime, mcp_server, mcp_store, mcp_sup, mcp_audit, mcp_holder, + mcp_encoder, mcp_policy, mcp_tools]). + +%% A process of the inspector itself (registered name, or running one of its modules: +%% workers and connections are anonymous funs of those modules). +is_inspector_pid(Pid) -> + case erlang:process_info(Pid, [registered_name, initial_call, current_function]) of + undefined -> false; + Info -> inspector_info(Info) + end. + +inspector_info(Info) -> + Named = case lists:keyfind(registered_name, 1, Info) of + {registered_name, N} when is_atom(N) -> inspector_name(N); + _ -> false + end, + Named orelse inspector_module(proplists:get_value(initial_call, Info)) + orelse inspector_module(proplists:get_value(current_function, Info)). + +inspector_module({M, _, _}) -> lists:member(M, ?INSPECTOR_MODULES); +inspector_module(_) -> false. + %% Safe check that a pid is a supervisor before any supervisor API is called on %% it (calling which_children on a plain gen_server would crash it). is_supervisor(Pid) -> @@ -963,7 +992,7 @@ registered_processes_build(Args, #{enc := Enc, config := Config} = Ctx) -> Shown = lists:sublist(Sorted, Cap), Masters = masters(Ctx), {Confirmed, B0} = case Selected of - {app, App} -> tree_pids(App, Ctx); + {app, App} -> cached_tree_pids(App, Ctx); all -> {#{}, new_b()} end, {B1, Unknown} = lists:foldl( @@ -1003,6 +1032,9 @@ masters(Ctx) -> _ -> #{} end. +cached_tree_pids(_App, #{tree_pids := Known}) -> {Known, new_b()}; +cached_tree_pids(App, Ctx) -> tree_pids(App, Ctx). + %% pids found in an application's supervision tree (confirmed membership) tree_pids(App, #{config := Config} = Ctx) -> case root_info(App, Ctx) of @@ -1164,7 +1196,7 @@ resolve_target(#{<<"pid">> := Text}) -> resolve_process(Text). %% Registered name (existing atoms only) or local pid text. Never creates atoms. resolve_process(<<"<", _/binary>> = Text) -> - case re:run(Text, "^<0\\.[0-9]{1,10}\\.[0-9]{1,10}>$", [{capture, none}]) of + case re:run(Text, "^<0\\.[0-9]{1,10}\\.[0-9]{1,10}>\\z", [{capture, none}]) of match -> try list_to_pid(binary_to_list(Text)) of Pid when node(Pid) =:= node() -> {ok, Pid} @@ -1194,16 +1226,21 @@ topology_overview_build(Args, #{config := Config} = Ctx) -> {ok, App} -> AppId = app_id(App), Allowed = fun(Tool) -> mcp_policy:tool_allowed(Tool, Config) end, - Part = fun(Tool, Build, PArgs) -> + Part = fun(Tool, Build, PArgs, C) -> case Allowed(Tool) of - true -> Build(PArgs, Ctx); + true -> Build(PArgs, C); false -> denied end end, - Tree = Part(<<"supervision_tree">>, fun supervision_tree_build/2, #{<<"id">> => AppId}), - Over = Part(<<"application_overview">>, fun application_overview_build/2, #{<<"application">> => AppId}), - Reg = Part(<<"registered_processes">>, fun registered_processes_build/2, #{<<"application">> => AppId}), - Ets = Part(<<"ets_tables">>, fun ets_tables_build/2, #{}), + Tree = Part(<<"supervision_tree">>, fun supervision_tree_build/2, #{<<"id">> => AppId}, Ctx), + %% the registered-process part reuses the pids of the tree just walked + RegCtx = case Tree of + {ok, _, TB} -> Ctx#{tree_pids => maps:map(fun(_, _) -> App end, maps:get(pids, TB))}; + _ -> Ctx + end, + Over = Part(<<"application_overview">>, fun application_overview_build/2, #{<<"application">> => AppId}, Ctx), + Reg = Part(<<"registered_processes">>, fun registered_processes_build/2, #{<<"application">> => AppId}, RegCtx), + Ets = Part(<<"ets_tables">>, fun ets_tables_build/2, #{}, Ctx), Parts = [{<<"supervision_tree">>, Tree}, {<<"application_overview">>, Over}, {<<"registered_processes">>, Reg}, {<<"ets_tables">>, Ets}], case [E || {_, {error, _, _} = E} <- Parts] of @@ -1306,13 +1343,14 @@ changes_since_build(Args, #{config := Config} = Ctx) -> {error, <<"baseline_expired">>, <<"the baseline collection is unknown or no longer retained (collection_ttl_ms, max_collections); " "call the tool again to get a new baseline">>}; - {ok, #{tool := OldTool, ctx := #{args := OldArgs}, meta := OldMeta, items := OldItems}} -> + {ok, #{tool := OldTool, ctx := #{args := OldArgs} = OldCtx, meta := OldMeta, items := OldItems}} -> case lists:member(OldTool, ?DIFFABLE) andalso mcp_policy:tool_allowed(OldTool, Config) of false -> {error, <<"invalid_baseline">>, <<"this collection cannot be used as a baseline">>}; true -> %% a fresh observation that is compared, not retained: only the diff takes a slot - case (builder(OldTool))(OldArgs, Ctx) of + %% observe again exactly as the baseline was observed (same detail) + case (builder(OldTool))(OldArgs, Ctx#{detail => maps:get(detail, OldCtx, summary)}) of {ok, _Scope, NB} -> Oms = lists:reverse(maps:get(oms, NB)), NewMeta = #{complete => Oms =:= [], omissions => Oms}, @@ -1392,6 +1430,152 @@ change_key(#{<<"kind">> := Kind, <<"name">> := N}) when is_binary(N) -> {Kind, n change_key(#{<<"kind">> := Kind, <<"childId">> := C}) when is_binary(C) -> {Kind, child, C}; change_key(_) -> undefined. +%%------------------------------------------------------------------------------ +%% Developer tier (never enabled by default; the project names them in allowed_tools): +%% bounded samples of application data. Values go through mcp_encoder: depth/item/size +%% limits, values of secret-looking keys replaced, credentials in URLs scrubbed. +%%------------------------------------------------------------------------------ + +%% A live, local, non-inspector process addressed by id, name or pid. +dev_target(Args) -> + case resolve_target(Args) of + {error, _, _} = E -> E; + {ok, Pid} -> + case is_process_alive(Pid) andalso not is_inspector_pid(Pid) of + true -> {ok, Pid}; + false -> {error, <<"not_found">>, <<"the process does not exist (anymore)">>} + end + end. + +dev_header(Pid) -> + #{<<"schemaVersion">> => ?SV, + <<"sessionId">> => mcp_store:session_id(), + <<"observedAt">> => now_iso(), + <<"process">> => pid_id(Pid), + <<"pid">> => mcp_encoder:pid_text(Pid), + <<"name">> => case erlang:process_info(Pid, registered_name) of + {registered_name, N} -> bin(N); + _ -> null + end}. + +%% sys:get_state/2 of a gen_server, gen_statem or gen_event (a system message is only sent to a +%% process whose callback module declares one of those behaviours; never a supervisor). +process_state(Args, #{enc := Enc} = Ctx) -> + case dev_target(Args) of + {error, _, _} = E -> E; + {ok, Pid} -> + case otp_behaviour(Pid) of + undefined -> + {error, <<"not_an_otp_process">>, + <<"the state is only read from gen_server, gen_statem and gen_event processes">>}; + {supervisor, _} -> + %% the state of a supervisor holds the child specifications, start arguments included + {error, <<"use_supervision_tree">>, + <<"the state of a supervisor contains child start arguments; use supervision_tree">>}; + {Behaviour, Callback} -> + T = max(100, min(2000, remaining(Ctx))), + case bounded(fun() -> sys:get_state(Pid, T) end, T + 200) of + {ok, State} -> + {ok, (dev_header(Pid))#{<<"behaviour">> => bin(Behaviour), + <<"callbackModule">> => bin(Callback), + <<"state">> => mcp_encoder:term(State, Enc), + <<"redaction">> => <<"best effort: values of keys named like a secret and credentials in URLs are replaced; a record without keys cannot be redacted">>}}; + timeout -> + {error, <<"timeout">>, + case paused_at(Pid) of + {ok, {M, L}} -> <<"the process is stopped at a breakpoint (", (bin(M))/binary, ":", + (integer_to_binary(L))/binary, ")">>; + _ -> <<"the process did not answer in time">> + end}; + {error, _} -> + {error, <<"unavailable">>, <<"the process exited or does not answer system messages">>} + end + end + end. + +%% {Behaviour, CallbackModule} | undefined +otp_behaviour(Pid) -> + case catch proc_lib:initial_call(Pid) of + {supervisor, Mod, _} when is_atom(Mod) -> {supervisor, Mod}; + {Mod, _, _} when is_atom(Mod) -> + case behaviours(Mod) of + L when is_list(L) -> + case [B || B <- [gen_server, gen_statem, gen_event, gen_fsm, supervisor], + lists:member(atom_to_binary(B, utf8), L)] of + [B | _] -> {B, Mod}; + [] -> undefined + end; + _ -> undefined + end; + _ -> undefined + end. + +%% The oldest messages of a mailbox (default 5, max 20), bounded and encoded. +mailbox_sample(Args, #{enc := Enc} = Ctx) -> + case dev_target(Args) of + {error, _, _} = E -> E; + {ok, Pid} -> + Limit = min(maps:get(<<"limit">>, Args, 5), ?MAX_SAMPLE), + Len = case erlang:process_info(Pid, message_queue_len) of + {message_queue_len, N} -> N; + _ -> 0 + end, + Head = (dev_header(Pid))#{<<"queueLength">> => Len, <<"order">> => <<"oldest first">>}, + case Len > ?MAX_MAILBOX_COPY of + true -> + {ok, Head#{<<"sample">> => [], <<"truncated">> => true, + <<"note">> => <<"the mailbox is too large to copy safely; only its length is reported">>}}; + false -> + T = max(100, min(2000, remaining(Ctx))), + case bounded(fun() -> erlang:process_info(Pid, messages) end, T) of + {ok, {messages, Msgs}} -> + {ok, Head#{<<"sample">> => [mcp_encoder:term(M, Enc) || M <- lists:sublist(Msgs, Limit)], + <<"truncated">> => length(Msgs) > Limit, + <<"redaction">> => <<"best effort: values of keys named like a secret and credentials in URLs are replaced">>}}; + timeout -> {error, <<"timeout">>, <<"the process did not answer in time">>}; + _ -> {error, <<"not_found">>, <<"the process exited during the observation">>} + end + end + end. + +%% A few objects of an approved named table (allowed_ets_tables), never of a private one. +ets_sample(Args, #{enc := Enc, config := Config} = Ctx) -> + Name = maps:get(<<"table">>, Args), + Limit = min(maps:get(<<"limit">>, Args, 5), ?MAX_SAMPLE), + case mcp_policy:ets_table_allowed(Name, Config) of + false -> {error, <<"policy_denied">>, <<"the table is not approved by allowed_ets_tables">>}; + true -> + Atom = try binary_to_existing_atom(Name, utf8) catch _:_ -> undefined end, + Prot = case Atom of undefined -> undefined; _ -> catch ets:info(Atom, protection) end, + case Prot of + P when P =:= public; P =:= protected -> + T = max(100, min(2000, remaining(Ctx))), + case bounded(fun() -> + Objs = case ets:match_object(Atom, '_', Limit) of + {O, _Cont} -> O; + '$end_of_table' -> [] + end, + {Objs, ets:info(Atom, size), ets:info(Atom, type)} + end, T) of + {ok, {Objects, Size, Type}} -> + {ok, #{<<"schemaVersion">> => ?SV, + <<"sessionId">> => mcp_store:session_id(), + <<"observedAt">> => now_iso(), + <<"table">> => Name, + <<"protection">> => bin(P), + <<"type">> => bin(Type), + <<"size">> => Size, + <<"sample">> => [mcp_encoder:term(O, Enc) || O <- Objects], + <<"truncated">> => Size > length(Objects), + <<"redaction">> => <<"best effort: values of keys named like a secret and credentials in URLs are replaced">>}}; + timeout -> {error, <<"timeout">>, <<"the table did not answer in time">>}; + _ -> {error, <<"not_found">>, <<"the table does not exist (anymore)">>} + end; + private -> {error, <<"policy_denied">>, <<"private tables are never read">>}; + _ -> {error, <<"not_found">>, <<"no table with this name exists">>} + end + end. + %%------------------------------------------------------------------------------ %% top_processes (ranking by queue length / reductions / memory) %%------------------------------------------------------------------------------ @@ -1469,13 +1653,9 @@ top_entry(Pid, SortBy, Rank, Masters, B) -> case erlang:process_info(Pid, Items) of undefined -> gone; Info -> - case lists:keyfind(registered_name, 1, Info) of - {registered_name, N} when is_atom(N) -> - case inspector_name(N) of - true -> skip; - false -> top_entity(Pid, Info, SortBy, Rank, Masters, B) - end; - _ -> top_entity(Pid, Info, SortBy, Rank, Masters, B) + case inspector_info(Info) of + true -> skip; + false -> top_entity(Pid, Info, SortBy, Rank, Masters, B) end end. @@ -1520,6 +1700,7 @@ top_ports_build(Args, Ctx) -> fun({_, Port}, {Acc, G, Rank}) -> case port_entry(Port, SortBy, Rank, Acc) of gone -> {Acc, G + 1, Rank}; + skip -> {Acc, G, Rank}; {ok, Acc1} -> {Acc1, G, Rank + 1} end end, {new_b(), 0, 1}, Ranked), @@ -1531,7 +1712,7 @@ top_ports_build(Args, Ctx) -> <<"limitations">> => [<<"A point-in-time scan, not an atomic snapshot: ports open and close while it runs.">>, <<"Only the driver name, byte counters, queue size and the owner process are reported; addresses, command lines, paths and data are never read.">>, - <<"driver is for ports that are not a plain driver name (for example a spawned program).">>, + <<"driver is for any port that is not a well-known VM driver (for example a spawned program: its name is its command line).">>, <<"owns_port links a port to its connected process; it is not evidence of traffic.">>]}, {ok, Scope, B2}. @@ -1556,8 +1737,16 @@ port_entry(Port, SortBy, Rank, B) -> <<"output">> => proplists:get_value(output, Info, 0), <<"queueSize">> => proplists:get_value(queue_size, Info, 0), <<"bytesUnit">> => <<"bytes">>}, - B1 = add_entity(B, PId, Fields), case proplists:get_value(connected, Info) of + Owner0 when is_pid(Owner0) -> InspectorOwned = is_inspector_pid(Owner0); + _ -> InspectorOwned = false + end, + B1 = case InspectorOwned of + true -> B; + false -> add_entity(B, PId, Fields) + end, + case proplists:get_value(connected, Info) of + _ when InspectorOwned -> skip; Owner when is_pid(Owner), node(Owner) =:= node() -> {OwnerId, B2} = process_entity(Owner, #{<<"role">> => <<"unknown">>}, B1), {ok, add_rel(B2, <<"owns_port">>, OwnerId, PId, <<"port_info connected">>, <<"confirmed">>)}; @@ -1565,12 +1754,15 @@ port_entry(Port, SortBy, Rank, B) -> end end. +%% Only well-known VM drivers are named: the "name" of a port opened with spawn/2 is its +%% command line, which can be any text (and look like a driver name). driver_name(Name) when is_list(Name) -> - case re:run(Name, "^[a-z][a-z0-9_]{0,31}$", [{capture, none}]) of - match -> list_to_binary(Name); - nomatch -> <<"">> + case lists:member(Name, ["tcp_inet", "udp_inet", "sctp_inet", "efile", "tty_sl", "forker", "fd", + "ram_file_drv", "zlib_drv", "inet_gethost", "spawn", "ssl_tls"]) of + true -> list_to_binary(Name); + false -> <<"">> end; -driver_name(_) -> <<"">>. +driver_name(_) -> <<"">>. %% omissions shared by the scanning tools scan_omissions(B, Gone, Unscanned, Cut, What) -> @@ -1595,7 +1787,7 @@ scan_omissions(B, Gone, Unscanned, Cut, What) -> %% ets_summary: ETS memory per owner process, without any table name, key or value %%------------------------------------------------------------------------------ -ets_summary_build(Args, Ctx) -> +ets_summary_build(Args, #{config := Config} = Ctx) -> Limit = min(maps:get(<<"limit">>, Args, 10), ?MAX_TOP), All = ets:all(), Total = length(All), @@ -1603,7 +1795,7 @@ ets_summary_build(Args, Ctx) -> true -> {lists:sublist(All, ?MAX_ETS_SCAN), Total - ?MAX_ETS_SCAN}; false -> {All, 0} end, - {Owners, Unscanned} = ets_owners(Scan, Ctx, 0, #{}), + {Owners, Unscanned, Excluded} = ets_owners(Scan, Config, Ctx, 0, #{}, 0), WordSize = erlang:system_info(wordsize), TotalWords = lists:sum([W || {_, W} <- maps:values(Owners)]), Ranked = lists:sublist(lists:reverse(lists:sort([{W, C, O} || {O, {C, W}} <- maps:to_list(Owners)])), Limit), @@ -1621,32 +1813,60 @@ ets_summary_build(Args, Ctx) -> {_, Acc1} = process_entity(Owner, Fields, Acc), {Acc1, Rank + 1} end, {new_b(), 1}, Ranked), - B2 = scan_omissions(B1, 0, Unscanned, Cut, <<"tables">>), + B2a = scan_omissions(B1, 0, Unscanned, Cut, <<"tables">>), + B2 = case Excluded of + 0 -> B2a; + _ -> add_omission(B2a, <<"policy_denied">>, Excluded, undefined, + <<"private tables and tables not approved by allowed_ets_tables are not counted">>, false) + end, Scope = #{<<"tool">> => <<"ets_summary">>, <<"limit">> => Limit, - <<"tableCount">> => Total, + <<"tableCount">> => Total - Excluded, <<"ownerCount">> => maps:size(Owners), <<"totalMemory">> => #{<<"words">> => TotalWords, <<"bytes">> => TotalWords * WordSize}, <<"limitations">> => [<<"Per-owner aggregates only: no table name, key, object or value is read or returned, and no table inventory is listed.">>, - <<"Private tables are counted in the aggregates (their owner and size, nothing else).">>, + <<"Only tables approved by allowed_ets_tables (exact names, or all) are counted; private tables never are.">>, <<"A point-in-time scan, not an atomic snapshot; tables are created and deleted while it runs.">>, <<"For the metadata of a specific table use ets_tables (approved names only).">>]}, {ok, Scope, B2}. -%% -> {#{OwnerPid => {Tables, Words}}, NotScanned} -ets_owners([], _Ctx, _N, Acc) -> {Acc, 0}; -ets_owners([Tab | T] = Tabs, Ctx, N, Acc) -> +%% -> {#{OwnerPid => {Tables, Words}}, NotScanned, NotCounted} +ets_owners([], _Config, _Ctx, _N, Acc, Ex) -> {Acc, 0, Ex}; +ets_owners([Tab | T] = Tabs, Config, Ctx, N, Acc, Ex) -> case N rem 500 =:= 0 andalso N > 0 andalso remaining(Ctx) =:= 0 of - true -> {Acc, length(Tabs)}; + true -> {Acc, length(Tabs), Ex}; false -> - Acc1 = try {ets:info(Tab, owner), ets:info(Tab, memory)} of - {Owner, Words} when is_pid(Owner), node(Owner) =:= node(), is_integer(Words) -> - maps:update_with(Owner, fun({C, W}) -> {C + 1, W + Words} end, {1, Words}, Acc); - _ -> Acc - catch _:_ -> Acc - end, - ets_owners(T, Ctx, N + 1, Acc1) + {Acc1, Ex1} = case ets_counted(Tab, Config) of + false -> {Acc, Ex + 1}; + true -> + try {ets:info(Tab, owner), ets:info(Tab, memory)} of + {Owner, Words} when is_pid(Owner), node(Owner) =:= node(), is_integer(Words) -> + case is_inspector_pid(Owner) of + true -> {Acc, Ex}; + false -> {maps:update_with(Owner, fun({C, W}) -> {C + 1, W + Words} end, + {1, Words}, Acc), Ex} + end; + _ -> {Acc, Ex} + catch _:_ -> {Acc, Ex} + end + end, + ets_owners(T, Config, Ctx, N + 1, Acc1, Ex1) + end. + +%% never a private table; approved by exact name, or every table when allowed_ets_tables is `all` +ets_counted(Tab, Config) -> + try ets:info(Tab, protection) of + private -> false; + undefined -> false; + _ -> + case maps:get(allowed_ets_tables, Config) of + all -> true; + Names -> + ets:info(Tab, named_table) =:= true andalso + lists:member(atom_to_binary(ets:info(Tab, name), utf8), Names) + end + catch _:_ -> false end. %%------------------------------------------------------------------------------ @@ -1654,7 +1874,11 @@ ets_owners([Tab | T] = Tabs, Ctx, N, Acc) -> %%------------------------------------------------------------------------------ ets_tables_build(_Args, #{config := Config}) -> - Names = maps:get(allowed_ets_tables, Config), + Names = case maps:get(allowed_ets_tables, Config) of + all -> [atom_to_binary(N, utf8) || T <- ets:all(), is_atom(T), ets:info(T, named_table) =:= true, + N <- [ets:info(T, name)], is_atom(N)]; + L -> L + end, {B, Denied, Gone} = lists:foldl( fun(Name, {Acc, D, G}) -> ets_entry(Name, Acc, D, G) end, {new_b(), 0, 0}, Names), diff --git a/apps/erlangbridge/src/mcp/mcp_server.erl b/apps/erlangbridge/src/mcp/mcp_server.erl index ef68d10d..501092bd 100644 --- a/apps/erlangbridge/src/mcp/mcp_server.erl +++ b/apps/erlangbridge/src/mcp/mcp_server.erl @@ -14,6 +14,7 @@ -behaviour(gen_server). -export([start_link/2, info/0, renew/0]). +-export([format_status/1, format_status/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). %% spawned processes -export([acceptor/3, conn_main/2]). @@ -100,6 +101,14 @@ handle_info({'EXIT', _Acceptor, Reason}, S) -> handle_info(_, S) -> {noreply, S}. +%% a crash report or sys:get_status must never print the bearer token +format_status(Status) when is_map(Status) -> + maps:map(fun(state, #state{} = S) -> S#state{token = <<"">>}; + (_, V) -> V + end, Status). +format_status(_Opt, [_PDict, #state{} = S]) -> S#state{token = <<"">>}; +format_status(_Opt, [_PDict, S]) -> S. + terminate(_, #state{listen = L, conns = Conns}) -> catch gen_tcp:close(L), [exit(Pid, kill) || Pid <- maps:keys(Conns)], diff --git a/apps/erlangbridge/src/mcp/mcp_store.erl b/apps/erlangbridge/src/mcp/mcp_store.erl index f40192ed..fbbbd8e3 100644 --- a/apps/erlangbridge/src/mcp/mcp_store.erl +++ b/apps/erlangbridge/src/mcp/mcp_store.erl @@ -14,6 +14,7 @@ -export([note_member/3, member/1]). -export([stats/0, notify/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +-export([format_status/1, format_status/2]). -define(SERVER, ?MODULE). -define(SWEEP_MS, 1000). @@ -195,6 +196,14 @@ handle_info(sweep, S) -> {noreply, sweep_collections(S)}; handle_info(_, S) -> {noreply, S}. +%% a crash report or sys:get_status must never print the cursor-signing secret +format_status(Status) when is_map(Status) -> + maps:map(fun(state, #state{} = S) -> S#state{secret = <<"">>}; + (_, V) -> V + end, Status). +format_status(_Opt, [_PDict, #state{} = S]) -> S#state{secret = <<"">>}; +format_status(_Opt, [_PDict, S]) -> S. + terminate(_, _) -> ok. %%------------------------------------------------------------------------------ diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 6c8278b6..2a1d7888 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -45,7 +45,7 @@ prompts() -> prompt(<<"map_application">>, Args, Config) when is_map(Args) -> case maps:get(<<"application">>, Args, undefined) of App when is_binary(App), byte_size(App) > 0, byte_size(App) =< 255 -> - case re:run(App, "^[A-Za-z0-9_@.-]+$", [{capture, none}]) of + case re:run(App, "^[A-Za-z0-9_@.-]+\\z", [{capture, none}]) of match -> {ok, #{<<"description">> => <<"Map the OTP topology of ", App/binary>>, <<"messages">> => [#{<<"role">> => <<"user">>, @@ -203,6 +203,49 @@ def(<<"changes_since">>) -> <<"format">> => format_prop()}, [<<"collectionId">>]), graph_output()}; +def(<<"process_state">>) -> + {<<"Process state (developer tier)">>, + <<"The state of one gen_server, gen_statem or gen_event process (sys:get_state with a short timeout), " + "by entity id, registered name or pid. Bounded (depth, items, binary size); values of keys named like a secret " + "(password, token, ...) and credentials in URLs are replaced, but a record without keys cannot be redacted: " + "the result can contain application data. Only enabled when the project lists it in allowed_tools. " + "Fails for a process stopped at a breakpoint (reported as such).">>, + obj(#{<<"id">> => id_prop(<<"Process entity id.">>), + <<"name">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 255, + <<"description">> => <<"Registered name (must be an existing atom).">>}, + <<"pid">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 64, + <<"description">> => <<"Local pid text such as <0.123.0>.">>}}, []), + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), + <<"process">> => str(), <<"state">> => #{}}, [<<"schemaVersion">>, <<"sessionId">>])}; + +def(<<"mailbox_sample">>) -> + {<<"Mailbox sample (developer tier)">>, + <<"The queue length and the oldest few messages (default 5, max 20) of one local process, by entity id, registered " + "name or pid. Messages are bounded and encoded like process_state (best-effort redaction); a mailbox of more than " + "20000 messages is not copied, only its length is reported. Only enabled when the project lists it in " + "allowed_tools.">>, + obj(#{<<"id">> => id_prop(<<"Process entity id.">>), + <<"name">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 255, + <<"description">> => <<"Registered name (must be an existing atom).">>}, + <<"pid">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 64, + <<"description">> => <<"Local pid text such as <0.123.0>.">>}, + <<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 20}}, []), + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), + <<"process">> => str(), <<"queueLength">> => int(), <<"sample">> => #{<<"type">> => <<"array">>}}, + [<<"schemaVersion">>, <<"sessionId">>])}; + +def(<<"ets_sample">>) -> + {<<"ETS sample (developer tier)">>, + <<"A few objects (default 5, max 20) of one named ETS table approved by the project (allowed_ets_tables: exact " + "names, or all). Never a private table. Objects are bounded and encoded like process_state (best-effort " + "redaction). Only enabled when the project lists it in allowed_tools.">>, + obj(#{<<"table">> => #{<<"type">> => <<"string">>, <<"maxLength">> => 255, + <<"description">> => <<"Name of the table (must be an existing atom).">>}, + <<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 20}}, [<<"table">>]), + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), + <<"table">> => str(), <<"size">> => int(), <<"sample">> => #{<<"type">> => <<"array">>}}, + [<<"schemaVersion">>, <<"sessionId">>])}; + def(<<"top_ports">>) -> {<<"Top ports">>, <<"Rank the local ports of the node (sockets, files, spawned programs) by queue size, bytes in or bytes out and " @@ -327,7 +370,7 @@ validate_args(Tool, Args) -> end end. -exclusive(<<"process_info">>, Args) -> +exclusive(Tool, Args) when Tool =:= <<"process_info">>; Tool =:= <<"process_state">>; Tool =:= <<"mailbox_sample">> -> case length([K || K <- [<<"id">>, <<"name">>, <<"pid">>], maps:is_key(K, Args)]) of 1 -> ok; _ -> {error, <<"exactly one of id, name or pid is required">>} diff --git a/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl index 1d042f34..41621bde 100644 --- a/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_encoder_SUITE.erl @@ -15,8 +15,25 @@ all() -> improper_lists_and_unicode, never_creates_atoms_and_always_json_encodable, text_is_bounded, + credentials_are_redacted_in_terms_text_and_urls, timestamps_and_hex]. +credentials_are_redacted_in_terms_text_and_urls(_) -> + L = #{max_depth => 8, max_items => 20, max_binary_bytes => 256}, + %% key/value pairs (proplists, options) and map entries + ?assertEqual(#{<<"tuple">> => [<<"password">>, <<"">>]}, mcp_encoder:term({password, "hunter2"}, L)), + ?assertEqual(#{<<"tuple">> => [<<"db">>, <<"ok">>]}, mcp_encoder:term({db, ok}, L)), + %% printed form (child ids): same rule, nested, and URLs with credentials + T = mcp_encoder:text({conn, [{host, "h"}, {api_key, "SENTINEL_KEY"}, {opts, #{secret => "SENTINEL_S"}}]}, L), + ?assertEqual(nomatch, binary:match(T, <<"SENTINEL">>)), + ?assertNotEqual(nomatch, binary:match(T, <<"redacted">>)), + ?assertNotEqual(nomatch, binary:match(T, <<"host">>)), + ?assertEqual(<<"postgres://@db/app">>, mcp_encoder:scrub(<<"postgres://user:pw@db/app">>)), + ?assertEqual(<<"http://example.org/a:b">>, mcp_encoder:scrub(<<"http://example.org/a:b">>)), + ?assertEqual(<<"see now">>, + binary:replace(mcp_encoder:term(<<"see https://u:p@h now">>, L), <<"https://@h">>, <<"">>)), + ?assertEqual(<<"db: ">>, binary:replace(mcp_encoder:term("db: ftp://a:b@c", L), <<"ftp://@c">>, <<"">>)). + limits() -> #{max_depth => 4, max_items => 5, max_binary_bytes => 16}. scalars_and_special_terms(_) -> diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index 27e5dcac..c0aa4c25 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -19,6 +19,9 @@ all() -> explicit_empty_allowlist_enables_no_tool, project_cannot_set_activation_or_binding, project_limits_cannot_exceed_ceilings, + developer_tier_is_opt_in_and_ets_all_is_accepted, + non_ascii_workspace_path_still_finds_the_project_policy, + token_with_trailing_newline_is_rejected, invalid_and_duplicate_project_terms, profile_only_policy_reported, malformed_project_file_disables, @@ -61,9 +64,10 @@ defaults_without_project_file(Config) -> {ok, C} = mcp_policy:resolve(input(Config, #{})), ?assertEqual("127.0.0.1", maps:get(host, C)), ?assertEqual(0, maps:get(port, C)), - ?assertEqual(mcp_policy:all_tools(), maps:get(allowed_tools, C)), + ?assertEqual(mcp_policy:default_tools(), maps:get(allowed_tools, C)), + ?assertEqual([], [T || T <- [<<"process_state">>, <<"mailbox_sample">>, <<"ets_sample">>], lists:member(T, maps:get(allowed_tools, C))]), ?assertEqual([], maps:get(allowed_ets_tables, C)), - ?assertEqual(mcp_policy:ceilings(), maps:get(limits, C)), + ?assertEqual(mcp_policy:default_limits(), maps:get(limits, C)), ?assertEqual(12, length(maps:get(allowed_tools, C))), %% a project without an mcp term uses the same built-in policy write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), @@ -124,14 +128,51 @@ project_limits_cannot_exceed_ceilings(Config) -> [begin write_rebar(Config, lists:flatten(io_lib:format("{mcp, [{limits, [~s]}]}.\n", [Term]))), ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Term) - end || Term <- ["{max_items, 501}", "{max_items, 0}", "{max_items, -1}", "{max_items, 1.5}", - "{max_result_bytes, 262145}", "{request_timeout_ms, 3001}", "{max_concurrency, 3}", + end || Term <- ["{max_items, 5001}", "{max_items, 0}", "{max_items, -1}", "{max_items, 1.5}", + "{max_result_bytes, 2097153}", "{request_timeout_ms, 15001}", "{max_concurrency, 9}", "{unknown_limit, 1}", "{max_items, <<\"5\">>}", %% unusable combinations: error envelopes must fit "{max_result_bytes, 100}", "{max_request_bytes, 100}", "{max_result_bytes, 8192}, {max_collection_bytes, 4096}"]], write_rebar(Config, "{mcp, [{limits, [{max_items, 1}, {max_result_bytes, 4096}, {max_collection_bytes, 4096}, {max_binary_bytes, 1024}]}]}.\n"), - ?assertMatch({ok, _}, mcp_policy:resolve(input(Config, #{}))). + ?assertMatch({ok, _}, mcp_policy:resolve(input(Config, #{}))), + %% raising a limit above its default is allowed up to the ceiling + write_rebar(Config, "{mcp, [{limits, [{max_items, 5000}, {max_result_bytes, 2097152}, {max_collection_bytes, 8388608}," + " {request_timeout_ms, 15000}, {collection_ttl_ms, 600000}, {max_collections, 16}]}]}.\n"), + {ok, Raised} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual(5000, mcp_policy:limit(max_items, Raised)), + ?assertEqual(600000, mcp_policy:limit(collection_ttl_ms, Raised)). + +developer_tier_is_opt_in_and_ets_all_is_accepted(Config) -> + Dev = [<<"process_state">>, <<"mailbox_sample">>, <<"ets_sample">>], + {ok, Default} = mcp_policy:resolve(input(Config, #{})), + ?assertEqual([], [T || T <- Dev, mcp_policy:tool_allowed(T, Default)]), + ?assert(lists:all(fun(T) -> lists:member(T, mcp_policy:all_tools()) end, Dev)), + write_rebar(Config, "{mcp, [{allowed_tools, [<<\"process_state\">>, <<\"ets_sample\">>]}, {allowed_ets_tables, all}]}.\n"), + {ok, C} = mcp_policy:resolve(input(Config, #{})), + ?assert(mcp_policy:tool_allowed(<<"process_state">>, C)), + ?assertNot(mcp_policy:tool_allowed(<<"mailbox_sample">>, C)), + ?assertEqual(all, maps:get(allowed_ets_tables, C)), + ?assert(mcp_policy:ets_table_allowed(<<"anything">>, C)), + %% crosses to the target and back unchanged + ?assertEqual({ok, C}, mcp_policy:from_json_map(mcp_policy:to_json_map(C))), + ?assertNot(mcp_policy:ets_table_allowed(<<"x">>, Default)), + [begin + write_rebar(Config, Text), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Text) + end || Text <- ["{mcp, [{allowed_ets_tables, everything}]}.\n", "{mcp, [{allowed_tools, [<<\"process_dump\">>]}]}.\n"]]. + +non_ascii_workspace_path_still_finds_the_project_policy(Config) -> + Dir = filename:join(?config(priv_dir, Config), "José_проект"), + ok = filelib:ensure_dir(filename:join(Dir, "x")), + ok = file:write_file(filename:join(Dir, "rebar.config"), "{mcp, [{allowed_tools, [<<\"runtime_summary\">>]}]}.\n"), + Bin = unicode:characters_to_binary(Dir), + {ok, C} = mcp_policy:resolve(#{<<"enabled">> => true, <<"trusted">> => true, <<"cwd">> => Bin, <<"root">> => Bin}), + ?assertEqual([<<"runtime_summary">>], maps:get(allowed_tools, C)). + +token_with_trailing_newline_is_rejected(_Config) -> + ?assertEqual(ok, mcp_policy:validate_token(<<"abcdefghijklmnop">>)), + ?assertMatch({error, _}, mcp_policy:validate_token(<<"abcdefghijklmnop\n">>)). invalid_and_duplicate_project_terms(Config) -> [begin diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index cf5e87bc..11480397 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -36,6 +36,11 @@ all() -> changes_since_reports_replaced_and_removed_children, top_ports_reports_drivers_and_owners_only, ets_summary_aggregates_per_owner_without_names, + ets_summary_counts_only_approved_tables, + developer_tools_are_off_by_default, + process_state_is_bounded_redacted_and_never_for_supervisors, + mailbox_sample_returns_the_oldest_messages_redacted, + ets_sample_only_for_approved_non_private_tables, paused_process_is_reported_from_debugger_evidence, restart_changes_identity, pagination_cursors_and_retention, @@ -88,6 +93,12 @@ test_config(Case) -> case Case of allowlist_enforced -> Base1#{allowed_tools => [<<"runtime_summary">>, <<"ets_tables">>]}; + ets_summary_aggregates_per_owner_without_names -> + Base1#{allowed_ets_tables => all}; + Dev when Dev =:= process_state_is_bounded_redacted_and_never_for_supervisors; + Dev =:= mailbox_sample_returns_the_oldest_messages_redacted; + Dev =:= ets_sample_only_for_approved_non_private_tables -> + Base1#{allowed_tools => mcp_policy:all_tools()}; topology_overview_leaves_out_parts_the_policy_denies -> Base1#{allowed_tools => [<<"topology_overview">>, <<"supervision_tree">>, <<"application_overview">>]}; pagination_cursors_and_retention -> @@ -320,7 +331,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> ?assertMatch(#{<<"result">> := #{}}, rpc(Config, <<"ping">>, #{})), #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), - ?assertEqual(lists:sort(mcp_policy:all_tools()), Names), + ?assertEqual(lists:sort(mcp_policy:default_tools()), Names), ?assertEqual(12, length(Tools)), [begin ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, @@ -1163,7 +1174,7 @@ top_ports_reports_drivers_and_owners_only(Config) -> Drivers = [maps:get(<<"driver">>, E) || E <- Ports], ?assert(lists:member(<<"tcp_inet">>, Drivers)), %% a spawned program is never named: no command line or path in the result - ?assert(lists:member(<<"">>, Drivers)), + ?assert(lists:member(<<"">>, Drivers)), Dump = iolist_to_binary(io_lib:format("~p", [S])), [?assertEqual(nomatch, binary:match(Dump, X)) || X <- [list_to_binary(os:find_executable("cat")), <<"SENTINEL">>, <<"127.0.0.1">>]], @@ -1217,6 +1228,104 @@ ets_summary_aggregates_per_owner_without_names(Config) -> exit(Big, kill) end. +ets_summary_counts_only_approved_tables(Config) -> + Self = self(), + Big = spawn(fun() -> + ets:new(mcp_fx_hidden_big, [named_table, public]), + ets:insert(mcp_fx_hidden_big, {1, "SENTINEL_BIG_VALUE"}), + Self ! ready, + receive stop -> ok end + end), + receive ready -> ok after 5000 -> ct:fail(no_big_table) end, + try + S = ok_call(Config, <<"ets_summary">>, #{<<"limit">> => 50}), + Ents = maps:get(<<"entities">>, S), + %% only the owner of the approved table mcp_fx_orders is counted + ?assertEqual([<<"mcp_fx_worker_c">>], [maps:get(<<"name">>, E) || E <- Ents]), + ?assertMatch(#{<<"ets">> := #{<<"tables">> := 1}}, hd(Ents)), + ?assertNot(lists:any(fun(E) -> maps:get(<<"pid">>, E, undefined) =:= list_to_binary(pid_to_list(Big)) end, Ents)), + ?assert(lists:any(fun(#{<<"reason">> := R}) -> R =:= <<"policy_denied">> end, maps:get(<<"omissions">>, S))) + after + exit(Big, kill) + end. + +developer_tools_are_off_by_default(Config) -> + #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), + Names = [maps:get(<<"name">>, T) || T <- Tools], + [?assertEqual(false, lists:member(T, Names)) || T <- [<<"process_state">>, <<"mailbox_sample">>, <<"ets_sample">>]], + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"process_state">>, #{<<"name">> => <<"mcp_fx_worker_a">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"mailbox_sample">>, #{<<"name">> => <<"mcp_fx_mailbox">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"ets_sample">>, #{<<"table">> => <<"mcp_fx_orders">>})). + +process_state_is_bounded_redacted_and_never_for_supervisors(Config) -> + S = ok_call(Config, <<"process_state">>, #{<<"name">> => <<"mcp_fx_worker_a">>}), + ?assertMatch(#{<<"behaviour">> := <<"gen_server">>, <<"callbackModule">> := <<"mcp_fixture_worker">>, + <<"name">> := <<"mcp_fx_worker_a">>, <<"process">> := _, <<"state">> := _}, S), + %% the state is a map with a `secret` key: its value is replaced, the orphan pid stays visible + Dump = iolist_to_binary(io_lib:format("~p", [S])), + ?assertEqual(nomatch, binary:match(Dump, <<"SENTINEL">>)), + ?assertNotEqual(nomatch, binary:match(Dump, <<"">>)), + %% by id too; no dictionary, never a supervisor (its state holds child start arguments) + Id = maps:get(<<"process">>, S), + ?assertMatch(#{<<"state">> := _}, ok_call(Config, <<"process_state">>, #{<<"id">> => Id})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"use_supervision_tree">>}}}, + call(Config, <<"process_state">>, #{<<"name">> => <<"mcp_fx_sub_sup">>})), + %% a plain process is never sent a system message + Orphan = whereis(mcp_fx_orphan), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_an_otp_process">>}}}, + call(Config, <<"process_state">>, #{<<"name">> => <<"mcp_fx_orphan">>})), + ?assertEqual({messages, []}, erlang:process_info(Orphan, messages)), + %% inspector processes, exited processes, hostile names and strict arguments + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"process_state">>, #{<<"name">> => <<"mcp_server">>})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"process_state">>, #{<<"name">> => <<"no_such_name_zz">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"process_state">>, #{})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"process_state">>, #{<<"name">> => <<"a">>, <<"pid">> => <<"<0.1.0>">>})). + +mailbox_sample_returns_the_oldest_messages_redacted(Config) -> + Hot = spawn(fun() -> register(mcp_fx_mail, self()), receive never -> ok end end), + wait_until(fun() -> whereis(mcp_fx_mail) =:= Hot end), + Hot ! {password, "SENTINEL_PW"}, + Hot ! #{token => "SENTINEL_TOKEN", ok => 1}, + Hot ! {url, "postgres://user:SENTINEL_URLPW@db/app"}, + [Hot ! {n, I} || I <- lists:seq(1, 27)], + try + S = ok_call(Config, <<"mailbox_sample">>, #{<<"name">> => <<"mcp_fx_mail">>, <<"limit">> => 4}), + ?assertMatch(#{<<"queueLength">> := 30, <<"truncated">> := true, <<"order">> := <<"oldest first">>}, S), + ?assertEqual(4, length(maps:get(<<"sample">>, S))), + Dump = iolist_to_binary(io_lib:format("~p", [S])), + ?assertEqual(nomatch, binary:match(Dump, <<"SENTINEL">>)), + ?assertNotEqual(nomatch, binary:match(Dump, <<"">>)), + %% the default sample is 5, the maximum 20; the mailbox itself is unchanged + ?assertEqual(5, length(maps:get(<<"sample">>, ok_call(Config, <<"mailbox_sample">>, #{<<"name">> => <<"mcp_fx_mail">>})))), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"mailbox_sample">>, #{<<"name">> => <<"mcp_fx_mail">>, <<"limit">> => 21})), + ?assertEqual({message_queue_len, 30}, erlang:process_info(Hot, message_queue_len)), + %% a quiet mailbox of the fixture holds its message + M = ok_call(Config, <<"mailbox_sample">>, #{<<"name">> => <<"mcp_fx_mailbox">>}), + ?assertMatch(#{<<"queueLength">> := 1, <<"sample">> := [<<"SENTINEL_MAILBOX">>], <<"truncated">> := false}, M) + after + exit(Hot, kill) + end. + +ets_sample_only_for_approved_non_private_tables(Config) -> + S = ok_call(Config, <<"ets_sample">>, #{<<"table">> => <<"mcp_fx_orders">>}), + ?assertMatch(#{<<"table">> := <<"mcp_fx_orders">>, <<"protection">> := <<"public">>, <<"size">> := 1, + <<"truncated">> := false}, S), + ?assertEqual(1, length(maps:get(<<"sample">>, S))), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"policy_denied">>}}}, + call(Config, <<"ets_sample">>, #{<<"table">> => <<"mcp_fx_private">>})), + ?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"not_found">>}}}, + call(Config, <<"ets_sample">>, #{<<"table">> => <<"mcp_fx_missing_table_zzz">>})), + %% a table that is not approved is denied before any atom lookup: no atom is created + Atoms = erlang:system_info(atom_count), + [?assertMatch({tool_error, #{<<"error">> := #{<<"code">> := <<"policy_denied">>}}}, + call(Config, <<"ets_sample">>, #{<<"table">> => <<"hostile_table_", (integer_to_binary(I))/binary>>})) + || I <- lists:seq(1, 30)], + ?assertEqual(Atoms, erlang:system_info(atom_count)), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"ets_sample">>, #{})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"ets_sample">>, #{<<"table">> => <<"mcp_fx_orders">>, <<"limit">> => 0})). + prompt_guides_an_agent_and_checks_its_argument(Config) -> #{<<"result">> := #{<<"prompts">> := [P]}} = rpc(Config, <<"prompts/list">>, #{}), ?assertMatch(#{<<"name">> := <<"map_application">>, diff --git a/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl index f51a1e96..719a3fca 100644 --- a/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_store_SUITE.erl @@ -16,7 +16,7 @@ all() -> members_are_bounded]. init_per_testcase(Case, Config) -> - L = (mcp_policy:ceilings())#{max_items => 2, max_collections => 2, collection_ttl_ms => 400, + L = (mcp_policy:default_limits())#{max_items => 2, max_collections => 2, collection_ttl_ms => 400, max_result_bytes => 4096, max_collection_bytes => 4096, max_binary_bytes => 512, max_concurrency => 2}, Cfg = (mcp_policy:defaults())#{limits => L}, From 47bd5ed319f14ae8aaa15633f1bc3c9df56f3255 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 17:31:15 +0200 Subject: [PATCH 14/20] Rewrite the MCP security note around the developer's real threat model and document the developer tier Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 ++ HELP.MD | 43 ++++++++++++++++++++++++---- mcp-tasks.md | 4 ++- mcp_security.md | 75 ++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 101 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a3d048d..3fbc0347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - New tool `topology_overview`: the application, its supervision tree, registered processes and owned approved ETS tables of one application in a single call (each part still subject to the project policy) - New tool `changes_since`: compares a retained collection with a fresh observation and reports added, removed and replaced (restarted) entities; removals from partial collections are marked unconfirmed - New tools `top_ports` (local ports by queue size or traffic counters: driver name and owner only, never addresses or command lines) and `ets_summary` (ETS memory per owner process, no table names or contents); `runtime_summary` lists the connected nodes (host redacted by default); new edge type `owns_port` + - Developer tier (opt-in, named in the project's `allowed_tools`): `process_state` (bounded `sys:get_state` of a gen_server/gen_statem/gen_event, never a supervisor), `mailbox_sample` (oldest messages) and `ets_sample` (a few rows of an approved table), with best-effort redaction of secret-like keys and URL credentials; `allowed_ets_tables` accepts `all`; limits can be raised above their defaults up to higher ceilings (e.g. `collection_ttl_ms` up to 10 min) + - Hardening: the bearer token and cursor secret no longer appear in crash reports, non-ASCII workspace paths find the project policy, `ets_summary` only counts approved tables, `top_ports` only names well-known drivers, inspector processes are excluded from the rankings, child ids are redacted - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) diff --git a/HELP.MD b/HELP.MD index 35e36b3e..c4d69141 100644 --- a/HELP.MD +++ b/HELP.MD @@ -170,9 +170,8 @@ messages or business flows. Security model and residual risks: [mcp_security.md] **Project policy (optional)** - a literal top-level term in the *target project's* `rebar.config` (nearest file from the debug `cwd`, never above the workspace folder; no `rebar.config.script`, -no profiles). It controls tools, tables and *lower* limits only; activation, host, port and -credentials come only from the VS Code settings. The values below are the defaults and the hard -ceilings: +no profiles). It controls tools, tables and limits only; activation, host, port and +credentials come only from the VS Code settings. The values below are the defaults: ```erlang {mcp, [ @@ -180,7 +179,7 @@ ceilings: <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, <<"top_ports">>, <<"ets_summary">>]}, - {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>]; private tables never + {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>], or `all`; private tables never {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, {max_concurrency, 2}, {request_timeout_ms, 3000}, {collection_ttl_ms, 30000}, @@ -188,6 +187,38 @@ ceilings: ]}. ``` +A limit can be raised above its default up to a hard ceiling (never beyond): `max_request_bytes` 65536, +`max_result_bytes` 2097152, `max_items` 5000, `max_depth` 32, `max_binary_bytes` 65536, +`max_traversal_depth` 16, `max_concurrency` 8, `request_timeout_ms` 15000, `collection_ttl_ms` 600000, +`max_collection_bytes` 8388608, `max_collections` 16. Raising `collection_ttl_ms` and `max_collections` +is what makes `changes_since` useful over a longer debugging session. + +**Developer tier (opt-in)** - on your own machine, with your own code, metadata alone is often not +enough ("why is this process stuck?"). Three more tools return a *bounded sample of application +data*; they are never enabled by default and must be named in `allowed_tools`. Remember that +whatever a tool returns goes into the agent's context, i.e. to the model provider: + +```erlang +{mcp, [ + {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, + <<"process_info">>, <<"top_processes">>, <<"topology_overview">>, + <<"process_state">>, <<"mailbox_sample">>, <<"ets_sample">>]}, + {allowed_ets_tables, all}, %% or exact names; also lets ets_summary count every non-private table + {limits, [{max_items, 2000}, {max_result_bytes, 1048576}, {request_timeout_ms, 10000}, + {collection_ttl_ms, 300000}, {max_collections, 8}]} +]}. +``` + +| Tool | Returns | +| --- | --- | +| `process_state` | `sys:get_state` of one gen_server / gen_statem / gen_event process (never a supervisor: its state holds the child start arguments; never a plain process: it is never sent a system message), bounded by depth, items and binary size. A process stopped at a breakpoint is reported as such | +| `mailbox_sample` | queue length and the oldest few messages (default 5, max 20) of one process; a mailbox of more than 20000 messages is not copied | +| `ets_sample` | a few objects (default 5, max 20) of one approved, non-private named table | + +Redaction is best effort: values of keys or map entries named like a secret (`password`, `token`, +`secret`, `api_key`, `cookie`, `auth`...) and credentials in URLs (`scheme://user:pw@host`) are replaced, +but a record without keys, or a secret in an unlabelled term, cannot be recognised. + A malformed or duplicated term, an `mcp` term only inside a profile, or keys such as `enabled`, `host`, `port`, `required` or a token disable MCP for that session with an explanation. @@ -204,10 +235,12 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `topology_overview` | one call for one application (`name` or `application` id): the application, its supervision tree with child counts, registered processes and the approved ETS tables they own, as one collection. Parts whose own tool is not allowed by the policy are left out (`policy_denied` omission) | | `changes_since` | what changed since an earlier collection (`collectionId`): added, removed and replaced (restarted: same child, new id) entities, with `absence: unconfirmed` for removals from a partial collection. The baseline is only retained for `collection_ttl_ms` (30 s by default) and 2 collections: compare soon after the first call, else `baseline_expired` | | `top_ports` | local ports ranked by queue size, bytes in or bytes out (`sortBy`, `limit` up to 50): driver name, counters, queue size and the owner process (`owns_port` edge). Never addresses, command lines, paths or data; a non-driver port (e.g. a spawned program) is `` | -| `ets_summary` | ETS memory per owner process, largest first (`limit` up to 50): table count and memory in words/bytes, plus node totals. No table name, key or value, no table inventory (use `ets_tables` for approved tables) | +| `ets_summary` | ETS memory per owner process, largest first (`limit` up to 50): table count and memory in words/bytes, plus node totals. Counts only tables approved by `allowed_ets_tables` (exact names or `all`), never private ones; no table name, key or value, no table inventory (use `ets_tables` for approved tables) | | `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | | `debug_session` | launch/attach, interpreted modules, breakpoint locations (module and line), processes currently stopped at a breakpoint (`pausedProcesses`: process id, module, line) | +The developer-tier tools above (`process_state`, `mailbox_sample`, `ets_sample`) are only listed when the project enables them. + Graph tools also take `detail`, `pageSize` and `format`. `format: "mermaid"` adds a `mermaid` field (a `graph TD` of the returned page, built from the returned edges only) so that an agent can render the diagram without redrawing it. `detail: "summary"` (default) is meant for agents: same entities, ids and edges, without rarely needed fields (pids, descriptions, per-edge diff --git a/mcp-tasks.md b/mcp-tasks.md index b411d8be..e3354f35 100644 --- a/mcp-tasks.md +++ b/mcp-tasks.md @@ -586,8 +586,10 @@ Decided with the user after the V1 work order, all within the read-only/metadata - `top_processes` (queue length / reductions / memory ranking), `top_ports` (queue size / traffic counters; driver name and owner only, never addresses or command lines), `ets_summary` (ETS memory per owner, no table names), `topology_overview` (one application in one collection; each part obeys its own tool's allowlist entry), `changes_since` (diff against a retained collection; baseline limited by `collection_ttl_ms` / `max_collections`). - Additive fields: `children` counts on supervisors (`supervisor:count_children`), `pausedProcesses` in `debug_session` and a `debugger` field in `process_info` (from `int:snapshot/0`; a supervisor that is itself stopped at a breakpoint is reported as `unavailable_while_paused`), run queue / VM resource usage / connected node names in `runtime_summary`, `format: "mermaid"` on graph tools. - New edge type `owns_port` (MAP-03 extension). New MCP prompt `map_application` (the only exception to "no prompts"; static text, argument-checked, grants nothing). +- Developer tier (opt-in, only when named in the project's `allowed_tools`; returns a bounded, best-effort-redacted sample of application data, so it is outside the metadata-only V1 guardrail by design): `process_state` (`sys:get_state` of gen_server/gen_statem/gen_event, never a supervisor or a plain process), `mailbox_sample`, `ets_sample` (approved non-private tables). `allowed_ets_tables` may be `all`. Limits can be raised above their defaults up to fixed ceilings. See [mcp_security.md](./mcp_security.md). +- Review fixes: `ets_summary` counts only approved tables, `top_ports` names only well-known drivers, `changes_since` observes with the baseline's detail, one time budget for `debug_session`, one tree walk in `topology_overview`, inspector processes excluded by module, redaction of child ids, token/secret hidden from crash reports, non-ASCII workspace paths, `\z` anchors. - Not added: restart intensity/period (no safe API), distributed traversal, tracing, any mutation. -- Tests: `./rebar3 ct --dir apps/erlangbridge/test/mcp` (74 cases). Plain `./rebar3 ct` does not include this directory. +- Tests: `./rebar3 ct --dir apps/erlangbridge/test/mcp` (83 cases). Plain `./rebar3 ct` does not include this directory. ## Acceptance Criteria diff --git a/mcp_security.md b/mcp_security.md index 52102f07..229bab5d 100644 --- a/mcp_security.md +++ b/mcp_security.md @@ -3,14 +3,35 @@ Scope: the opt-in, read-only MCP server embedded in the Erlang node of a **debug session** (`erlang.mcp.enabled`, see [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only)). -## Threat model +## Who is being protected from what -Binding to loopback is *not* an authorization boundary. Assumed attackers: other processes of the -same user, compromised developer tools, browser pages attacking local services (DNS rebinding, -cross-origin requests), a malicious or prompt-injected MCP client, an accidentally forwarded -port. All requests, parameters and identifiers are untrusted. +The developer who turns this on owns the machine, the code and the node: the controls are **not** +there to stop the developer from looking at their own application (the debugger already shows +variables and can evaluate expressions). They exist because the *client* of this server is an AI +agent, and because a local HTTP port is reachable by more than the developer: -## Controls +1. **Data leaves the machine.** Whatever a tool returns goes into the agent's context, i.e. to a + model provider. This is the main reason the default is metadata only. +2. **A prompt-injected or malicious agent.** Source files, logs, issues or web pages the agent reads + can steer it. The server must stay harmless in the hands of such an agent: read-only, no + evaluation, no mutation, bounded cost. +3. **Browser pages** attacking `localhost` (DNS rebinding, cross-origin requests). +4. **Other processes of the same user**, compromised developer tools, an accidentally forwarded port. + +Binding to loopback is *not* an authorization boundary. All requests, parameters and identifiers are +untrusted. + +## Two tiers + +| Tier | What it returns | How it is enabled | +| --- | --- | --- | +| **Metadata** (default) | Topology and counters: applications, supervision trees, process names/status/queue lengths, ports (driver name, counters), ETS sizes, breakpoints. No application data | `erlang.mcp.enabled` | +| **Developer** (opt-in) | A *bounded sample of application data*: `process_state`, `mailbox_sample`, `ets_sample`; plus `allowed_ets_tables: all` and raised limits | the project's `rebar.config` names the tools in `allowed_tools` (never in the default set) | + +The developer tier is meant for local debugging of your own code: pick it deliberately, knowing that +the sampled data goes to the agent. Redaction is best effort (below), not a guarantee. + +## Controls (both tiers) | Control | How | | --- | --- | @@ -19,20 +40,35 @@ port. All requests, parameters and identifiers are untrusted. | Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | | Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | | Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | -| Least privilege | Twelve fixed read-only tools; the project policy can only narrow them; unknown tools are never turned into atoms or function names | -| No execution | No eval, `rpc:call`, shell, code loading, message sending, `sys` calls, tracing, breakpoint changes, ETS writes or reads | -| Data minimisation | No process dictionary, stack, mailbox, state, child start arguments, application environment, source, code paths, cookies; ETS: metadata of approved, non-private named tables only; node host redacted by default; child identifiers are printed with a depth/size bound | -| Safe identifiers | Registered names are resolved with `binary_to_existing_atom/2` only; pids must be local (`<0.N.M>`); no `binary_to_term/1` on input | -| Bounded work | Per-request timeout, monitored worker processes killed on timeout (late replies dropped), bounded concurrency (excess is rejected with an MCP error), bounded collections/cursors/identity bookkeeping, bounded result size (structured result *and* its text copy) | +| Read-only tools | Fixed tools; the project policy chooses among them; unknown tools are never turned into atoms or function names. Default set: 12 metadata tools. Developer tier: 3 more, only when named | +| No execution | No eval, `rpc:call`, shell, code loading, message sending, tracing, breakpoint changes or ETS writes. The only runtime calls that touch application processes are `sys:get_state/2` (developer tier, gen_server/gen_statem/gen_event only, never plain processes, never supervisors) and `process_info/2` | +| Data minimisation (metadata tier) | No process dictionary, stack, mailbox contents, state, child start arguments, application environment, source, code paths, cookies, socket addresses, command lines; node host redacted by default; ETS: only tables approved by `allowed_ets_tables` (private tables never) | +| Developer tier data | Bounded by depth, item count and binary size; values of keys named like a secret (`password`, `token`, `secret`, `api_key`, `cookie`, `auth`...) and credentials in URLs are replaced; supervisor state (child start arguments) is never returned; a mailbox above 20000 messages is not copied; ETS sampling only for approved, non-private tables | +| Safe identifiers | Registered names and table names are resolved with `binary_to_existing_atom/2` only (and a table name is checked against the approval list *before* any atom lookup); pids must be local (`<0.N.M>`); no `binary_to_term/1` on input; validated with `\z` anchors | +| Bounded work | Per-request timeout, monitored worker processes killed on timeout (late replies dropped), bounded concurrency (excess is rejected with an MCP error), bounded collections/cursors/identity bookkeeping, bounded result size (structured result *and* its text copy). Defaults are conservative; a project may raise a limit up to a fixed ceiling (see HELP.MD), never beyond | | Session isolation | Tokens, ids, cursors (signed with a per-session secret and bound to tool + arguments) and workers belong to one session/node; a new session invalidates everything | | Clean shutdown | Dedicated supervisor `mcp_sup` (fail-closed: any child exit stops everything); stopped by disconnect/detach; an 8 s renewable lease renewed by the adapter through the target's own command server (independent of application processes stopped at breakpoints) stops the inspector within ~9-10 s if the adapter disappears; an attached node is never stopped by the inspector | -| Secret hand-over | The token is returned by the target only to the adapter, written by the adapter to a `0600` file in a `0700` temp directory, read and deleted at once by the extension host, and then kept in memory only. It is never in a DAP event/output, log, setting, command line or telemetry. Explicit *Copy Authorization header* is the only way it leaves VS Code | -| Logs | The *Erlang MCP* output channel journals request metadata only (method, known tool name, status, durations, sizes, counts); only lifecycle events, tool name, status and duration go to the target's logger; verbose adapter logging redacts secret-like keys and the `-setcookie` value | +| Secret hand-over | The token is returned by the target only to the adapter, written by the adapter to a `0600` file in a `0700` temp directory, read and deleted at once by the extension host, and then kept in memory only. It is never in a DAP event/output, log, crash report, setting, command line or telemetry. Explicit *Copy Authorization header* is the only way it leaves VS Code | +| Logs | The *Erlang MCP* output channel journals request metadata only (method, known tool name, status, durations, sizes, counts); only lifecycle events, tool name, status and duration go to the target's logger; the server and store processes redact the token and the cursor secret in `sys:get_status` and crash reports; verbose adapter logging redacts secret-like keys and the `-setcookie` value | + +## What you can relax, and what you should not + +Reasonable on your own machine, per project: the developer tier, `allowed_ets_tables: all`, higher +limits (for example a longer `collection_ttl_ms` so `changes_since` baselines survive), and a fixed +`erlang.mcp.authToken` for a static client such as Claude Code. + +Keep: loopback binding, the bearer token, Host/Origin checks, read-only access, debug-session-only +activation. They cost nothing in daily use and are what protects you from items 2-4 above. ## Residual risks (not removed by the controls) - Metadata can be sensitive: application, module, table and child-id names, process counts and memory sizes. Enable the tools/tables you are comfortable sharing with the agent (rebar policy). +- Developer tier: the sampled state, messages and rows can contain secrets that redaction does not + recognise (a record without keys, a token in an unlabelled string, personal data). Treat them as + disclosed to the model provider. A process stopped at a breakpoint cannot be sampled (and is + reported as such); `sys:get_state` is answered by the process itself, so a very large state costs + the target time and memory (bounded by the request timeout, not preemptible). - Node-level authorization: selecting an application is a navigation filter, not a boundary between applications in the same VM. - The token is bearer-only over plain HTTP on loopback: another process of the same user that can @@ -43,16 +79,21 @@ port. All requests, parameters and identifiers are untrusted. loopback HTTP endpoint (it can already evaluate expressions for the debugger). The MCP start command travels on it, so a local process could start or stop the inspector (a local process could already debug the node). The token is returned in the reply to the caller of that command. +- Connection slots are claimed before authentication (a bounded number, short timeouts): a local + process can keep them busy and delay the real client (denial of service, no data exposure). With + a fixed `erlang.mcp.port` on Windows the listener uses `SO_REUSEADDR`, which on that platform can + let another local process share the port; prefer the default ephemeral port there. - Optional `erlang.mcp.authToken` (user setting only): a fixed token replaces the per-session random one (validated 16-128 URL-safe characters). It lives in the user's settings file and, during a session, in the adapter and target memory; it survives across sessions, so it is exposed for as long as it is configured. Default is empty (random token). - Distribution: attach uses the node's Erlang cookie as before. - Not atomic and not free: collections are observation intervals; `application:which_applications/1`, - `registered/0` and `supervisor:which_children/1` materialise lists in the target and cannot be - interrupted (killing the worker only discards the late reply); `proc_lib:initial_call/1` is used - internally, only as a safety gate (to avoid calling supervisor APIs on non-supervisors), and - copies the target's process dictionary into the inspector's worker without returning it. + `registered/0`, `erlang:processes/0`, `ets:all/0` and `supervisor:which_children/1` materialise lists + in the target and cannot be interrupted (killing the worker only discards the late reply); + `proc_lib:initial_call/1` is used internally, only as a safety gate (to avoid calling supervisor + APIs on non-supervisors), and copies the target's process dictionary into the inspector's worker + without returning it. - Same-machine only: no port forwarding, remote-SSH/container arrangements are unsupported. - The MCP modules are also built into the `vscode_lsp` application's `ebin` (rebar compiles `src/mcp`), but the LSP node never loads or starts them. From 4226465550fdd59a6efcdc5cb33dd245c75f9eb9 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 17:33:11 +0200 Subject: [PATCH 15/20] Adapter e2e: ets_summary counts nothing by default, developer tools are off Co-Authored-By: Claude Sonnet 5.5 --- test/test-suite/mcp/mcp_debug_adapter.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test-suite/mcp/mcp_debug_adapter.test.ts b/test/test-suite/mcp/mcp_debug_adapter.test.ts index de9f2585..fe7f6952 100644 --- a/test/test-suite/mcp/mcp_debug_adapter.test.ts +++ b/test/test-suite/mcp/mcp_debug_adapter.test.ts @@ -202,7 +202,12 @@ suite('MCP: debug adapter end-to-end (real erl target)', function () { assert.strictEqual(top.entities.length, 3); assert.ok(top.mermaid.startsWith('graph TD')); assert.ok((await ok('top_ports', { limit: 3 })).entities.length > 0); - assert.ok((await ok('ets_summary', { limit: 3 })).entities.length > 0); + // no table is approved by default: nothing is counted, and that is reported + const ets = await ok('ets_summary', { limit: 3 }); + assert.strictEqual(ets.entities.length, 0); + assert.ok(ets.omissions.some((o: any) => o.reason === 'policy_denied')); + const denied = await call(started.url, token, 'process_state', { name: 'init' }); + assert.strictEqual(denied.json.error.code, -32602, 'developer tools are off by default'); const topo = await ok('topology_overview', { name: 'kernel', format: 'mermaid' }); assert.ok(topo.relationships.some((r: any) => r.type === 'supervises'), 'kernel supervision tree'); assert.ok(topo.mermaid.includes('-->')); From 545343a2d2ddd84c189c9ffa5599790b4880a429 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 17:55:03 +0200 Subject: [PATCH 16/20] MCP policy: allowed_tools accepts default; initialize lists disabled tools Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 6 +++++ apps/erlangbridge/src/mcp/mcp_policy.erl | 13 +++++++++-- apps/erlangbridge/src/mcp/mcp_server.erl | 22 ++++++++++++++++-- .../test/mcp/mcp_policy_SUITE.erl | 19 +++++++++++++++ .../test/mcp/mcp_server_SUITE.erl | 23 +++++++++++++++++++ 6 files changed, 80 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fbc0347..01f72243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - New tools `top_ports` (local ports by queue size or traffic counters: driver name and owner only, never addresses or command lines) and `ets_summary` (ETS memory per owner process, no table names or contents); `runtime_summary` lists the connected nodes (host redacted by default); new edge type `owns_port` - Developer tier (opt-in, named in the project's `allowed_tools`): `process_state` (bounded `sys:get_state` of a gen_server/gen_statem/gen_event, never a supervisor), `mailbox_sample` (oldest messages) and `ets_sample` (a few rows of an approved table), with best-effort redaction of secret-like keys and URL credentials; `allowed_ets_tables` accepts `all`; limits can be raised above their defaults up to higher ceilings (e.g. `collection_ttl_ms` up to 10 min) - Hardening: the bearer token and cursor secret no longer appear in crash reports, non-ASCII workspace paths find the project policy, `ets_summary` only counts approved tables, `top_ports` only names well-known drivers, inspector processes are excluded from the rankings, child ids are redacted + - `allowed_tools` accepts `default` (the built-in set, also after upgrades) or `[default, <<"tool">>]`; the server tells the agent in its `instructions` which tools the project policy disables - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) diff --git a/HELP.MD b/HELP.MD index c4d69141..18148649 100644 --- a/HELP.MD +++ b/HELP.MD @@ -187,6 +187,12 @@ credentials come only from the VS Code settings. The values below are the defaul ]}. ``` +`allowed_tools` is an allowlist: a tool that is not listed is hidden, including tools added in later +versions of the extension. To follow the built-in set write `{allowed_tools, default}`, or +`{allowed_tools, [default, <<"process_state">>]}` to add developer tools to it. When the policy +disables tools, the server says so in its MCP `instructions` (so the agent can tell you why a tool +is missing instead of silently using a fallback). + A limit can be raised above its default up to a hard ceiling (never beyond): `max_request_bytes` 65536, `max_result_bytes` 2097152, `max_items` 5000, `max_depth` 32, `max_binary_bytes` 65536, `max_traversal_depth` 16, `max_concurrency` 8, `request_timeout_ms` 15000, `collection_ttl_ms` 600000, diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index 46f10eaf..7ee19859 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -13,7 +13,7 @@ %% %% Normalized configuration (internal contract, atom keys) : %% #{host => "127.0.0.1", port => 0, -%% allowed_tools => [<<"runtime_summary">>, ...], +%% allowed_tools => [<<"runtime_summary">>, ...], (project term: a list, `default`, or [default, <<"tool">>]) %% allowed_ets_tables => [<<"orders_index">>], %% limits => #{max_items => 500, ...}} %% and its JSON form (camelCase binary keys) which crosses to the target. @@ -337,11 +337,20 @@ normalize_project_term(Term) when is_list(Term) -> normalize_project_term(_) -> {error, "invalid mcp policy: the mcp term must be a list"}. -project_key({allowed_tools, Tools}, Acc) -> +project_key({allowed_tools, default}, Acc) -> + Acc#{allowed_tools => ?DEFAULT_TOOLS}; +project_key({allowed_tools, Tools0}, Acc) when is_list(Tools0) -> + %% the atom `default` stands for the built-in default set, so that a policy keeps up + %% with tools added later: [default, <<"process_state">>] + {Defaults, Explicit} = lists:partition(fun(T) -> T =:= default end, Tools0), + length(Defaults) =< 1 orelse throw("allowed_tools lists default twice"), + Tools = Explicit ++ [T || _ <- Defaults, T <- ?DEFAULT_TOOLS, not lists:member(T, Explicit)], Names = binary_list(Tools, "allowed_tools"), length(Names) =:= length(lists:usort(Names)) orelse throw("allowed_tools has duplicates"), lists:foreach(fun(N) -> lists:member(N, ?TOOLS) orelse throw("allowed_tools has an unknown tool name") end, Names), Acc#{allowed_tools => Names}; +project_key({allowed_tools, _}, _Acc) -> + throw("allowed_tools must be a list of tool names, optionally with default, or default"); project_key({allowed_ets_tables, all}, Acc) -> Acc#{allowed_ets_tables => all}; project_key({allowed_ets_tables, Tables}, Acc) -> diff --git a/apps/erlangbridge/src/mcp/mcp_server.erl b/apps/erlangbridge/src/mcp/mcp_server.erl index 501092bd..6520a645 100644 --- a/apps/erlangbridge/src/mcp/mcp_server.erl +++ b/apps/erlangbridge/src/mcp/mcp_server.erl @@ -396,7 +396,7 @@ error_obj(Id, Code, Message) -> result_obj(Id, Result) -> #{<<"jsonrpc">> => <<"2.0">>, <<"id">> => Id, <<"result">> => Result}. -dispatch(<<"initialize">>, Params, Id, _Config) -> +dispatch(<<"initialize">>, Params, Id, Config) -> case maps:get(<<"protocolVersion">>, Params, undefined) of V when is_binary(V) -> %% version negotiation: the server answers with the revision it implements @@ -415,7 +415,8 @@ dispatch(<<"initialize">>, Params, Id, _Config) -> "keeps answers small; follow nextCursor only if you need the remaining items. Use " "detail=full only when the user asks for the complete JSON (e.g. to save it to a file). " "Maps are bounded observation intervals, not atomic snapshots: honour complete/" - "truncated/omissions. Structural edges are not message traffic.">>}); + "truncated/omissions. Structural edges are not message traffic.", + (disabled_note(Config))/binary>>}); _ -> error_obj(Id, -32602, <<"protocolVersion is required">>) end; @@ -435,6 +436,23 @@ dispatch(<<"prompts/get">>, Params, Id, Config) -> dispatch(_, _, Id, _) -> error_obj(Id, -32601, <<"method not found">>). +%% Tells the agent (and the user reading its explanation) why a tool is missing: the +%% project policy narrows the default set, or the developer tier is not enabled. +disabled_note(Config) -> + Allowed = fun(T) -> mcp_policy:tool_allowed(T, Config) end, + Off = [T || T <- mcp_policy:default_tools(), not Allowed(T)], + Dev = [T || T <- mcp_policy:all_tools() -- mcp_policy:default_tools(), not Allowed(T)], + Note1 = case Off of + [] -> <<>>; + _ -> iolist_to_binary([" Not available in this session (disabled by allowed_tools in the project's rebar.config; " + "use `default` there to follow the built-in set): ", lists:join(", ", Off), "."]) + end, + Note2 = case Dev of + [] -> <<>>; + _ -> iolist_to_binary([" Developer tools not enabled (name them in allowed_tools): ", lists:join(", ", Dev), "."]) + end, + <>. + tools_call(Params, Id, Config) -> Name = maps:get(<<"name">>, Params, undefined), Args = maps:get(<<"arguments">>, Params, #{}), diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index c0aa4c25..d8f9d03e 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -20,6 +20,7 @@ all() -> project_cannot_set_activation_or_binding, project_limits_cannot_exceed_ceilings, developer_tier_is_opt_in_and_ets_all_is_accepted, + default_keyword_follows_the_built_in_tool_set, non_ascii_workspace_path_still_finds_the_project_policy, token_with_trailing_newline_is_rejected, invalid_and_duplicate_project_terms, @@ -162,6 +163,24 @@ developer_tier_is_opt_in_and_ets_all_is_accepted(Config) -> ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Text) end || Text <- ["{mcp, [{allowed_ets_tables, everything}]}.\n", "{mcp, [{allowed_tools, [<<\"process_dump\">>]}]}.\n"]]. +default_keyword_follows_the_built_in_tool_set(Config) -> + Default = mcp_policy:default_tools(), + Tools = fun(Text) -> + write_rebar(Config, "{mcp, [{allowed_tools, " ++ Text ++ "}]}.\n"), + {ok, C} = mcp_policy:resolve(input(Config, #{})), + maps:get(allowed_tools, C) + end, + ?assertEqual(Default, Tools("default")), + ?assertEqual(Default, Tools("[default]")), + %% the built-in set plus a developer tool; an explicit tool is never listed twice + ?assertEqual(lists:sort([<<"process_state">> | Default]), lists:sort(Tools("[default, <<\"process_state\">>]"))), + ?assertEqual(lists:sort(Default), lists:sort(Tools("[default, <<\"runtime_summary\">>]"))), + [begin + write_rebar(Config, Text), + ?assertMatch({error, _}, mcp_policy:resolve(input(Config, #{})), Text) + end || Text <- ["{mcp, [{allowed_tools, [default, default]}]}.\n", "{mcp, [{allowed_tools, [defaults]}]}.\n", + "{mcp, [{allowed_tools, all}]}.\n"]]. + non_ascii_workspace_path_still_finds_the_project_policy(Config) -> Dir = filename:join(?config(priv_dir, Config), "José_проект"), ok = filelib:ensure_dir(filename:join(Dir, "x")), diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 11480397..94cd64e6 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -18,6 +18,8 @@ all() -> host_and_origin_validated, method_content_type_and_size_enforced, initialize_negotiates_and_lists_only_readonly_tools, + initialize_tells_the_agent_which_tools_are_disabled, + initialize_is_silent_when_nothing_is_disabled, allowlist_enforced, arguments_are_strict, runtime_summary_redacts_node_host, @@ -93,6 +95,8 @@ test_config(Case) -> case Case of allowlist_enforced -> Base1#{allowed_tools => [<<"runtime_summary">>, <<"ets_tables">>]}; + initialize_tells_the_agent_which_tools_are_disabled -> + Base1#{allowed_tools => [<<"runtime_summary">>, <<"ets_tables">>]}; ets_summary_aggregates_per_owner_without_names -> Base1#{allowed_ets_tables => all}; Dev when Dev =:= process_state_is_bounded_redacted_and_never_for_supervisors; @@ -351,6 +355,25 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> || T <- [<<"eval">>, <<"ets_lookup">>, <<"rpc_call">>, <<"sys_get_state">>, <<"shell">>, <<"trace">>, <<"kill_process">>, <<"set_breakpoint">>, <<"load_code">>]]. +initialize_tells_the_agent_which_tools_are_disabled(Config) -> + #{<<"result">> := #{<<"instructions">> := Instr}} = + rpc(Config, <<"initialize">>, #{<<"protocolVersion">> => <<"2025-06-18">>}), + %% allowlist_enforced config: only runtime_summary and ets_tables are enabled + ?assertNotEqual(nomatch, binary:match(Instr, <<"Not available in this session">>)), + ?assertNotEqual(nomatch, binary:match(Instr, <<"top_processes">>)), + ?assertNotEqual(nomatch, binary:match(Instr, <<"`default`">>)), + ?assertNotEqual(nomatch, binary:match(Instr, <<"Developer tools not enabled">>)), + %% enabled tools are not listed as disabled + ?assertEqual(nomatch, binary:match(Instr, <<"ets_tables,">>)), + ?assertEqual(nomatch, binary:match(Instr, <<", ets_tables">>)). + +initialize_is_silent_when_nothing_is_disabled(Config) -> + #{<<"result">> := #{<<"instructions">> := Instr}} = + rpc(Config, <<"initialize">>, #{<<"protocolVersion">> => <<"2025-06-18">>}), + ?assertEqual(nomatch, binary:match(Instr, <<"Not available in this session">>)), + %% only the developer tier is listed (it is off by default) + ?assertNotEqual(nomatch, binary:match(Instr, <<"Developer tools not enabled">>)). + allowlist_enforced(Config) -> #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), ?assertEqual([<<"ets_tables">>, <<"runtime_summary">>], lists:sort([maps:get(<<"name">>, T) || T <- Tools])), From 09af47fd292a2b98205fd051f4a817c2387e5eaa Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 18:13:33 +0200 Subject: [PATCH 17/20] Add process_groups tool: processes aggregated by initial call in a fixed-size answer Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + HELP.MD | 3 +- apps/erlangbridge/src/mcp/mcp_policy.erl | 2 +- apps/erlangbridge/src/mcp/mcp_runtime.erl | 154 ++++++++++++++++++ apps/erlangbridge/src/mcp/mcp_tools.erl | 19 +++ .../test/mcp/mcp_policy_SUITE.erl | 2 +- .../test/mcp/mcp_server_SUITE.erl | 49 +++++- mcp-tasks.md | 4 +- mcp_security.md | 2 +- test/test-suite/mcp/mcp_debug_adapter.test.ts | 2 +- 10 files changed, 230 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f72243..0827350f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Developer tier (opt-in, named in the project's `allowed_tools`): `process_state` (bounded `sys:get_state` of a gen_server/gen_statem/gen_event, never a supervisor), `mailbox_sample` (oldest messages) and `ets_sample` (a few rows of an approved table), with best-effort redaction of secret-like keys and URL credentials; `allowed_ets_tables` accepts `all`; limits can be raised above their defaults up to higher ceilings (e.g. `collection_ttl_ms` up to 10 min) - Hardening: the bearer token and cursor secret no longer appear in crash reports, non-ASCII workspace paths find the project policy, `ets_summary` only counts approved tables, `top_ports` only names well-known drivers, inspector processes are excluded from the rankings, child ids are redacted - `allowed_tools` accepts `default` (the built-in set, also after upgrades) or `[default, <<"tool">>]`; the server tells the agent in its `instructions` which tools the project policy disables + - New tool `process_groups`: all local processes aggregated by initial call (count, memory, reductions, queue, registered vs unregistered, sample ids) in an answer of fixed size, to find fan-out and leaks of anonymous processes - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) diff --git a/HELP.MD b/HELP.MD index 18148649..0dda054a 100644 --- a/HELP.MD +++ b/HELP.MD @@ -178,7 +178,7 @@ credentials come only from the VS Code settings. The values below are the defaul {allowed_tools, [<<"runtime_summary">>, <<"application_overview">>, <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, - <<"top_ports">>, <<"ets_summary">>]}, + <<"top_ports">>, <<"ets_summary">>, <<"process_groups">>]}, {allowed_ets_tables, []}, %% exact names, e.g. [<<"orders_index">>], or `all`; private tables never {limits, [{max_request_bytes, 16384}, {max_result_bytes, 262144}, {max_items, 500}, {max_depth, 16}, {max_binary_bytes, 4096}, {max_traversal_depth, 8}, @@ -240,6 +240,7 @@ A malformed or duplicated term, an `mcp` term only inside a profile, or keys suc | `ets_tables` | metadata of the approved named tables (protection, type, owner, size, memory in words and bytes) - never keys or values | | `topology_overview` | one call for one application (`name` or `application` id): the application, its supervision tree with child counts, registered processes and the approved ETS tables they own, as one collection. Parts whose own tool is not allowed by the policy are left out (`policy_denied` omission) | | `changes_since` | what changed since an earlier collection (`collectionId`): added, removed and replaced (restarted: same child, new id) entities, with `absence: unconfirmed` for removals from a partial collection. The baseline is only retained for `collection_ttl_ms` (30 s by default) and 2 collections: compare soon after the first call, else `baseline_expired` | +| `process_groups` | every local process aggregated by where it was started (initial call; the callback module for gen_server and friends): the top groups (`limit` up to 50, `sortBy` count / memory / reductions / message_queue_len) with count, share of the node, registered vs unregistered, memory, queue, inferred applications and 3 sample process ids. The answer has a fixed size whatever the number of processes (40,000 anonymous workers = one group); finds fan-out and leaks | | `top_ports` | local ports ranked by queue size, bytes in or bytes out (`sortBy`, `limit` up to 50): driver name, counters, queue size and the owner process (`owns_port` edge). Never addresses, command lines, paths or data; a non-driver port (e.g. a spawned program) is `` | | `ets_summary` | ETS memory per owner process, largest first (`limit` up to 50): table count and memory in words/bytes, plus node totals. Counts only tables approved by `allowed_ets_tables` (exact names or `all`), never private ones; no table name, key or value, no table inventory (use `ets_tables` for approved tables) | | `top_processes` | the hottest local processes ranked by message queue length, reductions or memory (`sortBy`, `limit` up to 50): status, current function, initial call, name, membership. Bounded point-in-time scan, never mailbox/state | diff --git a/apps/erlangbridge/src/mcp/mcp_policy.erl b/apps/erlangbridge/src/mcp/mcp_policy.erl index 7ee19859..a3bb9b31 100644 --- a/apps/erlangbridge/src/mcp/mcp_policy.erl +++ b/apps/erlangbridge/src/mcp/mcp_policy.erl @@ -36,7 +36,7 @@ <<"supervision_tree">>, <<"process_info">>, <<"registered_processes">>, <<"ets_tables">>, <<"debug_session">>, <<"top_processes">>, <<"topology_overview">>, <<"changes_since">>, - <<"top_ports">>, <<"ets_summary">>]). + <<"top_ports">>, <<"ets_summary">>, <<"process_groups">>]). %% Developer tier: they return data of the debugged application (a bounded process %% state, a few mailbox messages, a few table rows), so a project must name them in %% allowed_tools; they are never enabled by default. diff --git a/apps/erlangbridge/src/mcp/mcp_runtime.erl b/apps/erlangbridge/src/mcp/mcp_runtime.erl index 0f751885..52fbb38e 100644 --- a/apps/erlangbridge/src/mcp/mcp_runtime.erl +++ b/apps/erlangbridge/src/mcp/mcp_runtime.erl @@ -28,6 +28,7 @@ -define(MAX_TOP, 50). -define(MAX_ETS_SCAN, 50000). -define(MAX_MAILBOX_COPY, 20000). +-define(MAX_GROUPS, 5000). -define(MAX_SAMPLE, 20). %%------------------------------------------------------------------------------ @@ -71,6 +72,7 @@ run(<<"registered_processes">>, Args, Ctx) -> graph_tool(<<"registered_processes run(<<"process_info">>, Args, Ctx) -> tool_process_info(Args, Ctx); run(<<"ets_tables">>, Args, Ctx) -> graph_tool(<<"ets_tables">>, builder(<<"ets_tables">>), Args, Ctx); run(<<"process_state">>, Args, Ctx) -> process_state(Args, Ctx); +run(<<"process_groups">>, Args, Ctx) -> process_groups(Args, Ctx); run(<<"mailbox_sample">>, Args, Ctx) -> mailbox_sample(Args, Ctx); run(<<"ets_sample">>, Args, Ctx) -> ets_sample(Args, Ctx); run(<<"top_ports">>, Args, Ctx) -> graph_tool(<<"top_ports">>, builder(<<"top_ports">>), Args, Ctx); @@ -1676,6 +1678,158 @@ top_entity(Pid, Info, SortBy, Rank, Masters, B) -> {_, B1} = process_entity(Pid, Fields, B), {ok, B1}. +%%------------------------------------------------------------------------------ +%% process_groups: every local process aggregated by its initial call. The scan keeps +%% one counter per group (never per-process data), so memory is O(groups) and the answer +%% is O(limit) whether the node has 400 or 400000 processes. Finds fan-out and leaks +%% (thousands of anonymous workers) that a top-N list of single processes never shows. +%%------------------------------------------------------------------------------ + +process_groups(Args, #{enc := Enc} = Ctx) -> + Started = mcp_store:now_ms(), + SortBy = case maps:get(<<"sortBy">>, Args, <<"count">>) of + <<"memory">> -> memory; + <<"reductions">> -> reductions; + <<"message_queue_len">> -> queue; + _ -> count + end, + Limit = min(maps:get(<<"limit">>, Args, 10), ?MAX_TOP), + All = erlang:processes(), + Total = length(All), + {Scan, Cut} = case Total > ?MAX_SCANNED of + true -> {lists:sublist(All, ?MAX_SCANNED), Total - ?MAX_SCANNED}; + false -> {All, 0} + end, + Masters = masters(Ctx), + Acc0 = #{groups => #{}, scanned => 0, registered => 0, memory => 0, other => 0}, + {Acc, Unscanned} = group_scan(Scan, self(), Masters, Ctx, 0, Acc0), + #{groups := Groups, scanned := Scanned, registered := Registered, memory := TotalMem, other := Other} = Acc, + Ranked = lists:sublist(lists:sort(fun(A, B) -> group_rank(SortBy, A) >= group_rank(SortBy, B) end, + [G#{key => K} || {K, G} <- maps:to_list(Groups)]), Limit), + Shown = [group_json(G, I, Scanned, TotalMem, Enc) || {I, G} <- lists:zip(lists:seq(1, length(Ranked)), Ranked)], + CoveredCount = lists:sum([maps:get(count, G) || G <- Ranked]), + Oms = [#{<<"reason">> => <<"timeout">>, <<"count">> => Unscanned, <<"resumable">> => false, + <<"message">> => <<"the time budget ended the scan; the groups only cover the processes scanned so far">>} + || Unscanned > 0] + ++ [#{<<"reason">> => <<"limit_reached">>, <<"count">> => Cut, <<"resumable">> => false, + <<"message">> => <<"more processes than the scan limit; the groups only cover the first ones">>} + || Cut > 0] + ++ [#{<<"reason">> => <<"limit_reached">>, <<"count">> => Other, <<"resumable">> => false, + <<"message">> => <<"more distinct initial calls than the group limit; the rest are counted in ">>} + || Other > 0], + {ok, #{<<"schemaVersion">> => ?SV, + <<"sessionId">> => mcp_store:session_id(), + <<"observedAt">> => mcp_encoder:iso8601(Started), + <<"sortBy">> => atom_to_binary(case SortBy of queue -> message_queue_len; _ -> SortBy end, utf8), + <<"totals">> => #{<<"processCount">> => Total, <<"scanned">> => Scanned, + <<"registered">> => Registered, <<"unregistered">> => Scanned - Registered, + <<"groups">> => maps:size(Groups), <<"groupsShown">> => length(Shown), + <<"shownCoverPercent">> => percent(CoveredCount, Scanned), + <<"memoryBytes">> => TotalMem}, + <<"groups">> => Shown, + <<"complete">> => Oms =:= [], + <<"truncated">> => Cut > 0 orelse Other > 0, + <<"omissions">> => Oms, + <<"limitations">> => + [<<"Grouped by initial call (the callback module's init for OTP processes): a group is where processes were started, not a dependency.">>, + <<"A point-in-time scan, not an atomic snapshot: processes start and exit while it runs.">>, + <<"reductions are cumulative since the process started; memory is in bytes.">>, + <<"Only metadata is read: never mailbox, dictionary contents, stack or state. Inspector processes are excluded.">>]}}. + +group_scan([], _Self, _Masters, _Ctx, _N, Acc) -> {Acc, 0}; +group_scan([Pid | T] = Pids, Self, Masters, Ctx, N, Acc) -> + case N rem 1000 =:= 0 andalso N > 0 andalso remaining(Ctx) =:= 0 of + true -> {Acc, length(Pids)}; + false -> + Acc1 = case Pid =/= Self andalso + erlang:process_info(Pid, [registered_name, initial_call, current_function, memory, + reductions, message_queue_len, group_leader]) of + Info when is_list(Info) -> + case inspector_info(Info) of + true -> Acc; + false -> group_add(Pid, Info, Masters, Acc) + end; + _ -> Acc + end, + group_scan(T, Self, Masters, Ctx, N + 1, Acc1) + end. + +group_add(Pid, Info, Masters, #{groups := Groups, scanned := S, registered := R, memory := M, other := O} = Acc) -> + Key0 = group_key(Pid, Info), + {Key, Cap} = case maps:is_key(Key0, Groups) orelse maps:size(Groups) < ?MAX_GROUPS of + true -> {Key0, 0}; + false -> {other, 1} + end, + Reg = case lists:keyfind(registered_name, 1, Info) of + {registered_name, N} when is_atom(N), N =/= [] -> 1; + _ -> 0 + end, + Mem = proplists:get_value(memory, Info, 0), + App = case maps:find(proplists:get_value(group_leader, Info), Masters) of + {ok, A} -> A; + error -> undefined + end, + G0 = maps:get(Key, Groups, #{count => 0, memory => 0, reductions => 0, queue => 0, registered => 0, + apps => #{}, samples => []}), + #{count := C, memory := GM, reductions := GR, queue := GQ, registered := GReg, apps := Apps, samples := Sm} = G0, + G1 = G0#{count := C + 1, memory := GM + Mem, + reductions := GR + proplists:get_value(reductions, Info, 0), + queue := GQ + proplists:get_value(message_queue_len, Info, 0), + registered := GReg + Reg, + apps := maps:update_with(App, fun(X) -> X + 1 end, 1, Apps), + samples := case length(Sm) < 3 of true -> Sm ++ [Pid]; false -> Sm end}, + Acc#{groups := Groups#{Key => G1}, scanned := S + 1, registered := R + Reg, memory := M + Mem, other := O + Cap}. + +%% {M, F, A} of where the process started; for gen_server and friends the callback module's +%% init (OTP 25+ reads the one dictionary entry, never the whole dictionary). +group_key(Pid, Info) -> + Dict = try erlang:process_info(Pid, {dictionary, '$initial_call'}) of + {{dictionary, '$initial_call'}, {M0, F0, A0}} when is_atom(M0), is_atom(F0), is_integer(A0) -> {M0, F0, A0}; + _ -> undefined + catch _:_ -> undefined + end, + case Dict of + undefined -> + case proplists:get_value(initial_call, Info) of + {M, F, A} -> {M, F, A}; + _ -> unknown + end; + _ -> Dict + end. + +group_rank(count, #{count := V}) -> V; +group_rank(memory, #{memory := V}) -> V; +group_rank(reductions, #{reductions := V}) -> V; +group_rank(queue, #{queue := V}) -> V. + +group_json(#{key := Key, count := C, memory := M, reductions := R, queue := Q, registered := Reg, + apps := Apps, samples := Sm}, Rank, Scanned, TotalMem, Enc) -> + {Text, Mod} = case Key of + {Mo, F, A} -> {iolist_to_binary([bin(Mo), ":", bin(F), "/", integer_to_binary(A)]), bin(Mo)}; + other -> {<<"">>, null}; + unknown -> {<<"">>, null} + end, + AppList = lists:sublist(lists:reverse(lists:keysort(2, [{A, N} || {A, N} <- maps:to_list(Apps), A =/= undefined])), 5), + Unattributed = maps:get(undefined, Apps, 0), + #{<<"rank">> => Rank, + <<"initialCall">> => mcp_encoder:safe_binary(Text, maps:get(max_binary_bytes, Enc)), + <<"module">> => Mod, + <<"count">> => C, + <<"countPercent">> => percent(C, Scanned), + <<"registered">> => Reg, + <<"unregistered">> => C - Reg, + <<"memoryBytes">> => M, + <<"memoryPercent">> => percent(M, TotalMem), + <<"reductions">> => R, + <<"messageQueueLen">> => Q, + <<"applications">> => [#{<<"name">> => bin(A), <<"count">> => N, + <<"confidence">> => <<"inferred">>} || {A, N} <- AppList], + <<"unattributed">> => Unattributed, + <<"samples">> => [pid_id(P) || P <- Sm]}. + +percent(_, 0) -> 0.0; +percent(N, Total) -> round(N * 1000 / Total) / 10. + %%------------------------------------------------------------------------------ %% top_ports: local ports ranked by queue size / bytes in / bytes out. Only the %% driver name (never an address, command line or path), counters and the owner. diff --git a/apps/erlangbridge/src/mcp/mcp_tools.erl b/apps/erlangbridge/src/mcp/mcp_tools.erl index 2a1d7888..a0d49a79 100644 --- a/apps/erlangbridge/src/mcp/mcp_tools.erl +++ b/apps/erlangbridge/src/mcp/mcp_tools.erl @@ -246,6 +246,25 @@ def(<<"ets_sample">>) -> <<"table">> => str(), <<"size">> => int(), <<"sample">> => #{<<"type">> => <<"array">>}}, [<<"schemaVersion">>, <<"sessionId">>])}; +def(<<"process_groups">>) -> + {<<"Process groups">>, + <<"Aggregate every local process by where it was started (initial call; for gen_server and friends the callback " + "module's init) and return the largest groups (default 10, max 50) by process count, memory, reductions or " + "queue length: totals, shares of the node, registered versus unregistered, inferred application membership and " + "up to 3 sample process ids per group (usable with process_info). Finds fan-out and leaks - thousands of " + "anonymous workers - that a top list of single processes does not show. The answer has a fixed size however " + "many processes exist; the scan is bounded (process cap, time budget, group cap) and says when it was cut. " + "Metadata only.">>, + obj(#{<<"sortBy">> => #{<<"type">> => <<"string">>, + <<"enum">> => [<<"count">>, <<"memory">>, <<"reductions">>, <<"message_queue_len">>], + <<"description">> => <<"Ranking criterion. Default count.">>}, + <<"limit">> => #{<<"type">> => <<"integer">>, <<"minimum">> => 1, <<"maximum">> => 50, + <<"description">> => <<"Number of groups to return. Default 10.">>}}, []), + obj(#{<<"schemaVersion">> => str(), <<"sessionId">> => str(), <<"observedAt">> => str(), + <<"totals">> => #{<<"type">> => <<"object">>}, <<"groups">> => #{<<"type">> => <<"array">>}, + <<"complete">> => #{<<"type">> => <<"boolean">>}, <<"omissions">> => #{<<"type">> => <<"array">>}}, + [<<"schemaVersion">>, <<"sessionId">>])}; + def(<<"top_ports">>) -> {<<"Top ports">>, <<"Rank the local ports of the node (sockets, files, spawned programs) by queue size, bytes in or bytes out and " diff --git a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl index d8f9d03e..079f2168 100644 --- a/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_policy_SUITE.erl @@ -69,7 +69,7 @@ defaults_without_project_file(Config) -> ?assertEqual([], [T || T <- [<<"process_state">>, <<"mailbox_sample">>, <<"ets_sample">>], lists:member(T, maps:get(allowed_tools, C))]), ?assertEqual([], maps:get(allowed_ets_tables, C)), ?assertEqual(mcp_policy:default_limits(), maps:get(limits, C)), - ?assertEqual(12, length(maps:get(allowed_tools, C))), + ?assertEqual(13, length(maps:get(allowed_tools, C))), %% a project without an mcp term uses the same built-in policy write_rebar(Config, "{erl_opts, [debug_info]}.\n{deps, []}.\n"), ?assertEqual({ok, C}, mcp_policy:resolve(input(Config, #{}))). diff --git a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl index 94cd64e6..b375648d 100644 --- a/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl +++ b/apps/erlangbridge/test/mcp/mcp_server_SUITE.erl @@ -37,6 +37,7 @@ all() -> prompt_guides_an_agent_and_checks_its_argument, changes_since_reports_replaced_and_removed_children, top_ports_reports_drivers_and_owners_only, + process_groups_aggregates_anonymous_processes_in_a_fixed_size_answer, ets_summary_aggregates_per_owner_without_names, ets_summary_counts_only_approved_tables, developer_tools_are_off_by_default, @@ -336,7 +337,7 @@ initialize_negotiates_and_lists_only_readonly_tools(Config) -> #{<<"result">> := #{<<"tools">> := Tools}} = rpc(Config, <<"tools/list">>, #{}), Names = lists:sort([maps:get(<<"name">>, T) || T <- Tools]), ?assertEqual(lists:sort(mcp_policy:default_tools()), Names), - ?assertEqual(12, length(Tools)), + ?assertEqual(13, length(Tools)), [begin ?assertMatch(#{<<"inputSchema">> := #{<<"type">> := <<"object">>, <<"additionalProperties">> := false}, <<"outputSchema">> := #{<<"type">> := <<"object">>}, @@ -1215,6 +1216,52 @@ top_ports_reports_drivers_and_owners_only(Config) -> [catch gen_tcp:close(X) || X <- [A, C, L]] end. +%% anonymous workers of one spawn site (the process_groups test) +idle() -> receive stop -> ok end. + +process_groups_aggregates_anonymous_processes_in_a_fixed_size_answer(Config) -> + Pids = [spawn(?MODULE, idle, []) || _ <- lists:seq(1, 400)], + try + S = ok_call(Config, <<"process_groups">>, #{<<"limit">> => 5}), + Groups = maps:get(<<"groups">>, S), + ?assertEqual(5, length(Groups)), + [Top | _] = Groups, + %% the 400 anonymous workers are one group, not 400 entries + ?assertMatch(#{<<"rank">> := 1, <<"initialCall">> := <<"mcp_server_SUITE:idle/0">>, + <<"module">> := <<"mcp_server_SUITE">>, <<"count">> := C, + <<"registered">> := 0, <<"unregistered">> := C} when C >= 400, Top), + ?assertEqual(3, length(maps:get(<<"samples">>, Top))), + %% the sample ids join with process_info + [Id | _] = maps:get(<<"samples">>, Top), + ?assertMatch(#{<<"entities">> := [_ | _]}, ok_call(Config, <<"process_info">>, #{<<"id">> => Id})), + Counts = [maps:get(<<"count">>, G) || G <- Groups], + ?assertEqual(lists:reverse(lists:sort(Counts)), Counts), + #{<<"processCount">> := PC, <<"scanned">> := Sc, <<"registered">> := Reg, <<"unregistered">> := Un, + <<"shownCoverPercent">> := Cover} = Totals = maps:get(<<"totals">>, S), + ?assert(PC >= 400 andalso Sc >= 400 andalso Reg + Un =:= Sc), + ?assert(Cover > 0 andalso Cover =< 100), + ?assertMatch(#{<<"groups">> := G, <<"groupsShown">> := 5} when G >= 5, Totals), + ?assertEqual(true, maps:get(<<"complete">>, S)), + %% OTP processes are grouped by their callback module; inspector processes are not listed + All = maps:get(<<"groups">>, ok_call(Config, <<"process_groups">>, #{<<"limit">> => 50})), + Calls = [maps:get(<<"initialCall">>, G) || G <- All], + [?assertEqual(nomatch, binary:match(C1, X)) || C1 <- Calls, X <- [<<"mcp_runtime:">>, <<"mcp_server:">>, <<"mcp_store:">>]], + ?assert(length(All) =< 50), + %% other criteria, and strict arguments + lists:foreach(fun({By, Field}) -> + R = ok_call(Config, <<"process_groups">>, #{<<"sortBy">> => By, <<"limit">> => 8}), + Vs = [maps:get(Field, G) || G <- maps:get(<<"groups">>, R)], + ?assertEqual(lists:reverse(lists:sort(Vs)), Vs) + end, [{<<"memory">>, <<"memoryBytes">>}, {<<"reductions">>, <<"reductions">>}, + {<<"message_queue_len">>, <<"messageQueueLen">>}]), + ?assertEqual(nomatch, re:run(iolist_to_binary(io_lib:format("~p", [S])), "SENTINEL")), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"process_groups">>, #{<<"limit">> => 51})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"process_groups">>, #{<<"sortBy">> => <<"links">>})), + ?assertMatch({rpc_error, -32602, _}, call(Config, <<"process_groups">>, #{<<"cursor">> => <<"x">>})) + after + [exit(P, kill) || P <- Pids] + end. + ets_summary_aggregates_per_owner_without_names(Config) -> Self = self(), Big = spawn(fun() -> diff --git a/mcp-tasks.md b/mcp-tasks.md index e3354f35..bc3fa05a 100644 --- a/mcp-tasks.md +++ b/mcp-tasks.md @@ -581,9 +581,9 @@ Next: T11 real-client test, T12/T14 gaps above (still open; deliberately not sch ### Post-V1 additions -Decided with the user after the V1 work order, all within the read-only/metadata-only guardrails (no new write, eval, mailbox, ETS-value or source access). The toolset is now twelve tools; the original seven are unchanged apart from the additive fields noted. +Decided with the user after the V1 work order, all within the read-only/metadata-only guardrails (no new write, eval, mailbox, ETS-value or source access). The toolset is now thirteen default tools (plus three opt-in developer-tier tools); the original seven are unchanged apart from the additive fields noted. -- `top_processes` (queue length / reductions / memory ranking), `top_ports` (queue size / traffic counters; driver name and owner only, never addresses or command lines), `ets_summary` (ETS memory per owner, no table names), `topology_overview` (one application in one collection; each part obeys its own tool's allowlist entry), `changes_since` (diff against a retained collection; baseline limited by `collection_ttl_ms` / `max_collections`). +- `top_processes` (queue length / reductions / memory ranking), `process_groups` (all processes aggregated by initial call, fixed-size answer), `top_ports` (queue size / traffic counters; driver name and owner only, never addresses or command lines), `ets_summary` (ETS memory per owner, no table names), `topology_overview` (one application in one collection; each part obeys its own tool's allowlist entry), `changes_since` (diff against a retained collection; baseline limited by `collection_ttl_ms` / `max_collections`). - Additive fields: `children` counts on supervisors (`supervisor:count_children`), `pausedProcesses` in `debug_session` and a `debugger` field in `process_info` (from `int:snapshot/0`; a supervisor that is itself stopped at a breakpoint is reported as `unavailable_while_paused`), run queue / VM resource usage / connected node names in `runtime_summary`, `format: "mermaid"` on graph tools. - New edge type `owns_port` (MAP-03 extension). New MCP prompt `map_application` (the only exception to "no prompts"; static text, argument-checked, grants nothing). - Developer tier (opt-in, only when named in the project's `allowed_tools`; returns a bounded, best-effort-redacted sample of application data, so it is outside the metadata-only V1 guardrail by design): `process_state` (`sys:get_state` of gen_server/gen_statem/gen_event, never a supervisor or a plain process), `mailbox_sample`, `ets_sample` (approved non-private tables). `allowed_ets_tables` may be `all`. Limits can be raised above their defaults up to fixed ceilings. See [mcp_security.md](./mcp_security.md). diff --git a/mcp_security.md b/mcp_security.md index 229bab5d..aac46c05 100644 --- a/mcp_security.md +++ b/mcp_security.md @@ -40,7 +40,7 @@ the sampled data goes to the agent. Redaction is best effort (below), not a guar | Per-session credential | 32 random bytes (`crypto:strong_rand_bytes/1`) per session; bearer token required on **every** request, checked (constant time) before the body is read; never accepted from the environment or workspace files; an explicit user-level `erlang.mcp.authToken` is the only opt-in override | | Host / Origin | `Host` must be the bound endpoint; an `Origin` is refused unless it is that endpoint; no CORS headers | | Transport limits | POST `/mcp` only, `application/json` only, body cap before reading, header/body/connection timeouts, bounded connections, no chunked bodies, no batches, no SSE | -| Read-only tools | Fixed tools; the project policy chooses among them; unknown tools are never turned into atoms or function names. Default set: 12 metadata tools. Developer tier: 3 more, only when named | +| Read-only tools | Fixed tools; the project policy chooses among them; unknown tools are never turned into atoms or function names. Default set: 13 metadata tools. Developer tier: 3 more, only when named | | No execution | No eval, `rpc:call`, shell, code loading, message sending, tracing, breakpoint changes or ETS writes. The only runtime calls that touch application processes are `sys:get_state/2` (developer tier, gen_server/gen_statem/gen_event only, never plain processes, never supervisors) and `process_info/2` | | Data minimisation (metadata tier) | No process dictionary, stack, mailbox contents, state, child start arguments, application environment, source, code paths, cookies, socket addresses, command lines; node host redacted by default; ETS: only tables approved by `allowed_ets_tables` (private tables never) | | Developer tier data | Bounded by depth, item count and binary size; values of keys named like a secret (`password`, `token`, `secret`, `api_key`, `cookie`, `auth`...) and credentials in URLs are replaced; supervisor state (child start arguments) is never returned; a mailbox above 20000 messages is not copied; ETS sampling only for approved, non-private tables | diff --git a/test/test-suite/mcp/mcp_debug_adapter.test.ts b/test/test-suite/mcp/mcp_debug_adapter.test.ts index fe7f6952..779f6bf9 100644 --- a/test/test-suite/mcp/mcp_debug_adapter.test.ts +++ b/test/test-suite/mcp/mcp_debug_adapter.test.ts @@ -190,7 +190,7 @@ suite('MCP: debug adapter end-to-end (real erl target)', function () { assert.strictEqual(started.status, 'started', JSON.stringify(started)); const token = JSON.parse(fs.readFileSync(started.descriptor, 'utf8')).token; const tools = (await post(started.url, token, { jsonrpc: '2.0', id: 1, method: 'tools/list' })).json.result.tools.map((t: any) => t.name); - assert.strictEqual(tools.length, 12, tools.join(',')); + assert.strictEqual(tools.length, 13, tools.join(',')); const ok = async (name: string, args: any = {}) => { const r = await call(started.url, token, name, args); assert.ok(r.json.result && r.json.result.isError === false, name + ': ' + JSON.stringify(r.json)); From 4543ffc7807a03211c41dd23238342ca87c4ebdb Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 22:21:40 +0200 Subject: [PATCH 18/20] Version 1.2.6 --- CHANGELOG.md | 5 ++++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0827350f..1344cccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change log -## Unreleased + +## Version 1.2.6 (October 1, 2026) * MCP topology inspector for debug sessions (opt-in) - New settings `erlang.mcp.enabled` (default `false`), `erlang.mcp.host` (loopback only) and `erlang.mcp.port` (`0` = any free port). When enabled, a debug session (launch or attach, not *Run without debugging*) starts a read-only, token-authenticated [MCP](https://modelcontextprotocol.io) server inside the debugged Erlang node, so that an AI agent can map its OTP topology: `runtime_summary`, `application_overview`, `supervision_tree`, `registered_processes`, `process_info`, `top_processes` (hottest processes by queue length, reductions or memory), `ets_tables` (approved tables, metadata only) and `debug_session`. Results are bounded, paginated, with typed edges, evidence, confidence and explicit coverage limitations @@ -23,6 +24,8 @@ - MCP prompt `map_application`: the sample prompt served by the inspector itself (argument-checked, grants nothing) - A ready-to-use prompt to map an application with an agent: [HELP.MD](./HELP.MD#mcp-topology-inspector-ai-agents-debug-sessions-only) (*Sample prompt*) +* Update erlang.plist to latest + --- ## Version 1.2.5 (September 27, 2026) diff --git a/package-lock.json b/package-lock.json index 16d5e4b1..0875b62c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "erlang", - "version": "1.2.5", + "version": "1.2.6", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6bcd0782..7cd0e143 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "erlang", "description": "Erlang language extension for Visual Studio Code", - "version": "1.2.5", + "version": "1.2.6", "icon": "images/icon.png", "publisher": "pgourlain", "engines": { From 8a70969afc848cc212f8aea06e7c11b64a8ffc71 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 22:28:21 +0200 Subject: [PATCH 19/20] remove unused file --- mcp-tasks.md | 635 --------------------------------------------------- 1 file changed, 635 deletions(-) delete mode 100644 mcp-tasks.md diff --git a/mcp-tasks.md b/mcp-tasks.md deleted file mode 100644 index bc3fa05a..00000000 --- a/mcp-tasks.md +++ /dev/null @@ -1,635 +0,0 @@ -# V1: Embedded MCP OTP Topology Inspector for Erlang Debug Sessions - -## Objective - -Add a feature to the extension's DAP debugger: when the Erlang extension/LSP setting `erlang.mcp.enabled` is explicitly enabled and an actual debug session starts, the target Erlang node starts an embedded Model Context Protocol (MCP) server. Configuration belongs to the existing Erlang settings layer; the DAP debugger owns the runtime lifecycle. Enabling the setting alone must not start a server in the LSP node. - -The V1 goal is to let an agent discover, reconstruct, and explain the OTP topology of a selected application without knowing supervisor or process names in advance. Map applications, supervisors, processes, modules, and approved ETS metadata, with explicit relationships, evidence, and coverage limitations. - -This is a structural map, not an observation of message exchanges or business workflows. Mailbox inspection is limited to queue length; ETS inspection is limited to metadata. No agent framework or model-specific runtime is required. - -This is not a general administration API and must never be active in a normal LSP session or in a non-debug application launch. - -## Expected Behavior - -- The DAP debugger starts or attaches to an Erlang node through the existing debug flow. -- The existing Erlang configuration layer resolves MCP settings for the debug session's workspace folder and passes a validated internal configuration to the debugger. Do not add a user-facing `mcp` block to launch/attach configuration. -- The MCP runtime inspector starts only when both conditions are true: - - The session is an actual debug session. - - The folder-resolved `erlang.mcp.enabled` setting is explicitly true. -- The MCP server runs inside the application/node being debugged, under a dedicated OTP supervisor. -- When the debug session ends, disconnects, or fails, the MCP server is stopped and its resources are released. -- If MCP configuration, startup, or client registration fails, disable/clean up the inspector, report a sanitized actionable error, and keep ordinary debugging usable. There is no `required` option in V1. -- Normal LSP, DAP, compilation, indexing, and debug behavior must remain unchanged when the feature is disabled. - -## Proposed Configuration - -### Erlang Extension/LSP Settings - -Use the existing VS Code `erlang` settings namespace, not a new configuration service. Resolve settings with the debug workspace folder URI, not a global cache or the first workspace folder in a multi-root workspace. - -```json -{ - "erlang.mcp.enabled": true, - "erlang.mcp.host": "127.0.0.1", - "erlang.mcp.port": 0 -} -``` - -`erlang.mcp.enabled` defaults to `false`. The example explicitly opts in. Changes apply to new sessions; changing the setting must not silently expand permissions or rebind an active inspector. Stopping the debug session revokes its credentials. - -### Project Policy in rebar.config - -Add an optional top-level extension-owned term to the target project's [rebar.config](rebar.config). This is metadata read by the extension's MCP integration, not a rebar plugin or an instruction for rebar to start a server. Builds/tests run outside the debugger must never activate MCP. - -```erlang -{mcp, [ - {allowed_tools, [ - <<"runtime_summary">>, - <<"application_overview">>, - <<"supervision_tree">>, - <<"process_info">>, - <<"registered_processes">>, - <<"ets_tables">>, - <<"debug_session">> - ]}, - {allowed_ets_tables, []}, - {limits, [ - {max_request_bytes, 16384}, - {max_result_bytes, 262144}, - {max_items, 500}, - {max_depth, 16}, - {max_binary_bytes, 4096}, - {max_traversal_depth, 8}, - {max_concurrency, 2}, - {request_timeout_ms, 3000}, - {collection_ttl_ms, 30000}, - {max_collection_bytes, 1048576}, - {max_collections, 2} - ]} -]}. -``` - -The policy example shows the defaults. For an approved named table, use a binary name such as `<<"orders_index">>` in `allowed_ets_tables`. Metadata access remains opt-in and excludes private tables. - -- Resolve the nearest target-project rebar configuration from the resolved debug `cwd`, within the selected trusted workspace folder. For an umbrella, use its enclosing project file when the application has none. Do not walk outside that folder, consult the extension's own build configuration instead, or fetch a remote target's files implicitly. -- If no project file or no `mcp` term exists, use the built-in policy. A malformed file or invalid/duplicate MCP keys disables MCP for that session with an actionable error; it does not abort ordinary debugging or fall back to a more permissive policy. -- V1 reads only the literal top-level term: no `rebar.config.script` execution, profile merging, includes, or evaluation. Detect an `mcp` policy placed only in a profile and report it as unsupported instead of silently ignoring it. -- Use a structured Erlang-term reader in a bounded helper outside the long-lived LSP/target VM, or a reviewed parser that does not intern arbitrary input as atoms. Existing `file:consult` usage is a starting point, not permission to parse client input or unbounded project files in the target. Apply Workspace Trust, input-size, parse-time, and output limits; return only normalized MCP policy, never other project terms. -- Normalize snake_case project keys into the internal camelCase contract once. Configuration ownership is disjoint: VS Code controls enablement/host/port; rebar controls tool/table allowlists and lower resource limits. Reject `enabled`, `required`, `host`, `port`, transport, and credentials inside the project term. Neither source can relax built-in security ceilings. -- Keep defaults and schemas in MCP-owned helpers. Projects without rebar remain usable with built-in defaults; do not create a project file automatically. - -### Validation and Defaults - -- `erlang.mcp.enabled` is the only user-facing activation switch. A user-authored launch/attach `mcp` block is unsupported and cannot override it. -- `required` is removed. All MCP failures are non-fatal to debugging, but must be visible and must leave no unauthenticated or partially initialized inspector running. -- V1 uses only `streamable_http`; do not expose an unused transport selector. -- `host` defaults to `127.0.0.1` and must only accept loopback addresses in this feature version. -- `port: 0` requests an ephemeral OS-assigned port and should be the default. -- Reject wildcard, LAN, container-wide, and public bindings such as `0.0.0.0` and `::`. -- Generate a new bearer token from at least 32 cryptographically random bytes per debug session. Do not accept an `authToken` configuration field or reuse an environment-provided token in V1. -- Internal `allowedTools` defaults to the seven tools in the rebar example; an explicit empty list enables none. -- Internal `allowedETSTables` defaults to an empty list. Accept exact local named-table names, resolved without creating atoms. Private tables remain excluded even if listed. -- The example's limits are V1 defaults and hard ceilings. Overrides must be positive integers no greater than the corresponding ceiling. Validate relationships between limits so error and result envelopes fit; reject unusable combinations rather than exceeding a limit. `maxItems` counts entities and relationships together per page, and ordinary list entries. `maxResultBytes` includes the serialized MCP response envelope. -- Validate all fields, ranges, tool names, and limits before starting the server. -- Unknown MCP configuration keys should produce a useful warning or validation error according to existing project conventions. -- Extend the existing Erlang settings schema and documentation; pass normalized configuration internally to the adapter without exposing duplicate user-facing debug options. - -## Scope for Version 1 - -### Read-Only Tools - -Implement exactly the seven tools below, with discoverable input/output schemas and descriptions. Do not expose prompts or resources in V1. *(Superseded after V1: five more tools, one prompt and one edge type were added deliberately; see "Post-V1 additions" under Implementation Progress. The text below keeps describing the original V1 contract.)* - -#### `application_overview` - -- Discover started OTP applications without a required application, supervisor, or PID argument. -- Return names, versions, declared dependencies, and discoverable root supervisors with reusable entity IDs. Declared dependencies are not evidence of runtime communication. -- Support application selection by discovered ID and pagination. Preserve applications with no discoverable root and explain unavailable metadata. -- Expose safe module names and declared OTP behaviours where available, without invoking application callbacks, loading code, returning compile paths, or reading application environment values. - -#### `runtime_summary` - -Return bounded runtime metadata: - -- Erlang/OTP and ERTS versions. -- Node name, with an option to redact it if it contains host information. -- Uptime. -- Scheduler count. -- Process count. -- Memory summary. -- Current debug session identifier, using a non-secret opaque value. - -#### `registered_processes` - -Return a bounded list of locally registered process names and safe summary metadata. - -Do not enumerate every process by default. Support application filtering and continuation; mark membership as confirmed, inferred, or unknown. Do not silently assign unrelated registered processes to the selected application. - -#### `process_info` - -Accept a process entity ID, registered name, or validated PID belonging to the authorized local debug node. Return only an explicit allowlist of fields: - -- Registered name. -- Status. -- Current function. -- Initial call. -- Reductions. -- Memory. -- Message queue length. -- Links and monitors as bounded identifiers. -- Application membership with evidence and confidence. -- Callback module and OTP behaviour when available from safe metadata, otherwise an explicit unknown value. Do not read process dictionaries, call `sys:get_state`, or execute application callbacks to infer them. - -Do not return process dictionaries, heap fragments, stack contents, full mailbox messages, or arbitrary process state in version 1. - -#### `supervision_tree` - -Traverse from an application or supervisor entity ID discovered by `application_overview`, or an explicitly supplied local supervisor. Use OTP supervisor APIs where possible. Bound depth, items, bytes, and execution time; detect loops and stale PIDs. - -Return typed parent-child edges, safe child identifiers, worker/supervisor kind, module metadata, and available restart/shutdown metadata. Never return child start arguments. Preserve restarting or absent child entries without inventing live PIDs. - -Support progressive subtree expansion and continuation. An unresponsive supervisor produces a partial result, not a blocked map. Use `timeout` unless debugger evidence specifically establishes `unavailable_while_paused`; never resume or alter application processes to inspect them. - -#### `ets_tables` - -Return metadata only for named tables explicitly approved in `allowedETSTables`: name, protection, type, owner entity ID, size, and memory with its unit. Check approved names directly instead of dumping a node-wide table inventory. Exclude private tables even if listed. - -Return ownership edges joinable to process entities. Never read keys, objects, or values; `ets_lookup` is deferred beyond V1. - -#### `debug_session` - -Return safe debugger metadata such as launch versus attach, node connection status, interpreted modules, and active breakpoint locations if already available from the existing debug subsystem. - -Do not expose source contents, environment values, command-line secrets, cookies, or credentials. - -### Mapping Contract - -Mapping tools share a versioned structured contract. Prose may supplement it but cannot replace it. - -| ID | Requirement | -| --- | --- | -| MAP-01 | Discover applications and available roots without preconfigured process names. Distinguish the selected application, declared dependencies, and other node entities. | -| MAP-02 | Entity kinds are `application`, `process`, `module`, and `ets_table`. A supervisor is a process with role `supervisor`, not a duplicate entity. Opaque IDs are reusable across tools and collections in the live session, invalid across sessions/node incarnations, and different after process restart or table recreation. Bound identity bookkeeping and explicitly expire handles when identity cannot be retained safely. | -| MAP-03 | Typed edges are `depends_on`, `supervises`, `owns_table`, `linked_to`, `monitors`, `belongs_to`, and `uses_module`. Include evidence, observation time, and confidence (`confirmed`, `inferred`, `unknown`). Links/monitors do not imply supervision or message traffic. `uses_module` reflects callback/child metadata, not observed calls. | -| MAP-04 | Coverage is relative to the requested scope, not all runtime processes. Report limitations for unsupervised, unregistered, transient, and unattributed processes. A name or link alone cannot confirm application membership. | -| MAP-05 | Include `schemaVersion`, `sessionId`, `collectionId`, `startedAt`, `finishedAt`, `scope`, `entities`, `relationships`, `complete`, `truncated`, `omissions`, and `nextCursor`. Each call/subtree expansion identifies its collection. A collection is a bounded observation interval, never an atomic snapshot. | -| MAP-06 | Opaque cursors are bound to session, collection, tool, and arguments. Serve pages from bounded retained observations, not moving live offsets. Return `cursor_expired` after retention expires; never silently restart traversal. Explain non-resumable omissions. | -| MAP-07 | Expose safe module/behaviour names and available debugger locations for correlation through separate workspace/LSP tools. Explicitly report unavailable metadata. Do not load modules or return source contents through MCP. | - -`omissions` distinguishes `policy_denied`, `disappeared`, `timeout`, `unavailable_while_paused`, `limit_reached`, and `unknown_membership` without disclosing forbidden identifiers. Edge endpoints outside the current page need safe references or an unresolved status, never invented entities. - -Apply collection TTL, count, and retained-byte budgets per session. When acquisition exceeds a budget, return a partial map with a non-resumable reason and suggest a narrower scope. Pagination bounds output, not necessarily the cost of OTP APIs that materialize lists; review that cost explicitly. - -Agents may compare collections using entity IDs, but absence from a partial collection is not proof of termination. V1 needs no historical database or server-side diff tool. - -### Agent Exploration Workflow - -1. Discover tool schemas; call `runtime_summary` and `application_overview`. -2. Select an application, follow its root IDs, and expand `supervision_tree` progressively. -3. Enrich selected processes with `process_info`, registered names, approved ETS ownership, and debugger metadata. -4. Join IDs and typed edges; separate confirmed relationships from inferences and unresolved references. -5. Correlate modules through separately authorized workspace/LSP access when available. Topology reconstruction must work without source access. -6. Explain the topology, observation interval, incomplete regions, and uncertainty. Do not infer business flows or actual message traffic from structural edges. - -### Explicitly Out of Scope for Version 1 - -Do not implement any of the following: - -- Arbitrary Erlang expression evaluation. -- `rpc:call` with client-controlled module, function, or arguments. -- Shell command execution. -- Dynamically loading or purging code. -- Sending arbitrary messages to processes. -- Changing `sys` state. -- Killing, suspending, or restarting processes. -- Setting or removing breakpoints through MCP. -- Writing to ETS. -- Reading ETS keys or values, including exact-key lookups. -- Dumping full ETS tables. -- Tracing messages, function calls, or business flows. -- Traversing distributed topology on other nodes. -- Returning full process mailboxes. -- Returning complete `sys:get_state/1` values. -- Accessing arbitrary files, source files, environment variables, application secrets, Erlang cookies, or OS credentials. -- Proxying arbitrary LSP or DAP requests. - -If existing helper APIs make these actions easy, they must still not be exposed. - -## Architecture - -Implement the feature using OTP conventions and the repository's existing application structure. - -The MCP implementation is entirely Erlang: HTTP transport, protocol handling, authentication, tool schemas and dispatch, project-policy parsing/validation, runtime inspection, encoding, limits, and supervision. Do not add a production TypeScript MCP SDK, server, policy engine, or duplicate implementation. TypeScript changes are limited to existing VS Code settings, debugger bootstrap/event forwarding, and client-registration/UI integration; they do not process MCP tool requests. An external MCP client used only for interoperability tests is not part of the server implementation. - -### File Isolation and Naming - -- Put all new Erlang MCP production files under `apps/erlangbridge/src/mcp/`, with filenames and module names starting with `mcp_`: `mcp_sup.erl`, `mcp_server.erl`, `mcp_policy.erl`, `mcp_runtime.erl`, `mcp_encoder.erl`, and `mcp_audit.erl` as needed. Subdirectories do not namespace Erlang modules; verify name collisions, including dependencies. -- Do not create a dedicated TypeScript MCP directory or helper subsystem. Keep thin VS Code integration hooks in the existing settings, extension, and debugger files; place MCP implementation helpers in Erlang under `apps/erlangbridge/src/mcp/`. -- New MCP tests, fixtures, schemas, and auxiliary files must also use the `mcp_` prefix and live in dedicated MCP subfolders under the existing appropriate test/source directories. A mandatory framework basename is an exception only when documented. Keep test code out of production `src`. -- Existing integration files retain their names and contain only necessary hooks. Do not rename shared files such as `package.json`, `rebar.config`, or existing settings/debugger modules. Keep documentation in existing docs; new MCP-only documentation uses the same prefix. -- Verify rebar's recursive source discovery for the supported toolchain, target-side compilation from `src/mcp`, beam/dependency packaging, and launch/attach loading explicitly. Do not assume relocating a file makes the debugger compile/load it. Never load MCP automatically through the LSP startup module list. -- Prefix rules apply to project-owned additions, not upstream dependency files; do not rename or vendor dependencies merely to satisfy naming. - -Suggested components (combine helpers where appropriate): - -- `mcp_sup`: dedicated supervisor, started only for an enabled debug session. -- `mcp_server`: transport lifecycle and MCP request dispatch. -- `mcp_policy`: tool allowlist, authorization, validation, redaction, and limits. -- `mcp_runtime`: safe wrappers around Erlang runtime and OTP introspection. -- `mcp_encoder`: bounded Erlang-term to MCP-content conversion. -- `mcp_audit`: structured security and lifecycle events without sensitive payloads. - -Use names matching the existing codebase. - -The implementation must: - -- Keep the MCP supervision subtree isolated from the LSP and debugger core. -- Avoid blocking DAP or LSP handlers. -- Enforce request timeouts. -- Run inspection in monitored, bounded workers. Stop inspector-owned workers after timeout and discard late replies; ignoring results alone is insufficient. Never kill application processes. Document non-interruptible runtime operations and their residual resource risk. -- Enforce the shared mapping contract and bounded collection/identity retention. -- Bound concurrency and reject excess work with a clear MCP error. -- Handle dead PIDs, disappearing ETS tables, supervisor restarts, node disconnects, and malformed terms. -- Avoid atom creation from any client-controlled string. -- Avoid unsafe term decoding, especially `binary_to_term/1` on untrusted input. -- Avoid logging authorization tokens or returned runtime contents. -- Avoid converting arbitrary binaries to JSON strings without validation and limits. -- Expose MCP protocol errors without leaking Erlang stack traces or internal paths. - -## Transport and Endpoint Discovery - -Use a transport supported by the MCP library already present in the repository. If none exists, select a maintained Erlang-compatible implementation only after reviewing its license, dependency footprint, protocol support, and security posture. Avoid adding a custom partial MCP implementation unless repository constraints require it. - -For a local HTTP-based transport: - -- Bind only to an explicit loopback address. -- Default to an ephemeral port. -- Require the freshly generated per-session bearer token on every request. -- Compare tokens in a timing-resistant manner where practical. -- Validate `Origin` and `Host` for HTTP requests. -- Do not enable CORS. -- Do not provide a browser-oriented administrative UI. -- Cap request body size. -- Reject unsupported content types and methods. -- Apply connection, request, and idle timeouts. - -Publish endpoint metadata through the DAP event mechanism. Deliver credentials only through an explicitly secret-handling debugger/client integration, never ordinary output events. Define this integration before implementation. Sanitize existing verbose launch/attach argument logging and protocol tracing as well as new logs. Never write tokens into normal logs, telemetry, crash reports, workspace files, or source control. - -If the client requires a connection descriptor, create it with restrictive filesystem permissions, remove it at session end, and store it only in an OS-appropriate temporary location. Prefer passing secret material directly through the debugger integration rather than writing it to disk. - -### Connecting an AI MCP Client - -`port: 0` means the OS assigns the port when binding. The adapter must receive the actual bound address, port, MCP path, and session identity after readiness; never publish port zero as a usable endpoint. A URL in a debug log alone is not a complete client integration. - -1. For clients supporting dynamic server registration, the extension registers the live endpoint and session credential through the verified client API, then unregisters it on teardown. The user selects the debug session's server rather than guessing a port. Verify the VS Code MCP provider API and minimum VS Code version in T02; do not assume every client supports it. -2. Provide an `Erlang MCP: Show Connection Details` command through a thin hook in the existing extension integration, displaying connection metadata supplied by Erlang for live sessions. In multi-session workspaces, require session selection. Show the real URL and supported client setup route without revealing tokens in ordinary output, telemetry, or clipboard automatically. -3. For static clients, users can set `erlang.mcp.port` to a fixed local port and configure that URL. They must still supply a fresh per-session token using a supported secure credential input/export flow. A fixed port does not make the URL alone sufficient, and an old token must not reconnect after restart. -4. If a fixed port is occupied, disable MCP and report the conflict while debugging continues. Do not silently choose a different port for a statically configured client. Concurrent sessions need separate ports or dynamic registration. - -T02 must name at least one actually supported client and specify its complete endpoint-plus-credential setup, including any required user action and secure fallback. If no secure setup route exists, report a blocker rather than claiming automatic discovery or disabling authentication. Do not introduce an always-on proxy solely to hide ephemeral ports. - -## Security Model and Threat Analysis - -Localhost-only binding reduces network exposure, but it is not an authorization boundary. Other processes running as the same user, compromised development tools, browser-origin attacks against local services, malicious MCP clients, and accidentally forwarded ports may still reach the server. - -Treat all MCP requests, parameters, and client-supplied identifiers as untrusted. - -Required controls: - -- **Secure default:** disabled unless explicitly enabled for a debug session. -- **Loopback enforcement:** reject every non-loopback bind address, including values obtained after hostname resolution. -- **Per-session authentication:** use a fresh, high-entropy token and invalidate it when the debug session ends. -- **Least privilege:** expose only the configured read-only tool allowlist. -- **No arbitrary execution:** no eval, RPC trampoline, shell, module loading, or client-controlled function calls. -- **Data minimization:** return summaries rather than raw state and redact likely secrets. -- **Resource limits:** cap request size, response size, item count, nesting depth, binary size, traversal depth, concurrency, and execution time. -- **Safe identifiers:** resolve registered names through existing atoms only. Never call `list_to_atom/1`, `binary_to_atom/1`, or equivalent on input. -- **Safe serialization:** handle PIDs, ports, references, functions, improper lists, maps with non-string keys, large binaries, and cyclic-looking graph traversal without crashing or producing unbounded output. -- **No secret logging:** log only lifecycle, tool name, result status, duration, and bounded metadata. Do not log parameters or results by default. -- **Session isolation:** bind tokens, IDs, cursors, and workers to one session and target node. V1 authorizes node-level metadata inspection; selecting an application is a navigation filter, not a security boundary between applications in the same VM. Never traverse remote PIDs. Concurrent sessions must not share inspector state or credentials. -- **Clean shutdown:** stop listeners, invalidate tokens, cancel workers, and release retained data on controlled termination. A target-side session-owner monitor and renewable lease must stop inspection within 10 seconds without renewal while the VM is schedulable. Renewal must not depend on application processes stopped at breakpoints. Never terminate an attached application merely to stop its inspector. Remove temporary descriptors normally and clean stale descriptors on the next extension startup; immediate file deletion after an OS crash is not guaranteed. -- **No telemetry payloads:** runtime inspection results and authentication material must not enter telemetry. -- **Dependency review:** pin and review any new MCP or HTTP dependency according to project policy. - -Document residual risk: application names, child identifiers, module names, and table names can reveal sensitive information. Metadata-only access does not guarantee zero overhead, atomic observations, or isolation from other code in the same VM. The exclusions and allowlists reduce risk but do not remove it completely. - -## Execution Instructions for Luna or Sonnet - -This is an implementation work order, not a claim that the feature exists. Execute the tasks below in dependency order. Keep the specification in English to match the repository; report progress in the user's language. - -- Read repository instructions and inspect the current working tree before editing. Preserve unrelated changes. Do not commit or create branches unless requested. -- Work on one task at a time. Inspect the owning code, state the expected behavior, add the smallest useful change, and immediately run a focused check. Keep new modules small, but do not create one module per checkbox mechanically. -- Each task has dependencies, starting surfaces, work, and a completion gate. Starting surfaces are navigation hints, not permission to edit all listed files. Verify actual APIs and ownership before choosing new module names. -- Reuse existing test helpers and suites. Add a fixture or suite only where no suitable home exists. Do not refactor vendored JSON/formatting code. -- Record baseline failures separately. Never mark a task complete on an unexecuted check, a mock-only substitute for a required integration test, or an unsupported assumption. -- Update the checkboxes and append a short evidence entry under Implementation Progress after each completed task: changed files, exact commands, results, and remaining risks. Leave blocked tasks unchecked and identify the decision needed. -- Stop for a decision if no maintained transport supports the required OTP range, secure client credential delivery is unavailable, or the current attach topology cannot satisfy local-only access. Do not silently raise the OTP floor, forward a port, implement partial MCP, or weaken authorization. -- Do not delegate, broaden V1, add cloud services, or introduce an agent framework unless separately authorized. All task checks are deterministic; no paid model invocation is required. - -## Implementation Tasks - -### T01: Verify Integration Points and Baseline - -Dependencies: none. - -Starting surfaces: [lib/erlangDebugSession.ts](lib/erlangDebugSession.ts), [lib/ErlangShellDebugger.ts](lib/ErlangShellDebugger.ts), [lib/erlangConnection.ts](lib/erlangConnection.ts), [lib/ErlangConfigurationProvider.ts](lib/ErlangConfigurationProvider.ts), [package.json](package.json), [rebar.config](rebar.config), existing Erlang bridge and test directories. - -- [x] Trace launch, attach, `noDebug`, configuration validation, target readiness, disconnect, target loss, and adapter shutdown. Identify which process owns each resource. -- [x] Inspect target bridge compilation/loading, including its separation from the LSP beam directory and discovery of `src/mcp/mcp_*.erl`. Verify all new owned MCP files follow the isolation/naming rules. Inspect logging, tracing, telemetry if present, and all paths that serialize debug arguments. -- [x] Record supported/tested OTP, Node, and VS Code versions from configuration, docs, and CI. Distinguish claimed compatibility from tested compatibility. -- [x] Run the current TypeScript compile and relevant existing debugger/bridge tests; record pre-existing failures and environment prerequisites. - -Completion gate: append a concise verified integration note to this document with symbols, paths, lifecycle diagram or sequence, baseline commands, and version constraints. Do not invent an MCP dependency or a secret-delivery API. - -### T02: Resolve Transport and Client Integration - -Dependencies: T01. - -Starting surfaces: dependency manifests, existing Node/Erlang HTTP bridge, debugger adapter factories, VS Code extension activation. - -- [ ] Review available maintained MCP/HTTP libraries for license, pinned version, transitive footprint, OTP support, protocol revision, Streamable HTTP behavior, cancellation, authentication hooks, and known security issues. -- [ ] Select one supported MCP protocol revision and real client fixture. Document initialization, notifications, version negotiation, content types, optional streaming behavior, and error handling; do not invent protocol extensions for overload/errors. -- [x] Specify endpoint and credential delivery to an actual supported MCP client using verified client/VS Code APIs: dynamic registration of the bound endpoint, the connection-details command, session selection/unregistration, and fixed-port secure-token setup. Separate non-secret discovery from secret transfer, including DAP tracing redaction and descriptor fallback permissions if needed. -- [ ] Determine where the HTTP client, adapter, and target run in launch and attach. V1 requires the client to reach target loopback without forwarding. Reject unsupported remote/container arrangements with a clear error; preserve ordinary debugging when MCP is optional. -- [x] Document target-side dependency loading and cleanup for launch/attach, protocol session IDs versus authorization tokens, and limits for HTTP connections and protocol sessions. - -Completion gate: the integration note identifies a feasible dependency/client/OTP combination, secret path, and supported deployment topology. Resolve blockers with the user before adding production dependencies. - -### T03: Define Schemas and Mapping Semantics - -Dependencies: T01, T02. - -Starting surfaces: existing configuration/schema conventions and new MCP policy/contract code chosen after T01. - -- [x] Define strict input/output schemas for the seven tools, graph entities/edges, result envelope, pagination, omissions, and sanitized errors. Reject extra tool arguments and unknown tool names. -- [x] Define edge direction, scalar representations, timestamp units, memory units, membership evidence, and absent/restarting supervisor children. Specify the difference between `complete`, `truncated`, and an empty authorized result. -- [x] Define ID identity/expiry rules, collection/cursor retention, bounded identity bookkeeping, and response-size accounting including MCP framing. Use table identity, not its reusable name alone, to detect recreation. -- [x] Specify safe root and callback discovery for supported OTP versions, including explicit unsupported/unknown results. Do not use links as proof of supervision or group leader alone as proof of membership. -- [x] Create minimal contract examples and positive/negative schema tests using existing test infrastructure. - -Completion gate: all MAP-01 through MAP-07 requirements map to schema fields and at least one planned assertion; example results validate and invalid inputs fail deterministically. - -### T04: Integrate LSP Settings and rebar Policy - -Dependencies: T03. - -Starting surfaces: [package.json](package.json), [lib/ErlangConfigurationProvider.ts](lib/ErlangConfigurationProvider.ts), [lib/erlangSettings.ts](lib/erlangSettings.ts), [lib/ErlangShellDebugger.ts](lib/ErlangShellDebugger.ts), existing configuration/debugger tests. New MCP implementation helpers belong only in `apps/erlangbridge/src/mcp/mcp_*.erl`; TypeScript edits stay in existing integration files. - -- [x] Add `erlang.mcp.enabled`, `erlang.mcp.host`, and `erlang.mcp.port` to Erlang settings and folder-aware resolution. Carry validated internal configuration through the provider to DAP without adding a public launch/attach `mcp` block or `required` option. Share defaults/normalization. -- [x] Implement the trusted, bounded, non-evaluating project-term reader and nearest-project lookup in Erlang. TypeScript supplies folder/cwd context and Workspace Trust status, not parsed project policy. Normalize `allowed_tools`, `allowed_ets_tables`, and `limits` in Erlang; enforce disjoint source ownership, missing-file defaults, and explicit errors for malformed/duplicate/unsupported policies. -- [x] Validate booleans, port integers from 0 to 65535, literal/resolved loopback addresses, exact allowlists, unique tool names, limits, and unknown fields. Reject caller-provided credentials, legacy `required`, project activation/bind overrides, and debug-block attempts to bypass the setting. -- [x] Enforce the debug gate independently of JSON schema: no MCP startup for normal LSP, ordinary application launches, or `noDebug: true`. -- [x] Treat all MCP configuration/startup failures as non-fatal to debugging but fatal to that inspector startup. Never silently fall back to an open listener or broaden policy after validation failure. -- [x] Test defaults, empty allowlists, invalid ranges/types, IPv4/IPv6 loopback, wildcard/LAN/public addresses, unsupported topology, and disabled paths. Cover distinct multi-root settings, plain/umbrella projects, no rebar file, malformed/duplicate terms, profile-only policies, script non-execution, trust/path boundaries, parser resource limits, and new-session setting changes. - -Completion gate: focused Erlang policy/configuration tests, TypeScript integration tests, and compile checks pass; absent/disabled MCP preserves existing arguments and behavior. Server-side validation is authoritative in Erlang; VS Code schema checks do not replace it. - -### T05: Build Deterministic OTP Mapping Fixtures - -Dependencies: T03. - -Starting surfaces: [apps/erlangbridge/test](apps/erlangbridge/test), [test/test-fixtures](test/test-fixtures), [test/test-suite](test/test-suite). - -- [x] Reuse or add a small OTP fixture with nested supervisors, workers with callback modules, registered/unregistered processes, a linked but unsupervised process, monitors, and an application with no discoverable root. -- [x] Include approved/unapproved/private named ETS tables and an owner process. Store recognizable test-only sentinels in forbidden data to detect accidental leakage. -- [x] Add deterministic controls for a restarting child, disappearing process, deleted/recreated table, blocked supervisor, and enough children for pagination. These controls belong to tests, never MCP tools. -- [x] Define the expected graph independently of inspector output. Provide reliable teardown of nodes, ports, tables, and temporary files even after assertion failures. - -Completion gate: the fixture starts/stops repeatedly without leaks and supplies an independent expected topology for later assertions. - -### T06: Implement Safe Encoding and Identifiers - -Dependencies: T03, T05. - -Starting surfaces: existing vendored JSON encoder API and new bounded MCP encoder/policy helpers. - -- [x] Encode atoms, tuples, PIDs, ports, references, maps with non-string keys, improper lists, binaries, and unsupported terms with explicit representations. Do not encode executable functions or opaque terms as recoverable execution handles. -- [x] Enforce traversal/encoding depth, item, binary, and full-response byte budgets before unbounded conversion; account for JSON escaping and base64 expansion. -- [x] Resolve names through existing atoms only and validate local PIDs/IDs. Never use untrusted term decoding or client-controlled MFA dispatch. -- [x] Implement bounded session/node-scoped identities and stale-handle handling. Same observed entity must join across tools; process restart/table recreation must not reuse the prior ID. -- [x] Test invalid Unicode, huge/nested terms, non-string map keys, repeated client identifiers, stale/remote PIDs, and cross-session rejection. Warm up runtime dependencies before atom-count assertions. - -Completion gate: focused Erlang tests show bounded output, meaningful truncation, no input-driven atom growth, and correct identity reuse/expiry. - -### T07: Implement Collections, Workers, and Continuations - -Dependencies: T06. - -Starting surfaces: new inspector supervision subtree, collection state, and policy facade. - -- [x] Implement per-session collection TTL/count/byte budgets and identity accounting, with bounded eviction and explicit expiry errors. -- [x] Bind cursors to collection, tool, arguments, and session. Page retained observations deterministically; return narrower-scope guidance when acquisition was incomplete. -- [x] Run collection work in monitored workers with bounded concurrency and no unbounded queue. Enforce deadlines, terminate only inspector workers, and ignore late replies safely. -- [x] Audit runtime calls that can allocate whole lists or continue executing after a caller times out. Define protective limits and document remaining non-interruptible cost rather than claiming strict preemption. -- [x] Test overload, cancellation, timeout cleanup, pagination integrity, retention expiry, memory-budget rejection, and cross-session cursor isolation. - -Completion gate: workers and retained state return to baseline after timeout/expiry/shutdown; partial collections cannot masquerade as complete snapshots. - -### T08: Implement Runtime and Application Discovery - -Dependencies: T05, T07. - -Starting surfaces: new runtime facade and existing safe OTP/runtime helpers. - -- [x] Implement `runtime_summary` with bounded metadata, units, and non-secret session identity; omit/redact node host information by default. -- [x] Implement `application_overview` using safe OTP metadata, without invoking application callbacks. Discover applications, declared dependencies, available roots, modules, and behaviours. -- [x] Distinguish unsupported root discovery from an application with no supervisor. Exclude inspector internals from the selected application's topology and identify them explicitly if shown in node scope. -- [x] Implement graph envelopes, application selection, and paginated inventory. No process name should be required to begin exploration. -- [x] Verify MAP-01 and MAP-07 against the fixture, including absent module metadata and dependencies not currently running. - -Completion gate: a client can discover the fixture application and usable root IDs without preconfigured supervisor names or source access. - -### T09: Implement Supervision Mapping - -Dependencies: T08. - -Starting surfaces: runtime facade, worker deadlines, mapping fixture. - -- [x] Implement `supervision_tree` from discovered application/root IDs, using OTP supervisor APIs with worker deadlines. -- [x] Emit direct parent-child edges, child kind, safe identifier, available module/restart/shutdown metadata, and absent/restarting-child state; omit start arguments. -- [x] Bound traversal, detect loops/repeated nodes, support subtree expansion, and produce continuations or explicit non-resumable omissions. -- [x] Return partial results for blocked or stopped supervisors without resuming them. Use a paused-specific reason only when debugger evidence supports it. -- [x] Test exact expected edges, nested trees, dynamic restarts, stale roots, blocked branches, depth limits, and linked-but-unsupervised processes. - -Completion gate: MAP-02 through MAP-06 pass for supervision mapping; no link or monitor becomes a supervision edge. - -### T10: Implement Process, ETS, and Debugger Metadata - -Dependencies: T09. - -Starting surfaces: runtime facade, existing debugger metadata, configuration allowlists. - -- [x] Implement `registered_processes` and `process_info` with safe fields, application filtering, membership evidence, local-ID validation, and bounded links/monitors. -- [x] Add callback/behaviour correlation only from approved metadata; report unknown membership for unattributed processes instead of guessing. -- [x] Implement `ets_tables` by checking approved named tables only, excluding private tables and returning owner IDs/edges with stable table identity. Never call an ETS object/key read API for this tool. -- [x] Implement `debug_session` from available debugger state through a narrow sanitized metadata channel, not a generic DAP proxy. If authoritative data is unavailable inside the target, return an explicit unavailable status. -- [x] Test safe field allowlists, unknown membership, disappearing processes/tables, owner correlation, metadata freshness, and absence of sentinel secrets. - -Completion gate: all seven tools return contract-valid bounded data through the same policy facade; no `ets_lookup` or generic execution path exists. - -### T11: Implement Authenticated MCP Transport - -Dependencies: T02, T04, T10. - -Starting surfaces: reviewed dependencies, MCP supervisor/server/policy, real client fixture. - -- [x] Add pinned reviewed dependencies and load the dedicated `mcp_sup` supervisor only for a debug session enabled by Erlang settings. Keep it out of the normal LSP supervision tree; verify prefixed modules in `src/mcp` compile and load without dependency name collisions. -- [ ] Implement initialization/version negotiation, advertised capabilities, tool listing, schema validation, invocation, structured results, supported cancellation, and protocol-compliant errors through the selected library. -- [x] Bind explicit loopback only, allocate ephemeral ports, require authorization for every HTTP request, validate `Host` and `Origin`, and disable CORS. Document safe handling of absent `Origin` for non-browser clients. -- [x] Enforce supported methods/content types, request-body limits, connection/session limits, header/body/idle timeouts, and overload rejection before expensive parsing/inspection. Never reflect secrets or stack traces. -- [ ] Test real client negotiation/invocation plus missing/invalid tokens, disallowed tools, malformed JSON/arguments, hostile hosts/origins, oversized bodies, unsupported versions, and resource exhaustion. - -Completion gate: a real MCP client invokes the seven tools only when authorized, and protocol tests exercise the pinned revision without custom partial implementations. - -### T12: Integrate Launch, Attach, and Cleanup - -Dependencies: T11. - -Starting surfaces: [lib/erlangDebugSession.ts](lib/erlangDebugSession.ts), [lib/ErlangShellDebugger.ts](lib/ErlangShellDebugger.ts), [lib/erlangConnection.ts](lib/erlangConnection.ts), target Erlang debugger bridge. - -- [x] Compile/package/load the inspector and approved dependencies into the actual target node using the verified debugger bridge path, not the LSP node. Loading trusted inspector code is debugger bootstrap, never an MCP capability. -- [x] Start only after target readiness; generate fresh credentials and bind the endpoint in Erlang. Register the actual endpoint using the thin T02 client integration. Implement the connection-details command in the existing extension code, using Erlang-provided metadata, and reject fixed-port conflicts without rebinding. Do not expose tokens in process arguments or ordinary DAP output. -- [x] Implement target-side owner monitoring/lease renewal, token invalidation, worker/listener shutdown, retained-data cleanup, and descriptor cleanup where applicable. -- [ ] Cover terminate, disconnect, failed launch/attach, adapter crash, target loss, inspector supervisor failure, and two concurrent sessions. Disabled sessions must not start listeners, timers, or workers. -- [x] Preserve an attached target on detach or any MCP failure. Roll back partial inspector/client-registration resources and report the failure while launch/attach debugging continues. No MCP error may kill an externally owned node. -- [ ] Test launch/attach and all supported adapter modes, application breakpoints, lease expiry, dynamic endpoint registration/removal, static-port conflicts, multi-session selection, and non-fatal MCP failure behavior. - -Completion gate: lifecycle tests prove cleanup within the documented schedulable-VM lease bound and no change to disabled debugging or attached-node ownership. - -### T13: Audit Security and Regression Boundaries - -Dependencies: T12. - -Starting surfaces: all touched entry points, existing logging/tracing, negative tests. - -- [x] Audit that every transport route and runtime operation goes through authorization, allowlists, identifier validation, and limits. Unknown tool names must not become Erlang atoms or function names. -- [ ] Add log/telemetry capture assertions for launch, attach, successful calls, rejections, exceptions, and shutdown. Use sentinel credentials and forbidden runtime data; sanitize existing argument serialization before it can contain secrets. -- [x] Test absent/expired/cross-session credentials and cursors, PID/node escape attempts, hostile table names, repeated unique strings, and no eval/RPC/shell/file/environment/cookie/state/mailbox access. -- [x] Verify no trace, ETS value lookup, mutable debugger operation, or source-content endpoint is advertised or callable. -- [x] Run existing LSP/DAP regression checks with MCP disabled and validate normal LSP startup has no inspector subtree/listener. - -Completion gate: no unexplained security-test failures; residual risks and non-guarantees are explicitly documented, not hidden by skipped assertions. - -### T14: Prove Agent-Oriented End-to-End Mapping - -Dependencies: T13. - -Starting surfaces: real MCP client fixture, OTP graph fixture, debugger integration tests. - -- [ ] Launch a debug target and initialize a real client using the actual endpoint/credential discovery integration, not a test-only secret shortcut. -- [ ] Discover the application without known process names, expand roots across multiple pages, join process and approved ETS-owner IDs, and compare reconstructed edges to the independent expected graph. -- [ ] Include a blocked branch, unknown membership, missing source metadata, and a linked-but-unsupervised process. Assert explicit incompleteness and no fabricated relationships. -- [ ] Collect before/after a deterministic restart and verify identity changes without treating absence from a partial collection as termination. -- [ ] Terminate/detach and prove endpoint closure, removal from client discovery, invalid credentials/cursors, cleared retained state, and survival of an externally owned attached target. Repeat the applicable workflow for attach, port zero, and a configured fixed port. - -Completion gate: deterministic end-to-end tests prove the documented agent workflow without an LLM, preconfigured supervisor names, or source access. - -### T15: Document, Package, and Hand Off - -Dependencies: T14. - -Starting surfaces: [README.md](README.md), [HELP.MD](HELP.MD), [DevelopersReadme.md](DevelopersReadme.md), [CHANGELOG.md](CHANGELOG.md), existing packaging/CI configuration. - -- [x] Document supported OTP/client/topology combinations, folder-scoped Erlang enablement, the optional rebar policy term and precedence, no `required` option, dynamic and fixed-port endpoint/secret setup, seven tool schemas, mapping workflow, example partial graph, units, cursors, and breakpoint behavior in the most suitable existing docs. -- [x] Document node-level authorization versus application filters, exclusions, metadata leakage, local-process threats, no port forwarding, lease assumptions, overhead, and cleanup limitations. Avoid copying credentials into examples. -- [x] Add the changelog entry and ensure prefixed `src/mcp` modules and target dependencies ship in the extension artifact. Assert new project-owned MCP filenames/locations follow the isolation convention, including tests and helpers. Verify startup from a packaged artifact, not only the source tree; do not publish it. -- [x] Integrate deterministic new checks into existing CI conventions. Run supported compatibility checks available in the environment and explicitly report unavailable platforms/OTP versions. -- [ ] Run final compile, Erlang tests, extension tests, and packaging checks. Resolve regressions introduced by this feature; keep baseline failures separate. -- [ ] Produce a final handoff listing changed files, lifecycle/security decisions, exact checks/results, compatibility gaps, and deferred work. Mark only proven tasks complete. - -Completion gate: acceptance criteria below are evidenced, user/developer/security documentation agrees with implemented behavior, and the packaged extension can perform the V1 workflow. - -### Validation Commands - -Use repository-local commands and verify prerequisites first. These are commands for the implementing model, not evidence that they have been run for this document change. - -```sh -npm run compile -./rebar3 compile -./rebar3 ct --suite apps/erlangbridge/test/mcp/mcp__SUITE -npx vscode-test --run out/test/test-suite/mcp/mcp_.test.js -./rebar3 ct -npm test -npm run webpack -``` - -Replace placeholders with test files actually created or extended. Compile TypeScript before targeted VS Code tests. Use the repository's installed VSIX packaging tool for T15 and do not publish. Run focused checks immediately after edits; reserve full suites and artifact checks for relevant integration gates. Record actual exit codes and distinguish compile checks from behavior checks. - -## Implementation Progress - -Decisions taken with the user: own minimal Streamable HTTP MCP transport in Erlang (no maintained Erlang MCP library supports OTP 18+ and none is in the repo; zero new dependencies); client integration through the VS Code MCP provider API (`lm.registerMcpServerDefinitionProvider`, VS Code >= 1.101) plus the connection-details command. - -**T01 (verified integration note).** Launch: `ErlangDebugSession.launchRequest` -> `ErlangConnection.Start` compiles `gen_connection/vscode_connection/vscode_jsone(+decode)/mcp/*` into `_build/default/lib/ebin` (separate from the LSP beams) and starts the Node HTTP receiver; `configurationDone` spawns `erl -pa ebin -s vscode_connection start`; the target posts `/listen` with its command-server port. Attach: helper node (`-hidden`, `vscode_connection:attach/0`) pushes modules to the target and runs `start_attached`. Disconnect: `Quit()` -> `debugger_exit` (launch) or `debugger_detach` (attach, node kept). Target loss: `quitEvent`/`halt_on_nodedown`. Versions: CI OTP 25.3/26.2/27.3/28.1, Node 20, VS Code `^1.136.0`; tested locally only OTP 26.2.5 (erts 14.2.5), Node 25.9, VS Code 1.139.1 headless. Baseline: no failing test before/after (`./rebar3 ct`: 225 passed). - -**T02.** Protocol 2025-06-18, stateless (no Mcp-Session-Id), JSON responses only, GET 405, `notifications/cancelled` accepted but cancellation unsupported (request-scoped by timeout). Endpoint (non-secret) via DAP custom event `erlangMcp`; secret via a 0600 descriptor read+deleted by the extension host. Supported client: VS Code (provider API); others via *Show Connection Details* + fixed port. Not done: library review (superseded by the decision), a real third-party MCP client fixture, rejection of remote/container topologies (only documented). - -**T03-T10.** `apps/erlangbridge/src/mcp/mcp_{policy,encoder,audit,store,tools,runtime,server,sup}.erl`; hooks in `vscode_connection.erl` (`mcp_start/mcp_renew/mcp_stop`, conditional module push via `-vscode_mcp`). Tests: `apps/erlangbridge/test/mcp/` `mcp_{policy,encoder,store,server,bridge}_SUITE` + fixtures `mcp_fixture_*`. Command: `./rebar3 ct --dir apps/erlangbridge/test/mcp`. -Deviation: `proc_lib:initial_call/1` (reads the process dictionary internally, result never returned) gates supervisor API calls so a plain gen_server is never sent `which_children`. - -**T11.** No new dependency. Real MCP client interoperability and protocol-level cancellation NOT done (tests use a raw HTTP/JSON-RPC client). - -**T12.** Verified end-to-end with the real adapter over DAP and a real `erl` target (launch and attach): `test/test-suite/mcp/mcp_debug_adapter.test.ts`. Only `external` adapter mode tested; target loss, inspector supervisor failure, two concurrent sessions and the live VS Code provider registration are NOT tested. - -**T13.** Audit + negative tests done (auth, host/origin, hostile ids, atoms, sentinels, logs). Adapter/DAP log capture for every phase is partial. - -**T14 (not done).** No real MCP client; the adapter-level e2e covers discovery/credential hand-over, policy, lease renewal, cleanup and attach survival. - -**T15.** Docs: HELP.MD, mcp_security.md, DevelopersReadme.md, CHANGELOG.md; CI step added. Not done: startup from a packaged VSIX, OTP 25/27/28 runs. - -Next: T11 real-client test, T12/T14 gaps above (still open; deliberately not scheduled). - -### Post-V1 additions - -Decided with the user after the V1 work order, all within the read-only/metadata-only guardrails (no new write, eval, mailbox, ETS-value or source access). The toolset is now thirteen default tools (plus three opt-in developer-tier tools); the original seven are unchanged apart from the additive fields noted. - -- `top_processes` (queue length / reductions / memory ranking), `process_groups` (all processes aggregated by initial call, fixed-size answer), `top_ports` (queue size / traffic counters; driver name and owner only, never addresses or command lines), `ets_summary` (ETS memory per owner, no table names), `topology_overview` (one application in one collection; each part obeys its own tool's allowlist entry), `changes_since` (diff against a retained collection; baseline limited by `collection_ttl_ms` / `max_collections`). -- Additive fields: `children` counts on supervisors (`supervisor:count_children`), `pausedProcesses` in `debug_session` and a `debugger` field in `process_info` (from `int:snapshot/0`; a supervisor that is itself stopped at a breakpoint is reported as `unavailable_while_paused`), run queue / VM resource usage / connected node names in `runtime_summary`, `format: "mermaid"` on graph tools. -- New edge type `owns_port` (MAP-03 extension). New MCP prompt `map_application` (the only exception to "no prompts"; static text, argument-checked, grants nothing). -- Developer tier (opt-in, only when named in the project's `allowed_tools`; returns a bounded, best-effort-redacted sample of application data, so it is outside the metadata-only V1 guardrail by design): `process_state` (`sys:get_state` of gen_server/gen_statem/gen_event, never a supervisor or a plain process), `mailbox_sample`, `ets_sample` (approved non-private tables). `allowed_ets_tables` may be `all`. Limits can be raised above their defaults up to fixed ceilings. See [mcp_security.md](./mcp_security.md). -- Review fixes: `ets_summary` counts only approved tables, `top_ports` names only well-known drivers, `changes_since` observes with the baseline's detail, one time budget for `debug_session`, one tree walk in `topology_overview`, inspector processes excluded by module, redaction of child ids, token/secret hidden from crash reports, non-ASCII workspace paths, `\z` anchors. -- Not added: restart intensity/period (no safe API), distributed traversal, tracing, any mutation. -- Tests: `./rebar3 ct --dir apps/erlangbridge/test/mcp` (83 cases). Plain `./rebar3 ct` does not include this directory. - -## Acceptance Criteria - -The feature is complete when all the following are true: - -- MCP is disabled by default and impossible to start through the normal LSP path. -- Enabling `erlang.mcp.enabled` in the selected folder's Erlang settings starts MCP only when an actual debug session starts, inside its target node. Normal LSP operation and `noDebug` never start it. -- Optional top-level rebar policy controls tools/tables/lower limits without overriding activation, binding, or credentials; malformed policy disables only MCP. -- A supported AI client receives the actual ephemeral endpoint and fresh credential through the documented secure integration. Static clients have a documented fixed-port setup; port conflicts never silently rebind. -- New project-owned MCP files use `mcp_` names in dedicated subfolders, with Erlang production code under `apps/erlangbridge/src/mcp/`; packaged launch/attach resolves these modules correctly. -- The complete MCP server and policy implementation is Erlang. TypeScript contains only thin existing-file VS Code/debugger/client integration hooks, with no dedicated MCP subsystem or production MCP SDK. -- The endpoint accepts connections only through loopback. -- Every request requires session-scoped authorization. -- Only documented read-only tools are advertised and callable. -- A real client reconstructs the fixture topology without prior supervisor/process names; MAP-01 through MAP-07 are verified. -- Results expose typed edges, consistent IDs, evidence, observation intervals, continuation, and explicit coverage limitations. No map claims observed message traffic. -- Topology discovery works without source access; ETS remains metadata-only. -- No generic eval, arbitrary RPC, shell, mutation, full-state dump, or unrestricted ETS/mailbox access exists. -- Results are bounded, sanitized, and safely encoded. -- MCP failure never aborts ordinary debugging; partial inspector resources are cleaned up and an actionable error is reported. No `required` configuration exists. -- The MCP server and credentials are invalidated on shutdown or target-side lease expiry under the documented runtime assumptions; retained data and descriptors follow the documented cleanup policy. -- Existing LSP and debugger behavior remains unchanged when MCP is disabled. -- Unit, integration, security, and end-to-end tests pass. -- User and security documentation is included. - -## Deliverables - -- Implementation following the existing Erlang/OTP project style. -- Updated Erlang settings schema, rebar project-policy reader/schema, and client connection examples. -- Automated tests. -- User documentation. -- A short security note describing threat model, controls, exclusions, and residual risks. -- Changelog entry for the new feature. - -## Final Implementation Guardrails - -- Inspect the repository before choosing module names, dependencies, or integration points. -- Reuse existing configuration, supervision, logging, telemetry, JSON, HTTP, DAP, and test infrastructure. -- Keep the diff focused on this feature. Do not perform unrelated refactoring. -- Prefer small modules with explicit policy boundaries. -- Do not weaken an existing security check to simplify MCP integration. -- If a requirement conflicts with existing architecture, implement the safest compatible design and document the trade-off in the final summary. -- In the final response, list changed files, describe lifecycle and security decisions, and report tests executed and their results. From adacb8861fda384d1fbc8a9bcbef68c51d7504a8 Mon Sep 17 00:00:00 2001 From: Pierrick Gourlain Date: Thu, 1 Oct 2026 22:33:02 +0200 Subject: [PATCH 20/20] Update MCP settings test for the erlang.mcp.authToken setting Co-Authored-By: Claude Opus 5.5 --- test/test-suite/mcp/mcp_configuration.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/test-suite/mcp/mcp_configuration.test.ts b/test/test-suite/mcp/mcp_configuration.test.ts index bc9fc3ac..9a0961fb 100644 --- a/test/test-suite/mcp/mcp_configuration.test.ts +++ b/test/test-suite/mcp/mcp_configuration.test.ts @@ -57,17 +57,19 @@ suite('MCP: debug configuration hook', () => { assert.strictEqual(cfg.mcpSettings, undefined); }); - test('package.json declares the three settings with the documented defaults', () => { + test('package.json declares the four settings with the documented defaults', () => { const pkg = require('../../../../package.json'); const props = pkg.contributes.configuration.properties; assert.strictEqual(props['erlang.mcp.enabled'].default, false); assert.strictEqual(props['erlang.mcp.host'].default, '127.0.0.1'); assert.strictEqual(props['erlang.mcp.port'].default, 0); + // empty = random token per session; machine scope keeps it out of workspace settings + assert.strictEqual(props['erlang.mcp.authToken'].default, ''); + assert.strictEqual(props['erlang.mcp.authToken'].scope, 'machine'); assert.deepStrictEqual(Object.keys(props).filter(k => k.startsWith('erlang.mcp.')).sort(), - ['erlang.mcp.enabled', 'erlang.mcp.host', 'erlang.mcp.port']); - // no `required` option and no token setting in V1 + ['erlang.mcp.authToken', 'erlang.mcp.enabled', 'erlang.mcp.host', 'erlang.mcp.port']); + // no `required` option assert.ok(!('erlang.mcp.required' in props)); - assert.ok(!('erlang.mcp.authToken' in props)); assert.ok(pkg.contributes.commands.some((c: any) => c.command === 'erlang.mcp.showConnectionDetails')); }); });