@moikapy/lich 0.6.0 → 0.7.1

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 (39) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/README.md +59 -7
  3. package/dist/{chunk-JC2G3XH2.js → chunk-CVX7LZWC.js} +2 -2
  4. package/dist/chunk-PZNYVGD4.js +58 -0
  5. package/dist/chunk-PZNYVGD4.js.map +1 -0
  6. package/dist/{chunk-7HLVKVIG.js → chunk-WNFBIX4E.js} +1344 -162
  7. package/dist/chunk-WNFBIX4E.js.map +1 -0
  8. package/dist/cli.js +246 -18
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{gateway-RJFZJEUZ.js → gateway-44QTIJTJ.js} +108 -49
  11. package/dist/gateway-44QTIJTJ.js.map +1 -0
  12. package/dist/index.d.ts +288 -154
  13. package/dist/index.js +5 -3
  14. package/dist/{tui-LOUJVZ6A.js → tui-MEJGEILU.js} +25 -10
  15. package/dist/tui-MEJGEILU.js.map +1 -0
  16. package/docs/.vitepress/config.mts +4 -0
  17. package/docs/architecture/overview.md +30 -18
  18. package/docs/architecture/plugins.md +1 -1
  19. package/docs/architecture/tools.md +36 -4
  20. package/docs/getting-started.md +7 -6
  21. package/docs/index.md +5 -4
  22. package/docs/user-guide/cli.md +21 -8
  23. package/docs/user-guide/games.md +1 -1
  24. package/docs/user-guide/gateway.md +70 -16
  25. package/docs/user-guide/godot.md +3 -1
  26. package/docs/user-guide/library.md +4 -2
  27. package/docs/user-guide/plugins.md +2 -2
  28. package/docs/user-guide/redot.md +93 -0
  29. package/docs/user-guide/tui.md +2 -2
  30. package/examples/game_bridge/README.md +2 -0
  31. package/optional-mcps/godot/manifest.json +6 -0
  32. package/optional-mcps/redot/manifest.json +18 -0
  33. package/package.json +2 -1
  34. package/dist/chunk-6M6OAQGN.js +0 -17
  35. package/dist/chunk-6M6OAQGN.js.map +0 -1
  36. package/dist/chunk-7HLVKVIG.js.map +0 -1
  37. package/dist/gateway-RJFZJEUZ.js.map +0 -1
  38. package/dist/tui-LOUJVZ6A.js.map +0 -1
  39. /package/dist/{chunk-JC2G3XH2.js.map → chunk-CVX7LZWC.js.map} +0 -0
@@ -27,37 +27,54 @@ lich gateway # defaults to webhook
27
27
 
28
28
  The gateway is silent after startup: Telegram/Discord/Twitch respond only in chats, channels, or servers the bot can see or has joined, and the webhook only serves HTTP. Telegram media messages arrive as the placeholder text `media not supported yet`; other non-text events are ignored. Telegram `/start` is answered like a plain "hello".
29
29
 
30
+ **Security defaults:** the webhook binds loopback only; Telegram/Discord/Twitch default-deny until you configure allowlists; the gateway agent uses a read-only tool subset (no `terminal`, no file writes) unless you override `gateway.tools_enabled`.
31
+
30
32
  ## Setup: webhook
31
33
 
32
- Zero configuration — the server binds `0.0.0.0:$LICH_GATEWAY_PORT` (default 8089).
34
+ Zero configuration for local use — the server binds `127.0.0.1:$LICH_GATEWAY_PORT` (default 8089). Set `LICH_GATEWAY_HOST` only when you intentionally expose the port.
33
35
 
34
36
  ```sh
35
37
  lich gateway webhook
36
38
  ```
37
39
 
38
40
  ```sh
39
- curl -s -X POST http://localhost:8089/message \
41
+ curl -s -X POST http://127.0.0.1:8089/message \
40
42
  -H "content-type: application/json" -d '{"text": "hello"}'
41
43
  # -> {"reply":"...","usage":null}
42
44
 
43
- curl -s http://localhost:8089/health
45
+ curl -s http://127.0.0.1:8089/health
44
46
  # -> {"status":"ok"}
45
47
  ```
46
48
 
49
+ To bind a non-loopback address you **must** set a token; otherwise the adapter refuses to start:
50
+
51
+ ```sh
52
+ LICH_GATEWAY_HOST=0.0.0.0 LICH_GATEWAY_TOKEN=s3cret lich gateway webhook
53
+ ```
54
+
47
55
  With token auth, every POST must carry the exact `x-lich-token` header; mismatched or missing tokens get `401 {"error":"unauthorized"}`:
48
56
 
49
57
  ```sh
50
58
  LICH_GATEWAY_TOKEN=s3cret lich gateway webhook
51
- curl -s -X POST http://localhost:8089/message \
59
+ curl -s -X POST http://127.0.0.1:8089/message \
52
60
  -H "x-lich-token: s3cret" -H "content-type: application/json" -d '{"text": "hello"}'
53
61
  ```
