@moikapy/lich 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +26 -3
  3. package/dist/{chunk-JC2G3XH2.js → chunk-JYURFAGB.js} +2 -2
  4. package/dist/{chunk-7HLVKVIG.js → chunk-QVJCIZIF.js} +732 -10
  5. package/dist/chunk-QVJCIZIF.js.map +1 -0
  6. package/dist/chunk-SAEB3QL3.js +58 -0
  7. package/dist/chunk-SAEB3QL3.js.map +1 -0
  8. package/dist/cli.js +243 -17
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{gateway-RJFZJEUZ.js → gateway-5BG3YCZF.js} +2 -2
  11. package/dist/index.d.ts +260 -154
  12. package/dist/index.js +5 -3
  13. package/dist/{tui-LOUJVZ6A.js → tui-L6RABP2J.js} +4 -4
  14. package/docs/.vitepress/config.mts +4 -0
  15. package/docs/architecture/overview.md +31 -18
  16. package/docs/architecture/plugins.md +1 -1
  17. package/docs/architecture/tools.md +7 -2
  18. package/docs/getting-started.md +7 -6
  19. package/docs/index.md +5 -4
  20. package/docs/user-guide/cli.md +20 -8
  21. package/docs/user-guide/games.md +1 -1
  22. package/docs/user-guide/godot.md +3 -1
  23. package/docs/user-guide/library.md +4 -2
  24. package/docs/user-guide/plugins.md +2 -2
  25. package/docs/user-guide/redot.md +93 -0
  26. package/docs/user-guide/tui.md +2 -2
  27. package/examples/game_bridge/README.md +2 -0
  28. package/optional-mcps/godot/manifest.json +6 -0
  29. package/optional-mcps/redot/manifest.json +18 -0
  30. package/package.json +2 -1
  31. package/dist/chunk-6M6OAQGN.js +0 -17
  32. package/dist/chunk-6M6OAQGN.js.map +0 -1
  33. package/dist/chunk-7HLVKVIG.js.map +0 -1
  34. /package/dist/{chunk-JC2G3XH2.js.map → chunk-JYURFAGB.js.map} +0 -0
  35. /package/dist/{gateway-RJFZJEUZ.js.map → gateway-5BG3YCZF.js.map} +0 -0
  36. /package/dist/{tui-LOUJVZ6A.js.map → tui-L6RABP2J.js.map} +0 -0
@@ -59,7 +59,7 @@ sequenceDiagram
59
59
  end
60
60
  ```
61
61
 
62
- Lifecycle fan-outs live on the same wrapper: `Agent.run` calls `call_run_start({input_chars})` before `run_conversation` and `call_run_end({stopped_reason, turns_used})` after it (including the abort/throw path, via `finally`). Both are best-effort: hook throws are logged at `warn` and the run proceeds.
62
+ Lifecycle fan-outs live on the same wrapper: `Agent.run` calls `call_run_start({input_chars})` before `run_conversation` and `call_run_end({stopped_reason, turns_used})` in `finally` only when the loop returned an outcome (`final`, `budget`, or `aborted`). A provider throw leaves `outcome` undefined, so `on_run_end` is skipped and no `run_end` session record is written. Both fan-outs are best-effort: hook throws are logged at `warn` and the run proceeds.
63
63
 
64
64
  ## Builtin gatekeeper
65
65
 
@@ -39,7 +39,9 @@ result either way.
39
39
  **`ToolContext`** gives each execution a working directory (`work_dir`, the
40
40
  confinement root), a process environment map (the agent injects
41
41
  `LICH_TERMINAL_TIMEOUT_MS`), and an abort `signal` that fires on caller abort
42
- **or** the executor's own 30 s deadline.
42
+ **or** the executor deadline (`tool.timeout_ms`, else
43
+ `DEFAULT_TOOL_TIMEOUT_MS` = 30000). `terminal` sets 300000, `run_tests` sets
44
+ 600000, and registered MCP tools set 120000.
43
45
 
44
46
  **Parameter schemas.** `parameters` is a `JsonSchemaObject`
45
47
  (`src/util/json_schema.ts`) passed through verbatim into provider requests.
@@ -187,6 +189,9 @@ instructions. See the [plugins guide](../user-guide/plugins.md#skills-and-memory
187
189
  - **Enabling a subset** is done by rebuilding a fresh registry: `Agent`'s
188
190
  `filter_registry` (src/agent/agent.ts) iterates `base.list()` and registers
189
191
  only allowed names onto a new `ToolRegistry` when `tools_enabled` is a list
190
- (`"all"` returns the base registry untouched).
192
+ (`"all"` returns the base registry untouched). Plugin tools, including the
193
+ gatekeeper's `git_commit`, register after that filter, so `tools_enabled: []`
194
+ still leaves `git_commit`. MCP tools register later, on first `run()`, and
195
+ only when the allowlist is `"all"` or names an `mcp_` tool.
191
196
 
192
197
  For building your own tool, see [extending](./extending.md#add-a-builtin-tool).
@@ -10,12 +10,12 @@
10
10
 
11
11
  ```sh
12
12
  npm install -g @moikapy/lich
