Files
yvgude--lean-ctx/docs/guides/addons.md
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

546 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Addons — community extensions for lean-ctx
Addons let anyone extend lean-ctx with an **external MCP server** and have it
show up through the gateway with one command — no fork, no recompile. This guide
covers using addons and **building & publishing your own**.
> Not sure an Addon is the right mechanism? See
> [Extending lean-ctx](extensions.md) for the one-decision guide (Addon vs
> Plugin vs Provider vs Pack vs SDK).
Contract: [`addon-manifest-v1`](../contracts/addon-manifest-v1.md).
## Why an addon goes deeper than a passthrough
Most "MCP aggregators" stop at proxying: they forward a downstream tool's output
to the model verbatim. lean-ctx can do that too (a **governed passthrough**
secrets redacted, output audit-tagged as untrusted), but it can also do something
no aggregator does: run the addon's output through **its own context engine**, so
the result is retrieved, searched, graphed and remembered through the *same* paths
as your own code. One `ctx_expand`, one `ctx_search`, one `ctx_callgraph`, one
`ctx_knowledge` — regardless of which addon produced the data.
This is opt-in and **off by default** (pure passthrough until you enable it).
Turn it on globally and/or per server:
```toml
[gateway]
enabled = true
compress_output = true # L1: format-aware compression (deterministic)
handle_spill = true # L2: oversized output → ctx_expand retrieval handle
index_output = true # L3: consolidate into BM25 + graph + knowledge
output_budget_tokens = 2000 # L1 target / L2 spill threshold
[[gateway.servers]]
name = "repomix"
# … command/args …
integration = "codebase-pack" # L4 typed adapter (usually auto-derived; see below)
```
…or via the CLI: `lean-ctx config set gateway.index_output true`.
### The four levels
| Level | Flag | What happens to addon output |
|---|---|---|
| **L1 compress** | `compress_output` | Format-aware compression to `output_budget_tokens` — a deterministic function of (content, budget), so it never breaks provider prompt-caching ([#498](../reference/README.md)). |
| **L2 handle/spill** | `handle_spill` | Output over budget is stored verbatim in the content-addressed archive; the model gets a summary + a `ctx_expand` handle instead of the blob. Generalizes Repomix's `outputId` and Headroom's CCR to **every** addon, through one retrieval path. |
| **L3 consolidate** | `index_output` | A background side-channel feeds the output into the BM25 index (`ctx_search` / `ctx_semantic_search`), links file references as property-graph edges (`ctx_callgraph`), and remembers facts (`ctx_knowledge`). Never alters the returned text. |
| **L4 typed adapters** | per-server `integration` | A category-aware adapter folds a known payload into the matching store (below). |
Security and determinism are preserved at every level: post-processing runs
**after** `scrub_output` (secrets already gone), L1/L2 are deterministic
functions of the content, and L3 is a pure side-channel (like usage metering).
### Typed adapters (L4) — competitors as first-class citizens
When an addon belongs to a known category, a typed adapter understands its output
and routes it into lean-ctx's native store. The `integration` slug is normally
**auto-derived** from the addon's `categories`; set it explicitly only to force or
disable an adapter (`none`).
| `integration` | Example addons | What the adapter does |
|---|---|---|
| `codebase-pack` | Repomix | `pack_codebase` → archive + `ctx_expand` handle (keeps the repomix `outputId` for grep) |
| `code-graph` | Graphify | nodes/edges → property graph → `ctx_callgraph` |
| `code-symbols` | Serena | LSP-precise `find_referencing_symbols` → property-graph call edges (complements tree-sitter) |
| `memory` | Mem0 / OpenMemory / Cognee / Letta | `search_memories``ctx_knowledge` facts |
| `compression` | Headroom / RTK | registered as a named lean-ctx `Compressor` (selectable like the built-ins) |
The positioning is deliberate **counter-lock-in**: a competing tool plugs in as
one interchangeable component among many, while lean-ctx stays the unifying
retrieval / search / graph / memory substrate. You can integrate the competition
instead of being encapsulated by it.
## Use an addon
```bash
lean-ctx addon list # installed addons + the registry
lean-ctx addon search markdown # search the registry (empty = list all)
lean-ctx addon info <name> # details + the MCP wiring it would add
lean-ctx addon add <name> # install from the curated registry
lean-ctx addon add acme/tool # install a hosted pack from ctxpkg.com
lean-ctx addon remove <name> # uninstall
```
`add` prints the exact server it will run (transport, command, args, env) and
asks before changing anything. Pass `--yes` / `-y` to skip the prompt in
scripts. Installing an addon enables the MCP gateway (`gateway.enabled = true`);
its tools become reachable via `ctx_tools` (find/call) — restart your MCP client
to pick them up.
A `<namespace>/<name>` target resolves against the hosted ctxpkg registry
(GH #726): lean-ctx downloads the signed `kind=addon` pack, verifies the
artifact hash against the registry index, the pack's integrity hashes, its
**mandatory** ed25519 signature and the kind ↔ payload coherence — then the
embedded manifest walks the exact same consent → preflight → health-probe
pipeline as a local or curated install. `@version` pins a release;
`addon update` re-resolves from wherever the addon was installed.
### Install on add — artifacts, ephemeral runners & the `[install]` block
There are three ways `add` makes a tool runnable, all pinned and disclosed —
resolved in this order:
1. **`[artifacts]` block** (GH #725) — the manifest declares prebuilt binaries
per platform (Rust target triple). `add` downloads the one for your
platform into the **managed bin dir**
(`<data_dir>/addons/bin/<name>/<version>/`, never `PATH`), verifies its
SHA-256 before the atomic install, pins that hash as the spawn-time
binhash, and wires the gateway to the absolute path. A tampered binary is
refused at spawn; `lean-ctx addon update <name>` installs the next version
side-by-side, health-checks it, then prunes the old one.
2. **Ephemeral runner** — when the `[mcp]` command is `npx` (Node) or `uvx`
(uv/Python), the package is downloaded and run **lazily on the first tool
call**, then cached. `add` only writes the `[[gateway.servers]]` entry;
*adding is installing*, provided the runner is on your `PATH`.
3. **`[install]` block** (#1105, Phase 2) — for tools that need a one-time
bootstrap before a runnable command exists, the manifest declares a pinned
package-manager install. On `add`, lean-ctx runs it (idempotently); on
`remove`, it uninstalls it. The exact commands are shown before anything runs.
```toml
[artifacts.aarch64-apple-darwin]
filename = "my-addon-aarch64-apple-darwin"
url = "https://github.com/you/my-addon/releases/download/v1.2.0/my-addon-aarch64-apple-darwin"
sha256 = "…" # mandatory pin — unpinned artifacts are rejected
[artifacts.x86_64-unknown-linux-gnu]
filename = "my-addon-x86_64-unknown-linux-gnu"
url = "https://github.com/you/my-addon/releases/download/v1.2.0/my-addon-x86_64-unknown-linux-gnu"
sha256 = "…"
```
Platforms without an `[artifacts]` entry fall through to the runner /
`[install]` path, so one manifest can serve prebuilt binaries where you build
them and a `cargo install` bootstrap everywhere else.
```toml
[install]
manager = "uv" # uv | pip | cargo | npm | brew | dotnet
package = "headroom-ai[mcp]" # the package spec the manager understands
version = "0.27.0" # mandatory exact pin (no ranges / latest)
bin = "headroom" # binary the [mcp] command needs (PATH idempotency)
```
The engine never uses a shell: each manager has a fixed argv template, and
`package`/`version`/`bin` are passed as discrete arguments (and rejected if they
contain shell metacharacters). A team can forbid all bootstrap execution with
`lean-ctx config set addons.allow_bootstrap false`. Every installable entry pins
an exact version; an unpinned runner or `[install]` block is rejected by the
registry validator, so upstream can't change under you silently.
| Tool | Add = install? | Wiring / bootstrap | Secrets |
|---|---|---|---|
| `repomix` | **yes** (runner) | `npx -y repomix@1.15.0 --mcp` | — |
| `serena` | **yes** (runner) | `uvx --from serena-agent==1.5.3 serena start-mcp-server` | — |
| `qmd` | **yes** (runner) | `npx -y @tobilu/qmd@2.5.3 mcp` — register folders first (`qmd collection add`) | — |
| `memgraph-ingester` | **yes** (runner) | `uvx --from memgraph-ingester-mcp==0.6.6 memgraph-ingester-mcp` | needs a running Memgraph (`MEMGRAPH_INGESTER_MCP_BOLT_URI`) |
| `sequential-thinking` | **yes** (runner) | `npx -y @modelcontextprotocol/server-sequential-thinking@…` | — |
| `everything` | **yes** (runner) | `npx -y @modelcontextprotocol/server-everything@…` | — |
| `headroom` | **yes** (`[install]`) | `uv tool install headroom-ai[mcp]==0.27.0``headroom mcp serve` | — |
| `graphify` | listed | `uv tool install "graphifyy[mcp]"` **+ a built `graph.json`** (no out-of-the-box server) | — |
| `cognee` | listed | clone + `uv sync` (upstream #1815); no pinned one-liner | — |
| `letta` | listed | `npm i -g letta-mcp-server` + a running Letta backend | `LETTA_API_KEY` |
| `mem0` | listed | official MCP server (hosted) | `MEM0_API_KEY` |
| `claude-context` | listed | `npx @zilliz/claude-context-mcp` | `OPENAI_API_KEY` + Milvus |
| `rtk` | listed | shell-output hook; MCP via the `rtk-mcp` bridge | — |
| `lean-md` | listed | Directive-driven Markdown for agent plans (`@dasTholo/lean-md`) | — |
*Listed* tools either need secrets/a backend or don't ship a clean, pinned,
out-of-the-box MCP server yet. Each flips to install-on-add with a one-line
registry change (an `[install]` + `[mcp]` block) the moment upstream ships one —
see the [bootstrap-engine design](../dev/addon-bootstrap-engine.md).
## Build your own addon
An addon is just an MCP server plus a manifest. Four steps:
### 1. Expose your tool as an MCP server
Ship a `stdio` server (an executable that speaks MCP over stdin/stdout) or an
`http` server (a streamable-HTTP endpoint). This is what lean-ctx will run or
connect to. If your project is currently a library or a fork, wrap its
capabilities behind a thin MCP server binary — that is what makes it a runtime
addon instead of a build-time fork.
### 2. Add `lean-ctx-addon.toml` to your repo
Scaffold one in seconds — `lean-ctx addon init` writes a valid,
secure-by-default manifest (slug taken from the directory name) you then edit:
```bash
lean-ctx addon init # stdio addon in ./lean-ctx-addon.toml
lean-ctx addon init my-addon --http # or name it + use an HTTP endpoint
```
…or write it by hand:
```toml
[addon]
name = "my-addon" # slug: [a-z0-9-]
display_name = "My Addon"
version = "0.1.0"
description = "What it does, in one line."
author = "you"
homepage = "https://github.com/you/my-addon"
license = "Apache-2.0"
categories = ["workflow"]
keywords = ["plans", "macros"]
min_lean_ctx = "3.8.0"
[mcp]
transport = "stdio" # or "http"
command = "my-addon-mcp" # stdio: executable to spawn
args = ["serve"]
# env = { MY_TOKEN = "..." } # optional child-process env
# For an HTTP server instead of stdio:
# [mcp]
# transport = "http"
# url = "https://my-addon.example.com/mcp"
# headers = { Authorization = "Bearer ..." }
# Context packages this addon needs at runtime (depth-1, installed first):
[[dependencies]]
name = "@dasTholo/lean-md-skills"
version_req = "^0.2"
optional = false
[mcp.env]
LEAN_MD_SKILLS_DIR = "{pack_dir:@dasTholo/lean-md-skills}"
```
See the [contract](../contracts/addon-manifest-v1.md) for every field.
### Declare what your addon needs — `[capabilities]`
Add a `[capabilities]` block to opt your stdio addon into a **per-addon,
secure-by-default sandbox**. lean-ctx enforces the `network`/`filesystem` profile
you declare at the spawn point (`sandbox-exec` on macOS, `bwrap` on Linux — and
child processes inherit it), scrubs the environment to your `env` allowlist, and
shows the user the full list before they install:
```toml
[capabilities]
network = "full" # "none" (default) blocks all outbound network
filesystem = "read_only" # "read_write" if you write outside a scratch tmp
env = ["GITHUB_TOKEN"] # only these host env vars reach your process
```
Declaring nothing is the safest: no network, read-only filesystem, and a
scrubbed environment (host secrets never leak to your child process). Omit the
block entirely to keep the legacy global `addons.sandbox` behaviour. Declaring
the minimum you need is what makes your addon trustworthy in the marketplace.
### 3. Test it live — locally, before publishing
```bash
lean-ctx addon audit ./lean-ctx-addon.toml # the publish/list gate (#403)
lean-ctx addon add ./lean-ctx-addon.toml
lean-ctx addon list # your addon, installed (source: local)
# … exercise it via ctx_tools …
lean-ctx addon remove my-addon
```
`addon add <path>` wires a local manifest exactly like a registry entry, so you
get the full install flow without touching the registry. `addon audit` runs the
same gate the registry validator does — wiring risk, **capability coherence**
(do your `[capabilities]` match what the wiring actually does?) and **malware
heuristics** — and exits non-zero on a `fail` verdict, so you can run it in CI.
#### Pin your binary (stdio) — `sha256`
For a `stdio` addon, pin the binary so a swapped executable can never run under
your addon's name:
```bash
shasum -a 256 my-addon-mcp # → copy the hex digest
```
```toml
[mcp]
transport = "stdio"
command = "my-addon-mcp"
sha256 = "…the digest…" # the gateway refuses a mismatch, fail-closed
```
A pinned binary is one of the requirements for the verified/paid tier (see the
audit gate below).
### 4. Publish it
Two distribution channels, one trust chain:
**Self-service — `addon publish` (GH #726).** Ship without waiting for a
review cycle: your `lean-ctx-addon.toml` is wrapped verbatim into a signed
`kind=addon` context package and uploaded to the hosted ctxpkg registry.
```bash
lean-ctx addon publish --namespace <your-handle> --check # every gate, no upload
CTXPKG_TOKEN=ctxp_… lean-ctx addon publish --namespace <your-handle>
```
`publish` refuses locally what the registry would refuse remotely — schema
errors, a missing runnable `[mcp]` endpoint, an empty description, and every
blocking audit finding (shell-exec wiring, non-HTTPS endpoints, malware
heuristics, under-declared capabilities). A `review`-level audit publishes
with the findings disclosed. After that, anyone installs it with:
```bash
lean-ctx addon add <your-handle>/my-addon
```
**Curated default catalog.** For the addons every lean-ctx binary should know
about offline, open a merge request adding your manifest as an entry to
`rust/data/addon_registry.json`:
```json
{
"addon": {
"name": "my-addon",
"display_name": "My Addon",
"description": "What it does, in one line.",
"author": "you",
"homepage": "https://github.com/you/my-addon",
"license": "Apache-2.0",
"categories": ["workflow"],
"keywords": ["plans", "macros"],
"min_lean_ctx": "3.8.0"
},
"mcp": {
"transport": "stdio",
"command": "my-addon-mcp",
"args": ["serve"]
}
}
```
Before opening the merge request, validate and canonicalize the registry
locally — the same bar CI enforces. The registry files are **generated
snapshots**: `gen_registry` sorts entries by name and writes one canonical
form, and CI fails on any byte drift, so hand-edits can't diverge:
```bash
lean-ctx addon registry validate rust/data/addon_registry.json
cargo run --example gen_registry --features dev-tools # canonicalize in place
```
Once merged, everyone can run `lean-ctx addon add my-addon`, and your addon
appears on the website's Addons page.
> **Not ready to publish an endpoint yet?** Submit a *listed* entry — the
> `[addon]` table without an `[mcp]` block. It shows up in the registry and on
> the website and links to your homepage; `addon add` points users there until
> you ship the endpoint, then adding the `mcp` block flips it to one-click
> installable.
### 5. Sell your addon (optional)
Add a `[pricing]` block to make your addon a paid artifact — the same commerce
rails that already sell context packs:
```toml
[pricing]
price_cents = 1900 # $19.00 one-time
currency = "usd"
# or usage-metered, billed per tool call:
# model = "usage"
# usage_price_per_1k_cents = 200 # $2.00 per 1,000 calls
```
A paid addon must clear the **paid-listing gate** before it can be sold — this is
deliberate: buyers of third-party code get App-Store-level assurance. The gate
requires:
- a **pass** audit that is **paid-eligible** (declared + coherent
`[capabilities]`, and a pinned `sha256` for stdio addons),
- a **verified-publisher** entry, and
- well-formed pricing.
Check exactly where you stand any time:
```bash
lean-ctx addon audit ./lean-ctx-addon.toml # shows pricing + paid-listing gate
```
If blocked, the audit lists the precise remaining steps (pin your binary, apply
for verification, declare capabilities). Free addons are unaffected — the gate
only governs paid artifacts.
## Build *on* lean-ctx from inside your addon (`lean-ctx call`)
Your addon can call lean-ctx's own tools — read, search, symbol/outline, refactor
and the rest — by shelling out to `lean-ctx call`. This is the simplest, most
robust integration path and works from **any language**:
```bash
lean-ctx call <tool> --project-root <root> --json '<args>'
```
- **Stateless** — each call is a fresh, short-lived process; one error = one exit
code, trivially retryable. No server, no warm connection, no endpoint
discovery — it only needs `lean-ctx` on `PATH`.
- **No `tool_profile` precondition** — `call` builds the tool registry itself and
dispatches to *any* tool, independent of any running server's profile (unlike
the MCP path, where the code-intel `ctx_*` tools require `tool_profile = power`).
- **Always pass `--project-root`** — `call` resolves a `path` argument against it
(and pins `"."`/`""` to the root), so tools operate on your project, never the
process CWD.
```jsonc
// example: ask lean-ctx to read a file, compressed
lean-ctx call ctx_read --project-root /repo --json '{"path":"src/main.rs","mode":"signatures"}'
```
### Declare it: the callback capability block
Spawning `lean-ctx` is subprocess execution, so a callback addon should declare
`exec` — it's how the audit and the install consent reflect what the addon does.
Recommended block:
```toml
[capabilities]
network = "none" # local code-intel needs no internet
filesystem = "read_write" # the lean-ctx child writes its session cache
exec = ["lean-ctx"] # may spawn exactly lean-ctx
```
Two gotchas, because the spawned `lean-ctx call` **inherits your addon's
sandbox**:
- **Cache writes.** Under `filesystem = "read_only"`, the child's writes to its
data dir are blocked (only a scratch tmp is writable) — output still returns,
but caching degrades. Either declare `filesystem = "read_write"` **or** point
the child at a writable tmp with `LEANCTX_DATA_DIR=/tmp/lean-ctx-<addon>`.
- **Write tools.** `ctx_refactor` and friends modify files; if your addon
applies (not just previews) them, it needs `filesystem = "read_write"`.
`exec` is a **declared + audited** capability — not OS-enforced on any platform.
What's enforced is the network/filesystem sandbox, which the spawned `lean-ctx`
**inherits** (so the callback can't exfiltrate or tamper either). Declaring
`exec = ["lean-ctx"]` keeps the audit honest and shows the user exactly what the
addon does (see
[`addon-manifest-v1`](../contracts/addon-manifest-v1.md)).
## How it works
- Installing writes a `[[gateway.servers]]` entry to your global `config.toml`
and records the addon in `<data_dir>/addons/installed.json`. The gateway is
**global-only** and opt-in — an untrusted project can never wire a server.
- `remove` drops exactly the gateway server the addon installed. It leaves the
gateway enabled; turn it off with `lean-ctx config set gateway.enabled false`.
- Everything is local and deterministic: no network calls or telemetry in the
add/list/search/info/remove paths.
- **Output pipeline (opt-in).** Once a call returns, the gateway redacts secrets,
then — if the deep-integration flags are set — runs the output through L1L4
(see [Why an addon goes deeper](#why-an-addon-goes-deeper-than-a-passthrough)).
Installing a categorized addon records its `integration` slug in the
`[[gateway.servers]]` entry, so routing needs no catalog lookup on the hot path.
### Discover & measure
```bash
lean-ctx addon search plans # full-text search; [verified] addons are badged
lean-ctx addon categories # browse by category, with live counts
lean-ctx addon usage # per-addon / per-tool call counters (local meter)
```
`addon usage` reads the local meter (`<data_dir>/addons/usage.json`): every
gateway tool call is attributed to its addon + tool, so you can see what you
actually rely on. It is local-only and a pure side-channel — it never changes a
tool's output. Turn it off with `lean-ctx config set addons.metering false`.
## Security & trust
An addon runs real code with your privileges (stdio) or sends context to a remote
endpoint (http), so lean-ctx makes installing one a disclosed, policy-gated
action. Full model: the [contract](../contracts/addon-manifest-v1.md#security-model).
- **Trust tier.** Catalog entries are **verified** (maintainer-audited) or
**community** (installable, unaudited). The tier shows in `addon list`,
`addon info` and the install preview.
- **Risk review.** Before install, lean-ctx prints a security review of the
wiring — remote endpoints, shelling out, unpinned upstreams, secret-bearing env
— so you see what an addon can do before you say yes.
- **Capabilities.** An addon that declares `[capabilities]` runs under a
per-addon OS sandbox + environment allowlist derived from exactly those
permissions — secure-by-default, shown to you before install.
- **Audit gate.** `lean-ctx addon audit` (and the registry validator) flags any
addon whose declared capabilities don't match its wiring, and scans for malware
patterns (pipe-to-shell, base64-decode→exec, persistence writes). A `fail`
verdict bars a listing; verified/paid entries must pass cleanly, declare
coherent capabilities, and pin their binary.
- **Binary pin.** A stdio addon can pin its binary's `sha256`; the gateway hashes
the resolved executable before spawn and refuses a swap (fail-closed).
- **Untrusted output.** An addon's tool output is redacted for secrets and
audit-tagged as untrusted before it reaches the model.
- **Kill-switch.** `lean-ctx addon revoke <name>` blocks an addon from running
everywhere — install, the gateway catalog, and every call — without waiting for
an uninstall. `unrevoke` lifts it; `revocations` lists active blocks.
- **Integrity lock.** Install pins a hash of the exact wiring. `lean-ctx addon
verify` re-checks it against your live config and flags drift — a swapped
command, an extra arg, or a widened capability after install.
### Lock it down (teams / enterprise)
The global-only `[addons]` block sets a floor an untrusted repo can't loosen:
```bash
# only install maintainer-verified addons
lean-ctx config set addons.policy verified_only
# or restrict to an explicit allowlist
lean-ctx config set addons.policy allowlist
lean-ctx config set addons.allowlist my-addon,other-addon
# refuse anything with a high-risk capability
lean-ctx config set addons.block_risky true
# sandbox spawned addon servers without a [capabilities] block
# (macOS sandbox-exec / Linux bwrap)
lean-ctx config set addons.sandbox strict
# fail closed if a declared-capability addon can't be sandboxed
lean-ctx config set addons.enforce_capabilities true
# require a signed user-override registry (trusted org key)
lean-ctx config set addons.require_signature true
lean-ctx config schema addons # inspect every key
```
Distribute these via MDM / config-management, or pin them through the signed
org-policy floor (`policy org`) to make them un-bypassable.
## Troubleshooting
```bash
lean-ctx addon list # is it installed? which gateway server?
lean-ctx config schema gateway # inspect gateway config keys
lean-ctx status # MCP server / gateway status
```
If a freshly installed addon's tools do not appear, restart your MCP client so
it re-reads the gateway catalog.