54
62
 
55
- Payload fields (all optional except `text`): `platform` (default `"webhook"`), `chat_id` (default `"default"`), `user_id` (default `"anonymous"`), `text` (required; missing `text` is a `400`). Use distinct `chat_id` values to keep independent conversation memories.
63
+ Payload fields (all optional except `text`): `chat_id` (default `"default"`), `user_id` (default `"anonymous"`), `text` (required; missing `text` is a `400`). A `platform` field in the body is **ignored** — conversations are always keyed as `webhook:<chat_id>`. Use distinct `chat_id` values to keep independent conversation memories.
56
64
 
57
65
  ## Setup: Telegram
58
66
 
59
67
  1. Message [@BotFather](https://t.me/BotFather) → `/newbot` → copy the token.
60
- 2. Export it and run:
68
+ 2. Allow your user (and optionally chat) ids in config, export the bot token, and run:
69
+
70
+ ```json
71
+ {
72
+ "gateway": {
73
+ "allowed_users": { "telegram": ["123456789"] },
74
+ "allowed_chats": { "telegram": ["123456789"] }
75
+ }
76
+ }
77
+ ```
61
78
 
62
79
  ```sh
63
80
  export LICH_TELEGRAM_BOT_TOKEN=123456:ABC-your-token
@@ -66,14 +83,22 @@ lich gateway telegram
66
83
 
67
84
  3. Open your bot in Telegram, send a message, get a reply. Media messages arrive as the text `media not supported yet`; the bot replies from there.
68
85
 
69
- Telegram uses long polling (no public URL needed). Replies split at 4096 chars.
86
+ Without `allowed_users` / `allowed_chats` for `telegram`, every inbound message is denied (default-deny). Telegram uses long polling (no public URL needed). Replies split at 4096 chars.
70
87
 
71
88
  ## Setup: Discord
72
89
 
73
90
  1. Create an application at the [Discord developer portal](https://discord.com/developers/applications), add a **Bot**, and copy the bot token.
74
91
  2. Enable the **Message Content Intent** (Bot settings → Privileged Gateway Intents) — the adapter requests intents `512 | 32768`, which includes message content.
75
92
  3. Invite the bot with the `bot` scope (OAuth2 → URL Generator; no extra permissions needed beyond sending messages in target channels).
76
- 4. Export the token (and the bot's application/user id, so leading `<@BOT_ID>` mentions are stripped) and run:
93
+ 4. Allow channel and/or user ids in config, export the token (and the bot's application/user id, so leading `<@BOT_ID>` mentions are stripped) and run:
94
+
95
+ ```json
96
+ {
97
+ "gateway": {
98
+ "allowed_chats": { "discord": ["123456789012345678"] }
99
+ }
100
+ }
101
+ ```
77
102
 
78
103
  ```sh
79
104
  export LICH_DISCORD_BOT_TOKEN=your-bot-token
@@ -81,14 +106,23 @@ export LICH_DISCORD_BOT_ID=123456789012345678
81
106
  lich gateway discord
82
107
  ```
83
108
 
84
- 5. Send the bot a message (DM or any channel it can read — every non-bot message gets a reply); each channel has its own conversation memory (keyed by `channel_id`). Replies split at 2000 chars. Bot-authored messages are ignored (no loops).
109
+ 5. Send the bot a message in an allowed channel; each channel has its own conversation memory (keyed by `channel_id`). Replies split at 2000 chars. Bot-authored messages are ignored (no loops). Without allowlists for `discord`, every inbound message is denied.
85
110
 
86
111
  **Known limitation:** the Discord adapter has no reconnect resume. If its gateway WebSocket drops, messages sent while offline are missed permanently; the adapter reconnects fresh after 5s. If guaranteed delivery across disconnects matters, run webhook or Telegram instead.
87
112
 
88
113
  ## Setup: Twitch
89
114
 
90
115
  1. Generate an OAuth token with the `chat:read` and `chat:edit` scopes (for example via [twitchtokengen](https://twitchtokengen.com)).
91
- 2. Export it, your bot account's nickname, and the channels to join (comma-separated, lowercased by the adapter):
116
+ 2. Allow channel names and/or viewer nicks in config, export credentials, and run:
117
+
118
+ ```json
119
+ {
120
+ "gateway": {
121
+ "allowed_chats": { "twitch": ["channelone"] },
122
+ "allowed_users": { "twitch": ["trustedviewer"] }
123
+ }
124
+ }
125
+ ```
92
126
 
93
127
  ```sh
94
128
  export LICH_TWITCH_OAUTH_TOKEN=oauth:abc123...
@@ -97,9 +131,28 @@ export LICH_TWITCH_CHANNELS=channelone,channeltwo
97
131
  lich gateway twitch
98
132
  ```
99
133
 
100
- 3. The bot joins `#channelone` and `#channeltwo` and replies in chat (own messages are ignored). Replies split at 512 chars; IRC PING/PONG is answered automatically.
134
+ 3. The bot joins `#channelone` and `#channeltwo` and replies in chat only when the allowlist matches (own messages are ignored). Replies split at 512 chars; IRC PING/PONG is answered automatically.
135
+
136
+ Only real chat messages count for the allowlist: the adapter strips optional IRC tags and then requires a full `PRIVMSG` line (`^:<nick>!... PRIVMSG #<channel> :<text>`), so Twitch events such as `USERNOTICE` (raids, subs) and `WHISPER` are ignored even if their text contains something that looks like a `PRIVMSG` — they can never impersonate an allowed user or channel.
137
+
138
+ All three fields (`token`, `nick`, `channels`) are required — a missing one idles the adapter. Without allowlists for `twitch`, every inbound message is denied.
139
+
140
+ ## Allowlists and gateway tools
141
+
142
+ Public platforms (telegram, discord, twitch) are **default-deny**. Configure at least one of:
143
+
144
+ | Field | Meaning |
145
+ | --- | --- |
146
+ | `gateway.allowed_users.<platform>` | User ids (Telegram/Discord) or nicks (Twitch) that may talk to the bot. |
147
+ | `gateway.allowed_chats.<platform>` | Chat / channel ids (or Twitch channel names) that may talk to the bot. |
148
+
149
+ If both lists are set for a platform, a message must match **both**. If only one list is set, the other dimension is unrestricted. Enforcement lives in the shared gateway bus — denied senders get no agent run and no reply.
150
+
151
+ The gateway agent ignores top-level `tools_enabled` and uses `gateway.tools_enabled`, which defaults to a read-only subset:
152
+
153
+ `read_file`, `list_dir`, `grep_files`, `fetch_url`, `web_search`, `docs_read`, `docs_search`
101
154
 
102
- All three fields (`token`, `nick`, `channels`) are required a missing one idles the adapter.
155
+ Set `"gateway": { "tools_enabled": "all" }` (or an explicit name list) only when you intentionally want writes / `terminal` on chat platforms.
103
156
 
104
157
  ## Running multiple platforms at once
105
158
 
@@ -116,8 +169,9 @@ Adapters whose credentials are missing start **idle** (a warning is logged, e.g.
116
169
 
117
170
  | Variable | Default | Purpose |
118
171
  | --- | --- | --- |
172
+ | `LICH_GATEWAY_HOST` | `127.0.0.1` | Webhook bind address. Non-loopback requires `LICH_GATEWAY_TOKEN`. |
119
173
  | `LICH_GATEWAY_PORT` | `8089` | Webhook server port (invalid/empty values fall back to 8089). |
120
- | `LICH_GATEWAY_TOKEN` | unset | If set, POST `/message` requires header `x-lich-token` to match; else 401. |
174
+ | `LICH_GATEWAY_TOKEN` | unset | If set, POST `/message` requires header `x-lich-token` to match; else 401. Required for non-loopback binds. |
121
175
  | `LICH_TELEGRAM_BOT_TOKEN` | unset | Bot token from BotFather; adapter idles without it. |
122
176
  | `LICH_DISCORD_BOT_TOKEN` | unset | Bot token from the developer portal; adapter idles without it. |
123
177
  | `LICH_DISCORD_BOT_ID` | unset | Bot user id; strips a leading `<@id>` mention from messages. |
@@ -143,10 +197,10 @@ Provider/model configuration comes from the same resolution as every mode (`LICH
143
197
  Request:
144
198
 
145
199
  ```json
146
- {"text": "hello", "platform": "webhook", "chat_id": "default", "user_id": "anonymous"}
200
+ {"text": "hello", "chat_id": "default", "user_id": "anonymous"}
147
201
  ```
148
202
 
149
- Only `text` is required. Success (`200`):
203
+ Only `text` is required. Any `platform` field in the body is ignored; the conversation key is always `webhook:<chat_id>`. Success (`200`):
150
204
 
151
205
  ```json
152
206
  {"reply":"Hello! How can I help you today? ...","usage":null}
@@ -165,4 +219,4 @@ Agent-level failures (e.g. every provider failed) return `200` with `reply` set
165
219
 
166
220
  ### `GET /health`
167
221
 
168
- `200 {"status":"ok"}` unconditionally — the webhook server itself is alive; it does not reflect platform adapters or provider health.
222
+ `200 {"status":"ok"}` unconditionally — the webhook server itself is alive; it does not reflect platform adapters or provider health.
@@ -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` ship in 0.7.0. 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. On Bun, the spawned child is not `unref`'d, so one-shot and other short-lived runs wait for the MCP answer instead of exiting while the child is still working; `Agent.close()` (every CLI mode calls it) kills the child.
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 ships in 0.7.0 (`lich mcp`, `mcp_servers`). From a clone, use `bun src/cli.ts mcp ...`.
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.7.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.7.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.1",
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":[]}