13
- lich --version # -> 0.3.0
13
+ lich --version # -> 0.6.0 (reads package.json)
14
14
  ```
15
15
 
16
16
  ## Choose a configuration path
17
17
 
18
- Lich needs exactly one thing before it runs: a model. You can provide it three ways, and they can be mixed (flags override env vars, and both override the config file).
18
+ Lich needs exactly one thing before it runs: a model. You can provide it three ways. Flags override a config file and override `LICH_*` env vars. Those env vars apply only when no config file is found; they are not merged into a discovered file.
19
19
 
20
20
  ### Path A: environment variables only
21
21
 
@@ -42,7 +42,7 @@ lich config > .lich/config.json
42
42
 
43
43
  `lich config` honors `LICH_PROVIDER_KIND` and `LICH_MODEL` when you have them set, and otherwise prints an ollama-oriented template. The file is picked up automatically from `.lich/config.json` in the working directory (or `~/.config/lich/config.json` as a fallback) — after this, plain `lich "task"` needs no env vars.
44
44
 
45
- `lich init` writes that same starter file for you (it creates `.lich/` and never overwrites an existing `.lich/config.json`). Bare `lich` on a TTY, with no config in that search chain and no `LICH_MODEL`, runs a setup wizard and writes `.lich/config.json` once before opening the TUI. `.lich/` is gitignored.
45
+ `lich init` writes that same starter file for you (it creates `.lich/` and never overwrites an existing `.lich/config.json`). Bare `lich` on a TTY, with no config in that search chain, runs a setup wizard and writes `.lich/config.json` once before opening the TUI. `LICH_MODEL` / `--model` prefills the model prompt; it does not skip the wizard. Non-TTY stdin skips the wizard. `.lich/` is gitignored.
46
46
 
47
47
  ### Path C: an explicit config file
48
48
 
@@ -121,7 +121,7 @@ jq -r 'select(.kind=="message") | "\(.message.role): \(.message.content)"' .lich
121
121
  | Symptom | Cause and fix |
122
122
  | --- | --- |
123
123
  | `no model configured: set LICH_MODEL, pass --model, or create .lich/config.json` | No provider was resolvable. Set `LICH_MODEL`, pass `--model`, or save a config file (`lich init` or `lich config`). |
124
- | `lich: config not found: <path>` | `--config` was given a path that does not exist. Check the path or drop the flag to use discovery. |
124
+ | `lich: cannot use config file <path>: ...` | `--config` was given a path that does not exist or is not readable JSON. Check the path or drop the flag to use discovery. |
125
125
  | Provider error `kind=auth`, http 401/403 | The api key is missing or wrong. Verify the env var named by `LICH_API_KEY_ENV` (default `OPENAI_API_KEY`/`ANTHROPIC_API_KEY`) is exported in the same shell. |
126
126
  | `fetch failed` / connection refused | The endpoint is unreachable. For ollama, check `ollama serve` is running on `http://localhost:11434`; for remote APIs, check `LICH_BASE_URL`. |
127
127
  | `[lich] budget exhausted after N turns — the ritual is spent` | The task did not finish within `max_turns` (default 25). The `budget exhausted` keyword stays; the flavor suffix comes from the theme. Raise the cap with `--max-turns 50` or in config. |
@@ -153,7 +153,7 @@ To hack on Lich itself, run the CLI straight from a clone instead of the npm pac
153
153
  ```sh
154
154
  git clone https://github.com/Moikapy/lich.git && cd lich
155
155
  bun install
156
- bun src/cli.ts --version # -> 0.3.0
156
+ bun src/cli.ts --version # -> 0.6.0 (package.json; MCP in this tree is unreleased)
157
157
  ```
158
158
 
159
159
  `bun src/cli.ts` accepts the same arguments as the installed `lich` binary, so every command on this page works unchanged.
@@ -164,4 +164,5 @@ bun src/cli.ts --version # -> 0.3.0
164
164
  - Slash commands and the status bar: [TUI guide](user-guide/tui.md).
165
165
  - Telegram, Discord, Twitch, and webhook setup: [Gateway guide](user-guide/gateway.md).
166
166
  - Embedding the agent in your own TypeScript: [Library guide](user-guide/library.md).
167
- - Session JSONL as a combat log: [Games guide](user-guide/games.md).
167
+ - Session JSONL as a combat log: [Games guide](user-guide/games.md).
168
+ - Editor MCP (`lich mcp`, unreleased in the published 0.6.0 package): [Redot guide](user-guide/redot.md). Play is still the [Godot guide](user-guide/godot.md).
package/docs/index.md CHANGED
@@ -8,7 +8,7 @@ outline: [2, 3]
8
8
 
9
9
  Lich is a TypeScript AI agent harness: a library and a CLI that run a chat model inside a Think-Act-Observe loop. A chat wrapper forwards one prompt and prints one completion. A harness keeps going: the model plans (think), calls tools such as `read_file` or `terminal` (act), reads the tool results (observe), and repeats until it can produce a final answer. Lich wraps that loop with the machinery real deployments need: provider failover with bounded retries, path confinement and output clamps on every tool, context compression when the transcript grows past a token budget, and append-only JSONL session transcripts.
10
10
 
11
- One package, four ways to drive the same agent: a one-shot CLI, an interactive chat REPL, an ink-based terminal UI, and a long-running messaging gateway that bridges Telegram, Discord, Twitch, and a zero-config HTTP webhook. All four share the same builtin tools, the same provider configuration, and the same session store.
11
+ One package, four ways to drive the same agent: a one-shot CLI, an interactive chat REPL, an ink-based terminal UI, and a long-running messaging gateway that bridges Telegram, Discord, Twitch, and a zero-config HTTP webhook. All four share the same builtin tools, the same provider configuration, and the same session store. `lich init`, `lich config`, `lich update`, and `lich mcp` do not start that loop.
12
12
 
13
13
  ## Feature overview
14
14
 
@@ -18,7 +18,7 @@ One package, four ways to drive the same agent: a one-shot CLI, an interactive c
18
18
  | Tools | Builtins (file read/write/edit, directory listing, shell, grep, HTTP fetch/request, web search, process list, disk usage, env inspection, `run_tests`), all confined to the working directory. `git_commit` is the gatekeeper's tool, not a config plugin. |
