Packages

MCP server exposing live Elixir runtime introspection tools to AI agents.

Current section

Files

Jump to
extools README.md
Raw

README.md

# Extools
MCP server that lives **inside your Elixir app's BEAM** and exposes live
runtime introspection tools to AI agents (Hermes, Claude Code, Cursor, any
MCP client). Framework-agnostic: works in Phoenix apps and plain Elixir
apps alike.
## The 12 tools
| Tool | Description |
|------|-------------|
| `project_eval` | Evaluate Elixir code in the live runtime |
| `execute_sql_query` | Read-only SQL via the auto-detected Ecto Repo |
| `get_logs` | Live logs from an in-memory ETS buffer (level/grep/tail filters) |
| `get_docs` | Module/function docs via `Code.fetch_docs` |
| `get_source_location` | file:line for a module or function |
| `get_ecto_schemas` | Ecto schemas with fields and types (Ecto 3.14 compatible) |
| `get_process_info` | Top BEAM processes by memory |
| `get_app_state` | `:sys.get_state` of a named process |
| `ets_inspect` | List ETS tables and sample entries |
| `supervision_tree` | Full supervision tree, rendered + structured |
| `trace_function` | Trace function calls with `:dbg` (safety-capped) |
| `config_dump` | Application environment dump with key filter |
## Installation
### Phoenix app
```elixir
# mix.exs
defp deps do
[
{:extools, "~> 0.1", only: :dev}
]
end
```
```elixir
# lib/my_app_web/endpoint.ex
if Mix.env() == :dev do
plug Extools
end
```
MCP endpoint: `http://localhost:4000/extools/mcp`
### Non-Phoenix app
```elixir
# mix.exs
defp deps do
[
{:extools, "~> 0.1", only: :dev},
{:bandit, "~> 1.0", only: :dev}
]
end
```
```elixir
# config/dev.exs
config :extools, port: 4040
```
MCP endpoint: `http://localhost:4040/extools/mcp` (standalone Bandit,
started automatically by the `:extools` application).
## Connecting an MCP client
The endpoint speaks MCP Streamable HTTP (protocol 2025-03-26). Clients
must `Accept: application/json, text/event-stream` and complete the
`initialize` handshake; the session id is returned in the
`mcp-session-id` response header.
Example handshake with curl:
```bash
# 1. initialize (capture the mcp-session-id response header)
curl -i -X POST http://localhost:4040/extools/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"me","version":"0.1"}}}'
# 2. notifications/initialized (with the session header), then:
curl -X POST http://localhost:4040/extools/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'mcp-session-id: <session-id>' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"project_eval","arguments":{"code":"1+1"}}}'
```
## Configuration
| Key | Default | Description |
|-----|---------|-------------|
| `config :extools, :port` | `nil` | Standalone Bandit port (non-Phoenix). Unset = no standalone server. |
| `config :extools, :repo` | auto-detect | Explicit Ecto Repo module for `execute_sql_query`. |
| `config :extools, :otp_app` | auto-detect | OTP app to scan for the Ecto Repo. |
## Security
**Dev-only tool.** The standalone server binds to `127.0.0.1` only, and
the package is meant to be included with `only: :dev`. `project_eval`
executes arbitrary code in your runtime — that is the point, and exactly
why you must never ship it to production. `execute_sql_query` only
accepts read-only statements (`select`/`with`/`explain`).
## License
MIT — 2026 Alvaro Lizama