19
19
  | Context compression | Transcript summarized in place when estimated tokens cross `compress_threshold` of `context_budget_tokens`; the 8 most recent turns always stay verbatim. |
20
20
  | Sessions | Every run persists a `.jsonl` transcript under `.lich/sessions/`, labeled by origin (`tui`, `gw:<platform>:<chat>`). |
21
- | CLI | One-shot tasks, chat REPL, TUI, gateway, and a `config` template command, all with flag/env/config-file configuration. |
21
+ | CLI | One-shot tasks, chat REPL, TUI, gateway, `init`, `update`, `config`, and (in this source) `lich mcp`. Flag, env, and config-file configuration. |
22
22
  | TUI | Live ink transcript with tool-call rows, status bar (model, turns, tokens, session path), and slash commands. |
23
23
  | Gateway | One shared agent behind webhook/Telegram/Discord/Twitch with per-conversation memory (40-message history cap) and per-platform message splitting. |
24
24
  | Library | `create_agent` / `run_agent` with typed events (`AgentEmitter`), multi-turn history, and `ProviderError` kinds for error handling. |
@@ -29,12 +29,13 @@ One package, four ways to drive the same agent: a one-shot CLI, an interactive c
29
29
  | Page | Read it to |
30
30
  | --- | --- |
31
31
  | [Getting started](getting-started.md) | Install, configure a provider, and get your first reply in any mode. |
32
- | [CLI reference](user-guide/cli.md) | Master all four modes, flags, provider resolution, and config files. |
32
+ | [CLI reference](user-guide/cli.md) | Modes, flags, provider resolution, config files, and `lich mcp`. |
33
33
  | [TUI guide](user-guide/tui.md) | Run the terminal UI and use slash commands and the status bar. |
34
34
  | [Gateway guide](user-guide/gateway.md) | Wire Telegram, Discord, Twitch, and the HTTP webhook to one agent. |
35
35
  | [Library guide](user-guide/library.md) | Embed the agent in TypeScript with events and multi-turn history. |
36
36
  | [Plugins guide](user-guide/plugins.md) | Add your own tools and lifecycle hooks, and run the self-improvement loop. |
37
37
  | [Godot guide](user-guide/godot.md) | Run lich beside a Godot game and drain `.lich/game/` orders each tick. |
38
+ | [Redot guide](user-guide/redot.md) | Add editor MCP servers (Redot catalog entry). Play still uses the game bridge, the opposite direction. |
38
39
  | [Games guide](user-guide/games.md) | Replay session JSONL as a combat log, including token totals. |
39
40
  | [Architecture overview](architecture/overview.md) | Understand how the harness works inside. |
40
41
 
@@ -67,4 +68,4 @@ Working from a clone of the repository? `bun install`, then run the same command
67
68
 
68
69
  ## Version compatibility
69
70
 
70
- Documented for **v0.3.0**. The npm package requires Node >= 20 (`engines` in `package.json`); Bun is the recommended runtime for development from a clone (`bun src/cli.ts ...`). The TUI needs a TTY; the gateway and library run headless on both runtimes.
71
+ The published npm package is **0.6.0**. `lich --version` reads `package.json`, so this tree also prints `0.6.0`. Editor MCP (`mcp_servers`, `lich mcp`) is in this source under changelog **0.7.0 (unreleased)** and is not in the published package. Node >= 20 (`engines` in `package.json`); Bun is the recommended runtime for development from a clone (`bun src/cli.ts ...`). The TUI needs a TTY; the gateway and library run headless on both runtimes.
@@ -13,19 +13,25 @@ lich tui # interactive terminal UI (ink)
13
13
  lich gateway <plat..> # messaging gateway (webhook|telegram|discord|twitch)
14
14
  lich config # print a starter config template
15
15
  lich update # install a newer npm release, if one exists
16
+ lich mcp list # list servers in this work dir's .lich/config.json
17
+ lich mcp add <name> # catalog or --command/--url; stays disabled
18
+ lich mcp enable <name> # set enabled true
19
+ lich mcp disable <name>
20
+ lich mcp remove <name>
16
21
  lich --help # usage text
17
- lich --version # print 0.3.0
22
+ lich --version # package.json version (published package and this tree: 0.6.0)
18
23
  ```
19
24
 
20
- - **Bare `lich`** opens the same TUI as `lich tui`. It does not print usage. On a TTY, if neither `.lich/config.json` nor `~/.config/lich/config.json` exists and `LICH_MODEL` / `--model` is unset, a setup wizard runs first (name, provider, optional gateway env-var names, optional plugins) and writes `.lich/config.json` once. An existing `.lich/config.json` skips the wizard and is not replaced. Non-TTY stdin skips the wizard and prints guidance instead of hanging. `lich --help` still prints usage.
25
+ - **Bare `lich`** opens the same TUI as `lich tui`. It does not print usage. On a TTY, if neither `.lich/config.json` nor `~/.config/lich/config.json` exists, a setup wizard runs first (name, provider, optional gateway env-var names, optional plugins) and writes `.lich/config.json` once. `LICH_MODEL` / `--model` prefills the model prompt; it does not skip the wizard. An existing config in that chain skips the wizard and is not replaced. Non-TTY stdin skips the wizard and prints guidance instead of hanging. `lich --help` still prints usage.
21
26
  - **`lich init`** writes that starter file without prompts, using the same writer as the wizard. Existing flags such as `--model` are written into the file and win over `LICH_MODEL`. It never overwrites an existing `.lich/config.json`. `.lich/` is gitignored.
22
27
  - **One-shot** joins all positional words into a single task, runs the agent loop, prints the final answer to stdout, and exits. Progress (turn numbers, tool results) goes to stderr.
23
28
  - **Chat** is a readline REPL over one long-lived agent: each line is a turn, memory persists across lines, and an empty line, `/exit`, or `/quit` ends the session. After each turn it prints a `[turns N | tokens M]` footer.
24
29
  - **TUI** launches the ink interface. See the [TUI guide](tui.md).
25
30
  - **Gateway** runs platform adapters (defaults to `webhook` when no platform is given). See the [Gateway guide](gateway.md). Unknown platform names are skipped with a warning; if none remain, the CLI exits `1`.
26
31
  - **Update** compares the installed version to the npm registry and, when a newer release exists, runs `npm install -g @moikapy/lich@latest`. Exit any running TUI or gateway first; npm cannot replace the package while those processes are running. A git clone is told to `git pull`. See [Updating](../getting-started.md#updating).
32
+ - **MCP** is in this source (changelog 0.7.0, unreleased). The published 0.6.0 npm package does not include `lich mcp`. It edits only `mcp_servers` in `<work-dir>/.lich/config.json` through the same writer as `lich init` (`update` mode, so other keys stay). A missing file lists as empty; `add` creates the file if needed. New entries stay disabled until `enable`. Names must match `^[a-z][a-z0-9_]*$`. Catalog names use `optional-mcps/`; otherwise pass `--command` and repeatable `--arg`, or `--url` (loopback only), not both. Redot still needs `--project-path` for the catalog args, and the command basename must be `redot`. No prompts. See the [Redot guide](redot.md).
27
33
 
28
- The installed `lich` binary and `bun src/cli.ts` (from a repository clone) accept identical arguments.
34
+ A repository clone's `bun src/cli.ts` matches that checkout. The published 0.6.0 binary does not include `lich mcp`.
29
35
 
30
36
  ## Flags
31
37
 
@@ -44,6 +50,10 @@ Flags work before or after the subcommand. Every value flag can also be set via
44
50
  | `--session-dir <path>` | Transcript directory. | `<work_dir>/.lich/sessions` |
45
51
  | `--log-level <level>` | `debug` \| `info` \| `warn` \| `error`. | `info` |
46
52
  | `--theme <name>` | Display theme loaded once at startup. `lich` is built-in; other names read `~/.lich/themes/<name>.json`. | `lich` |
53
+ | `--command <bin>` | `lich mcp add` only: local stdio binary. | – |
54
+ | `--arg <value>` | `lich mcp add` only: repeatable stdio arg. May start with `--`. | – |
55
+ | `--url <url>` | `lich mcp add` only: loopback HTTP MCP URL. | – |
56
+ | `--project-path <path>` | `lich mcp add` only: catalog `${project_path}` substitute. | – |
47
57
 
48
58
  Passing `--max-turns 0` or a non-integer fails with `--max-turns must be a positive integer`. Unknown flags fail with `unknown flag: --foo`. A flag missing its value fails with `<flag> requires a value`.
49
59
 
@@ -51,7 +61,7 @@ Passing `--max-turns 0` or a non-integer fails with `--max-turns must be a posit
51
61
 
52
62
  The effective provider for a run is decided in this order:
53
63
 
54
- 1. If `--config <path>` was passed, that file is the whole configuration (it must exist, or the CLI fails with `config not found`).
64
+ 1. If `--config <path>` was passed, that file is the whole configuration (it must exist and be a JSON object, or the CLI fails with `cannot use config file <path>: ...`).
55
65
  2. Otherwise the discovery chain is walked: `./.lich/config.json`, then `~/.config/lich/config.json`. The first file found becomes the config. `LICH_*` env vars are *not* merged into a discovered file.
56
66
  3. If no config file exists, one is built from the environment: `--provider-kind` / `LICH_PROVIDER_KIND` (default `openai_compat`), `--model` / `LICH_MODEL` (required — without it the CLI fails with `no model configured`), `--base-url` / `LICH_BASE_URL`, and `--api-key-env` / `LICH_API_KEY_ENV`, each falling back to the per-kind defaults below.
57
67
  4. Provider override flags (`--model`, `--provider-kind`, `--base-url`, `--api-key-env`) always win over the chosen source: with a config file present they patch `providers[0]` in place; without one they seed a fresh provider from the environment.
@@ -66,7 +76,7 @@ Per-kind defaults:
66
76
 
67
77
  ## Config file reference
68
78
 
69
- Validated by zod (top-level unknown keys are silently stripped; extra keys inside a `providers[]` entry are passed through). Full example with every field:
79
+ Validated by zod (top-level unknown keys are silently stripped; extra keys inside a `providers[]` entry are passed through; each `mcp_servers` entry is strict). Example:
70
80
 
71
81
  ```json
72
82
  {
@@ -114,12 +124,14 @@ Validated by zod (top-level unknown keys are silently stripped; extra keys insid
114
124
  | `providers[].think` | boolean | – | Ollama only: request thinking mode. |
115
125
  | `providers[].keep_alive` | string | – | Ollama only: model residency (e.g. `"10m"`). |
116
126
  | `agent_name` | string | `lich` | Wizard label. The TUI banner uses the active theme welcome string, not this field. |
117
- | `theme` | string | `lich` | Display theme name. See [Themes](../../README.md#themes). |
127
+ | `theme` | string | `lich` | Display theme name. See [Themes](https://github.com/Moikapy/lich/blob/main/README.md#themes). |
118
128
  | `gateway` | object | omitted | Optional. `platforms` (`webhook` \| `telegram` \| `discord` \| `twitch`) and `token_envs` (platform → env-var name). Secrets stay in the environment. |
129
+ | `plugins` | string array | `[]` | Module paths relative to `work_dir` or absolute. Bare `lich`, one-shot, chat, tui, and gateway load them through `create_agent_with_plugins`. `run_agent` does too. `create_agent` does not. See the [plugins guide](plugins.md). |
130
+ | `mcp_servers` | object | omitted | Optional. Closed record of named servers. Each entry is stdio `{command, args, env?}` or loopback http `{url}`. `enabled` defaults to false. Unknown keys are rejected. In this source only (0.7.0 unreleased; not in the published 0.6.0 package). See the [Redot guide](redot.md). |
119
131
  | `system_prompt` | string | built-in | Replaces the default system prompt. |
120
132
  | `max_turns` | int >= 1 | `25` | Turn budget per run. |
121
133
  | `work_dir` | string | cwd | Root for all file tools; paths outside are rejected. |
122
- | `tools_enabled` | `"all"` or name array | `"all"` | Restrict the registry to these builtin tools. |
134
+ | `tools_enabled` | `"all"` or name array | `"all"` | Restrict the builtin registry to these names. `[]` drops builtins and MCP tools and does not connect to MCP servers. Plugin tools still register afterward, including the gatekeeper's `git_commit`. |
123
135
  | `temperature` | 0–2 | – | Sampling temperature. |
124
136
  | `max_tokens` | positive int | – | Completion cap. |
125
137
  | `context_budget_tokens` | positive int | `100000` | Estimated budget before compression triggers. |
@@ -169,7 +181,7 @@ jq -r 'select(.kind=="message") | "\(.message.role): \(.message.content // "(too
169
181
 
170
182
  | Code | Meaning |
171
183
  | --- | --- |
172
- | `0` | Success: final answer produced (also `--help`, `--version`, `config`, `init`, and a TUI that exits cleanly). |
184
+ | `0` | Success: final answer produced (also `--help`, `--version`, `config`, `init`, a successful `mcp` action, `update` when nothing newer is installed or the install succeeds, and a TUI that exits cleanly). |
173
185
  | `1` | Any failure: unknown flag, missing model, unreadable config, provider error after failover, aborted run, budget exhaustion, non-TTY bare `lich`, or a cancelled setup wizard. |
174
186
 
175
187
  ## Log levels
@@ -4,7 +4,7 @@
4
4
 
5
5
  lich does not replay combat from RNG seeds. The commander's choices are sampled. The JSONL transcript is the replay. Godot still drains `.lich/game/`; these recipes read `.lich/sessions/`, not the order file.
6
6
 
7
- Tool shapes live in [`examples/game_bridge/README.md`](https://github.com/Moikapy/lich/blob/main/examples/game_bridge/README.md). This page does not repeat them. The plugin entry is `examples/game_bridge/game_bridge.plugin.mjs`, loaded through `config.plugins` and `create_agent_with_plugins`. A per-persona HTTP front for that plugin is the [orchestrator example](../../examples/persona_orchestrator/README.md) — a pattern the game repo copies, not a second agent core.
7
+ Tool shapes live in [`examples/game_bridge/README.md`](https://github.com/Moikapy/lich/blob/main/examples/game_bridge/README.md). This page does not repeat them. The plugin entry is `examples/game_bridge/game_bridge.plugin.mjs`, loaded through `config.plugins` and `create_agent_with_plugins`. A per-persona HTTP front for that plugin is the [orchestrator example](https://github.com/Moikapy/lich/blob/main/examples/persona_orchestrator/README.md) — a pattern the game repo copies, not a second agent core.
8
8
 
9
9
  ## Session files as combat logs
10
10
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  lich is the AI brain in a separate process. Godot speaks HTTP. This page is the recipe; the file contract and tool list live in [`examples/game_bridge/README.md`](https://github.com/Moikapy/lich/blob/main/examples/game_bridge/README.md). Plugin authoring is the [plugins guide](plugins.md). Platform setup beyond the webhook is the [gateway guide](gateway.md). A TypeScript game backend that embeds the library uses the [library guide](library.md) — Godot itself does not.
6
6
 
7
+ Editor MCP and this page are opposite directions. An editor MCP is lich connecting to the editor to build the project — the [Redot guide](redot.md). This page is the game connecting to lich. Godot has no official MCP server. lich does not download a community addon and does not spawn one. A game master is another persona, not an editor tool. Play for both editors is still one `POST /message` per round, then drain `.lich/game/orders.jsonl`. Do not call lich per frame.
8
+
7
9
  ## Architecture
8
10
 
9
11
  Two tiers:
@@ -24,7 +26,7 @@ flowchart LR
24
26
  G --> H[resolve the round]
25
27
  ```
26
28
 
27
- A game backend may instead call `run_agent`, which loads `config.plugins`. `create_agent` does not. Godot still reaches that backend over HTTP; it does not import the package. Several personas means several agents behind that HTTP process — the pattern is [`examples/persona_orchestrator`](../../examples/persona_orchestrator/README.md), and the service is the game's. Session replay is the [games guide](games.md).
29
+ A game backend may instead call `run_agent`, which loads `config.plugins`. `create_agent` does not. Godot still reaches that backend over HTTP; it does not import the package. Several personas means several agents behind that HTTP process — the pattern is [`examples/persona_orchestrator`](https://github.com/Moikapy/lich/blob/main/examples/persona_orchestrator/README.md), and the service is the game's. Session replay is the [games guide](games.md).
28
30
 
29
31
  ## Gateway contract
30
32
 
@@ -145,7 +145,9 @@ Listed providers form a failover chain tried in order: `rate_limit`/`network` er
145
145
 
146
146
  ## Custom tool filtering
147
147
 
148
- `tools_enabled` accepts `"all"` (default) or an array of builtin tool names to register; everything else stays unregistered and invisible to the model. The filter does not apply to plugin tools: they register afterward, including the gatekeeper's `git_commit`. `[]` strips every builtin and does not throw.
148
+ `tools_enabled` accepts `"all"` (default) or an array of tool names to register; everything else stays unregistered and invisible to the model. MCP tools, when a named server is `enabled`, use the same allowlist and stay off when the list is `[]` (that empty list does not connect). The filter does not apply to plugin tools: they register afterward, including the gatekeeper's `git_commit` (fail-closed unless `LICH_ALLOW_SELF_COMMIT=1`). `[]` strips every builtin and every MCP tool and does not throw.
149
+
150
+ `mcp_servers` and `catalog_client_entry` are in this source (changelog 0.7.0, unreleased). The published 0.6.0 package does not include them. See the [Redot guide](redot.md).
149
151
 
150
152
  ```ts
151
153
  const agent = create_agent({
@@ -184,7 +186,7 @@ When every configured provider fails, the last `ProviderError` is thrown. Tool f
184
186
 
185
187
  ## Games
186
188
 
187
- A Godot client does not embed the library. A game backend that does is still one `Agent` per persona, not a second loop. The pattern — factory, history cap, per-conversation queue, `POST /message` → `{reply, usage}` — is [`examples/persona_orchestrator`](../../examples/persona_orchestrator/README.md). The service itself is game-repo work. Session files as a combat log: [games guide](games.md).
189
+ A Godot client does not embed the library. A game backend that does is still one `Agent` per persona, not a second loop. The pattern — factory, history cap, per-conversation queue, `POST /message` → `{reply, usage}` — is [`examples/persona_orchestrator`](https://github.com/Moikapy/lich/blob/main/examples/persona_orchestrator/README.md). The service itself is game-repo work. Session files as a combat log: [games guide](games.md).
188
190
 
189
191
  ## Session access
190
192
 
@@ -112,7 +112,7 @@ Failures are contained at every layer:
112
112
 
113
113
  ## Runtime notes
114
114
 
115
- `package.json` `engines.node` is `>=20`. The CLI loads `config.plugins` before the run. A broken entry logs one `plugin load errors` warning and the run continues without that plugin.
115
+ `package.json` `engines.node` is `>=20`. Bare `lich`, one-shot, chat, tui, and gateway load `config.plugins` through `create_agent_with_plugins` before the run. `lich init`, `lich config`, `lich update`, and `lich mcp` do not start an agent. `create_agent` stays plugin-free; `run_agent` loads plugins. A broken entry logs one `plugin load errors` warning and the run continues without that plugin.
116
116
 
117
117
  `.mjs` and other plain JS (no type syntax) are the form that matches `engines.node` `>=20`. They load on Node and on Bun with no plugin-load warning.
118
118
 
@@ -122,7 +122,7 @@ Checked with Node 26.8.2 (`node dist/cli.js`) and Bun 1.3.14 (`bun src/cli.ts`):
122
122
  - On Node 20 and Node 22 before 22.18, a `.ts` entry still warns and the run continues without that plugin.
123
123
  - Syntax Node cannot strip (for example `enum`) warns and continues. Bun runs that same file with no plugin-load warning. A syntax error warns on both and the run continues.
124
124
 
125
- The combat-commander reference is `examples/game_bridge/game_bridge.plugin.mjs`. Point `config.plugins` at `./examples/game_bridge/game_bridge.plugin.mjs` (relative to `work_dir`). See `examples/game_bridge/README.md`. Embedding it beside a Godot game: [Godot guide](godot.md).
125
+ The combat-commander reference is `examples/game_bridge/game_bridge.plugin.mjs`. Point `config.plugins` at `./examples/game_bridge/game_bridge.plugin.mjs` (relative to `work_dir`). See [`examples/game_bridge/README.md`](https://github.com/Moikapy/lich/blob/main/examples/game_bridge/README.md). Embedding it beside a Godot game: [Godot guide](godot.md).
126
126
 
127
127
  Plain JS (matches `engines.node` `>=20`):
128
128
 
@@ -0,0 +1,93 @@
1
+ # Redot guide
2
+
3
+ > What you'll learn: how to add an editor MCP server, and why that is the opposite of in-game play.
4
+
5
+ Editor MCP and in-game play point opposite ways. This page is lich connecting **to** an editor. NPC control, vetoes, and the game master are the game connecting **to** lich: the webhook and [`examples/game_bridge`](https://github.com/Moikapy/lich/blob/main/examples/game_bridge/README.md). That contract is the [Godot guide](godot.md). A game master is another persona, not an editor tool.
6
+
7
+ The client is general, the same shape as Hermes and Claude: a named list you extend when you need a server. Redot is a catalog entry on that client, not a hard-wired spawner. lich does not ship the engine and does not download an addon.
8
+
9
+ ## Client
10
+
11
+ `mcp_servers` is a closed record. Omit it and no MCP tools register. Each name is snake_case. Each value is either stdio or loopback HTTP. `enabled` defaults to `false`. Unknown keys are rejected.
12
+
13
+ ```json
14
+ {
15
+ "mcp_servers": {
16
+ "notes": {
17
+ "enabled": true,
18
+ "command": "/usr/local/bin/notes-mcp",
19
+ "args": ["--stdio"]
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ stdio is a local binary you named, plus `args`, plus optional `env`. The process is spawned with those argv, never a shell. `npx`, `npm`, `bunx`, `uvx`, `curl`, `wget`, a URL, and shell metacharacters are refused. lich does not run `npx -y` or fetch an addon. Values in `env` are passed to the process and are never logged. `lich mcp add` has no flag for `env`; set that key in the file if you need it.
26
+
27
+ HTTP is `{ "url": "http://127.0.0.1:9/mcp" }`. The host must be `127.0.0.1` or `localhost`. `0.0.0.0` and any other host are refused. There is no remote MCP in v1.
28
+
29
+ On connect the client sends `initialize`, then `notifications/initialized`, then `tools/list`. `tools/call` runs only when the model invokes a registered tool. Registered names are `mcp_<server>_<tool>`, so two servers cannot collide. They appear only when that server is `enabled` and `tools_enabled` is `"all"` or lists the prefixed name. `tools_enabled: []` drops them even when the server is enabled, and does not connect. Plugin tools still register, including the gatekeeper's `git_commit`. The commander persona keeps `tools_enabled: []` and the `game_bridge` plugin only — `persona_config` does not copy `mcp_servers`.
30
+
31
+ This client is in this source (changelog 0.7.0, unreleased). The published npm package is 0.6.0 and does not include `lich mcp` or `mcp_servers`. From a clone, use `bun src/cli.ts mcp ...`. `lich --version` still prints `0.6.0`.
32
+
33
+ ## Add a server
34
+
35
+ Paste an entry, call `catalog_client_entry`, or use the CLI. None of these start a process. New entries stay disabled until you enable them.
36
+
37
+ ```sh
38
+ lich mcp add redot --project-path /home/me/game
39
+ lich mcp enable redot
40
+ lich mcp list
41
+ lich mcp disable redot
42
+ lich mcp remove redot
43
+ ```
44
+
45
+ `lich mcp add redot --project-path /home/me/game` writes a disabled entry whose `command` is `redot` and whose args are `--headless --mcp-server --path` plus that directory. PATH lookup happens at connect, not at add.
46
+
47
+ `lich mcp add notes --command /usr/local/bin/notes-mcp --arg --stdio` is a custom stdio server. `--url` must be loopback. The command writes through the existing config writer and leaves providers, plugins, and gateway untouched. `lich mcp list` reads this work dir's `.lich/config.json`. A missing `mcp_servers` prints nothing and exits 0.
48
+
49
+ ```ts
50
+ import { catalog_client_entry } from "@moikapy/lich";
51
+
52
+ catalog_client_entry("redot", { project_path: "/home/me/game" });
53
+ ```
54
+
55
+ That returns a disabled stdio entry. Unknown names and the Godot catalog return an explanation instead of a spawn plan. Catalog files live in `optional-mcps/<name>/manifest.json`. A new server is a new manifest plus a config entry. It is not a new client.
56
+
57
+ ## Redot
58
+
59
+ Official Redot 26.1+ only. The catalog command is the local `redot` binary. The args are exactly:
60
+
61
+ ```text
62
+ redot --headless --mcp-server --path <project>
63
+ ```
64
+
65
+ Basename must be `redot`. A missing binary tells you to install official Redot 26.1+ from [redotengine.org](https://redotengine.org). lich does not download a build.
66
+
67
+ ```json
68
+ {
69
+ "mcp_servers": {
70
+ "redot": {
71
+ "enabled": true,
72
+ "command": "/usr/local/bin/redot",
73
+ "args": ["--headless", "--mcp-server", "--path", "/home/me/game"]
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ `tools/list` is what registers. The official server lists five controllers. The client drops a listed tool named `execute` (`exclude_tools`). It does not hard-allowlist those five names: any other listed tool is registered. Existing `.gd` files stay normal file edits (`edit_file` / `write_file`). Scene edits go through the scene tool. Do not rewrite `.tscn` as text.
80
+
81
+ | Registered name | Use it for |
82
+ | --- | --- |
83
+ | `mcp_redot_scene_action` | `.tscn` edits: nodes, properties, instances, signals |
84
+ | `mcp_redot_resource_action` | `.tres` files and asset import metadata |
85
+ | `mcp_redot_code_intel` | GDScript validation and engine docs |
86
+ | `mcp_redot_project_config` | Input map, autoloads, settings, run and stop |
87
+ | `mcp_redot_game_control` | Screenshots, clicks, and the live scene tree |
88
+
89
+ ## Play is the other direction
90
+
91
+ These tools build and inspect the project. They do not queue combat orders. NPC and game-master play still use one `POST /message` per round, then drain `work_dir/.lich/game/orders.jsonl` and refresh `state.json`. Do not call lich per frame.
92
+
93
+ Godot has no official MCP server. lich does not spawn a community godot-mcp and does not download an Asset Library addon. If you later point a **different** server name at a local binary you already have, that is a normal stdio entry. The catalog name `godot` writes no spawn plan. Play for both editors stays the [Godot guide](godot.md).
@@ -9,12 +9,12 @@ lich # front door: TUI, plus a first-run setup wizard when no config exi
9
9
  lich tui # same TUI, no wizard. From a clone: bun src/cli.ts tui
10
10
  ```
11
11
 
12
- The TUI needs a TTY and a resolvable provider (same resolution as every mode). On startup it prints a dim header from the active theme welcome string, e.g. `⚱ lich v0.5.1 — the agent that will not stay dead · llama3.2 (ollama)`. That banner is the only tagline placement. Quit with `/exit`, `/quit`, `/q`, or Ctrl+C.
12
+ The TUI needs a TTY and a resolvable provider (same resolution as every mode). On startup it prints a dim header from the active theme welcome string, e.g. `⚱ lich v0.6.0 — the agent that will not stay dead · llama3.2 (ollama)`. `{version}` is `LICH_VERSION` from `package.json`. That banner is the only tagline placement. Quit with `/exit`, `/quit`, `/q`, or Ctrl+C.
13
13
 
14
14
  ## Anatomy
15
15
 
16
16
  ```
17
- ⚱ lich v0.5.1 — the agent that will not stay dead · llama3.2 (ollama)
17
+ ⚱ lich v0.6.0 — the agent that will not stay dead · llama3.2 (ollama)
18
18
  mortal › list the files here <- your input, echoed into the transcript
19
19
  ⏺ list_dir({}) <- live tool-call row (name + args preview)
20
20
  ⏷ list_dir: ok (d src/ d test/ ...) <- result row (ok/error + output preview)
@@ -66,3 +66,5 @@ A round that does not match `state.json` is still appended. The tool output then
66
66
  ## Failures
67
67
 
68
68
  Tools return `{ok: false, error}` and do not throw. Reads skip malformed JSONL lines. The first call creates `.lich/game/` if it is missing. `memory.jsonl` grows forever; only the read is capped.
69
+
70
+ Editor MCP builds the project. This plugin is the opposite direction: the game connects to lich. NPC and game-master play still drain `.lich/game/orders.jsonl` and the webhook. A game master is another persona, not an editor tool. See the [Redot guide](../../docs/user-guide/redot.md).
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "godot",
3
+ "description": "Godot has no official MCP server. Do not download a community addon.",
4
+ "transport": "none",
5
+ "note": "Godot has no official MCP server. lich does not spawn a community addon. Play stays the webhook and examples/game_bridge: the game connects to lich. That is the opposite direction from editor MCP."
6
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "redot",
3
+ "description": "Official Redot 26.1 MCP server. lich does not ship the engine.",
4
+ "transport": "stdio",
5
+ "command": "redot",
6
+ "args": ["--headless", "--mcp-server", "--path", "${project_path}"],
7
+ "command_basename": "redot",
8
+ "args_prefix": ["--headless", "--mcp-server", "--path"],
9
+ "exclude_tools": ["execute"],
10
+ "missing_hint": "redot binary not found. Install official Redot 26.1+ from https://redotengine.org",
11
+ "tools": [
12
+ "scene_action",
13
+ "resource_action",
14
+ "code_intel",
15
+ "project_config",
16
+ "game_control"
17
+ ]
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moikapy/lich",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Lich — a TypeScript AI agent harness (library + CLI) inspired by Hermes",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  "!docs/.vitepress/dist",
23
23
  "!docs/.vitepress/cache",
24
24
  "examples/game_bridge",
25
+ "optional-mcps",
25
26
  "README.md",
26
27
  "CHANGELOG.md"
27
28
  ],
@@ -1,17 +0,0 @@
1
- // src/index.ts
2
- import { readFileSync } from "fs";
3
- import { fileURLToPath } from "url";
4
- function read_package_version() {
5
- const pkg_path = fileURLToPath(new URL("../package.json", import.meta.url));
6
- const pkg = JSON.parse(readFileSync(pkg_path, "utf8"));
7
- if (typeof pkg.version !== "string" || pkg.version.length === 0) {
8
- throw new Error("package.json is missing version");
9
- }
10
- return pkg.version;
11
- }
12
- var LICH_VERSION = read_package_version();
13
-
14
- export {
15
- LICH_VERSION
16
- };
17
- //# sourceMappingURL=chunk-6M6OAQGN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Public surface of the lich agent harness. Pure re-exports, plus the package version.\n */\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nfunction read_package_version(): string {\n const pkg_path = fileURLToPath(new URL(\"../package.json\", import.meta.url));\n const pkg = JSON.parse(readFileSync(pkg_path, \"utf8\")) as { version?: unknown };\n if (typeof pkg.version !== \"string\" || pkg.version.length === 0) {\n throw new Error(\"package.json is missing version\");\n }\n return pkg.version;\n}\n\nexport const LICH_VERSION = read_package_version();\n\nexport { Agent, create_agent, run_agent, create_agent_with_plugins } from \"./agent/agent.js\";\nexport type { AgentRunOptions, AgentRunResult } from \"./agent/agent.js\";\nexport type { AgentConfig } from \"./agent/config.js\";\nexport { parse_agent_config } from \"./agent/config.js\";\nexport { AgentEmitter } from \"./agent/events.js\";\nexport type { AgentEvent, AgentEventHandler, AgentEvents } from \"./agent/events.js\";\nexport type { LoopOutcome, LoopParams } from \"./agent/loop.js\";\nexport type {\n ChatOptions,\n ChatResult,\n Message,\n ProviderConfig,\n ToolCall,\n ToolDefinition,\n Usage,\n} from \"./providers/types.js\";\nexport { ProviderError } from \"./providers/types.js\";\nexport { ToolExecutor } from \"./tools/executor.js\";\nexport { ToolRegistry } from \"./tools/registry.js\";\nexport type { Tool, ToolContext, ToolResult } from \"./tools/types.js\";\nexport { register_builtin_tools } from \"./tools/builtin/index.js\";\nexport { HookedToolRunner } from \"./plugins/hooks.js\";\nexport { load_plugins, plugin_errors_summary } from \"./plugins/loader.js\";\nexport type { LoadedPlugin, PluginLoadError } from \"./plugins/loader.js\";\nexport type {\n AfterToolCallInfo,\n BeforeToolCallInfo,\n BeforeToolCallResult,\n HookContext,\n Plugin,\n PluginHooks,\n RunEndInfo,\n} from \"./plugins/types.js\";"],"mappings":";AAGA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAE9B,SAAS,uBAA+B;AACtC,QAAM,WAAW,cAAc,IAAI,IAAI,mBAAmB,YAAY,GAAG,CAAC;AAC1E,QAAM,MAAM,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AACrD,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,SAAO,IAAI;AACb;AAEO,IAAM,eAAe,qBAAqB;","names":[]}