airlok 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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  All notable changes to airlok. The format follows Keep a Changelog; versions follow SemVer.
4
4
 
5
+ ## [0.7.0] - 2026-09-12
6
+
7
+ ### Changed
8
+
9
+ - Tools from an MCP server are now named `mcp__<server>__<tool>`, the convention the other clients use, in place of `<server>__<tool>`. This is a breaking change for anything that referred to the old names: a saved session's history, a note in AIRLOK.md, or a `tools = [...]` list naming a tool keeps working, since that list names the server's own tool rather than the prefixed one.
10
+
11
+ ### Added
12
+
13
+ - MCP servers are configured with the `mcpServers` JSON that Claude Code, Cursor, and VS Code share, so a `.mcp.json` copied from another project works unchanged. Both forms are read: `command`/`args`/`env` for stdio, and `type`/`url`/`headers` for http. Unknown keys other tools write are ignored rather than refused.
14
+ - Three scopes, each winning over the one above it: `~/.config/airlok/mcp.json`, `./.mcp.json` meant to be committed, and `./.airlok/mcp.json` for personal overrides. A server named in more than one takes the highest definition whole. `[[mcp]]` TOML blocks keep working, merge with the JSON, and win a name clash.
15
+ - Airlok's own options live under an `airlok` key inside a JSON entry, so a plain config stays plain.
16
+ - `${VAR}` and `${VAR:-default}` expand from the environment in `command`, `args`, `env`, `url`, and `headers`. Unset or empty with no default is an error naming the variable. `env_cmd` and `header_cmd` remain the better way to hold a secret.
17
+ - `airlok mcp add`, `remove`, `get`, `import`, and `export` manage those files, and `airlok mcp list` now shows which scope each server came from.
18
+ - An approval can outlive the run: `s` at an MCP confirmation remembers that server, that tool, and the places that call named, in `.airlok/mcp-trust.json`, written 0600 and gitignored. It never covers a place outside the ones approved, and it records what the server was, so changing its command or url asks again. `airlok mcp trust list` and `trust revoke <server>` manage it.
19
+
20
+ ## [0.6.1] - 2026-09-12
21
+
22
+ ### Fixed
23
+
24
+ - Approving an MCP call with `a` covered the whole server for the rest of the run, so a later call to the same server ran without asking, even when it reached a different place. "All" now covers one tool on one server, and only the places that call named: a call naming anything outside them asks again. Paths are resolved before they are compared, so a different spelling of the same place is still covered and `..` cannot step outside an approval.
25
+ - The confirmation now shows what the server can reach and the resolved place each argument names, marking one outside the current project, and `airlok mcp list` shows the same root. A call that reads outside the repository is visible before it runs rather than after.
26
+ - A tool call or a note printed in the middle of a streamed line split the line in two: a bullet's bold label was rendered as a finished bullet and its text as a separate block, which is what `• Overview:` and its text landing on different lines was. Only complete markdown is flushed now, so a line still arriving keeps its block.
27
+
5
28
  ## [0.6.0] - 2026-09-12
6
29
 
7
30
  ### Added
package/README.md CHANGED
@@ -115,45 +115,94 @@ Tool results over 50 KiB are cut with a marker telling the model to page with `r
115
115
 
116
116
  ## MCP servers
117
117
 
118
- airlok can offer the model tools from external [MCP](https://modelcontextprotocol.io) servers. They are configured with `[[mcp]]` blocks, which merge across the user and project files like every other setting:
118
+ airlok offers the model tools from external [MCP](https://modelcontextprotocol.io) servers, configured the way Claude Code, Cursor, and VS Code configure them. A `.mcp.json` copied from another project works unchanged:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "files": {
124
+ "command": "npx",
125
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
126
+ "env": { "NODE_ENV": "production" }
127
+ },
128
+ "docs": {
129
+ "type": "http",
130
+ "url": "https://example.com/mcp",
131
+ "headers": { "Authorization": "Bearer ${DOCS_TOKEN}" }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Three files are read, each winning over the one above it. A server named in more than one takes the highest definition whole, so a local file is a real override rather than a patch.
138
+
139
+ | Scope | File | For |
140
+ |---|---|---|
141
+ | user | `~/.config/airlok/mcp.json` | servers you want everywhere |
142
+ | project | `./.mcp.json` | the project's servers, meant to be committed |
143
+ | local | `./.airlok/mcp.json` | your own overrides, gitignored |
144
+
145
+ `${VAR}` and `${VAR:-default}` expand from the environment in `command`, `args`, `env`, `url`, and `headers`. A variable that is unset or empty with no default is an error naming it, rather than an empty string that fails later in a way nobody can read. For a secret, prefer `env_cmd` and `header_cmd`, which take the value from a command's stdout: nothing is written in the file, and nothing has to sit in your environment where every other process can read it.
146
+
147
+ Managing them:
148
+
149
+ | Command | What it does |
150
+ |---|---|
151
+ | `airlok mcp list` | every server, its scope, whether it answers, and its tools |
152
+ | `airlok mcp add <name> --scope user -- npx -y server-filesystem .` | write a stdio server; `--url` with `--transport http` for an http one |
153
+ | `airlok mcp get <name>` | the resolved entry and which file it came from |
154
+ | `airlok mcp remove <name> [--scope ...]` | take it out again |
155
+ | `airlok mcp import <path>` | merge another tool's `mcpServers` file into a scope |
156
+ | `airlok mcp export [--scope ...]` | print standard JSON, to hand to another tool |
157
+ | `airlok mcp call <server> <tool> '<json>'` | call one tool through the same gates, for debugging |
158
+ | `airlok mcp trust list` and `trust revoke <server>` | approvals remembered past a run |
159
+ | `/mcp` | in a session, the same list; `/mcp <name>` enables a disabled server for that session |
160
+
161
+ ### airlok's own options
162
+
163
+ Everything airlok adds lives under an `airlok` key, so a plain config stays plain:
164
+
165
+ ```json
166
+ {
167
+ "mcpServers": {
168
+ "docs": {
169
+ "type": "http",
170
+ "url": "https://example.com/mcp",
171
+ "airlok": {
172
+ "trust": "prompt",
173
+ "rehydrate": false,
174
+ "tools": ["search"],
175
+ "header_cmd": { "Authorization": "printf 'Bearer %s' $(cat ~/.docs-token)" }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ The same options are available as `[[mcp]]` blocks in `config.toml` or `airlok.toml`, which keep working and win a name clash with any JSON file:
119
183
 
120
184
  ```toml
121
185
  [[mcp]]
122
- name = "files" # its tools reach the model as files__<tool>
186
+ name = "files"
123
187
  command = "npx"
124
188
  args = ["-y", "@modelcontextprotocol/server-filesystem", "."]
125
- # env = { NODE_ENV = "production" }
126
- # env_cmd = { TOKEN = "op read op://vault/item/token" } # stdout is the value
127
-
128
- [[mcp]]
129
- name = "docs"
130
- transport = "http"
131
- url = "https://example.com/mcp"
132
- headers = { Accept = "application/json" }
133
- header_cmd = { Authorization = "printf 'Bearer %s' $(cat ~/.docs-token)" }
134
- tools = ["search"] # or "all", the default
135
- trust = "prompt" # "prompt" (default), "allow", "deny"
136
- rehydrate = false # the default
137
- timeout_secs = 30 # starting the server, and every call
189
+ trust = "prompt" # "prompt" (default), "allow", "deny"
190
+ rehydrate = false # the default
191
+ tools = "all" # or a list: ["read_text_file"]
192
+ timeout_secs = 30
138
193
  enabled = true
139
194
  ```
140
195
 
141
- `env_cmd` and `header_cmd` take their values from a command's stdout, the way `api_key_cmd` does, so a token never sits in the file. `airlok config show` prints the command, never what it produced.
196
+ ### What a server may and may not do
142
197
 
143
- Servers start on the first turn that can use them, not when airlok starts. One that fails to start prints a line and is skipped, and the run continues without it. Tools are named `<server>__<tool>`; a built-in keeps its name if a server ever claims one.
198
+ Servers start on the first turn that can use them, not when airlok starts. One that fails to start prints a line and is skipped, and the run continues without it. Tools reach the model as `mcp__<server>__<tool>`; a built-in keeps its name if a server ever claims one.
144
199
 
145
- `trust` decides the gate. The default, `prompt`, shows the server, the tool, and the arguments, and asks with the same `[y]es / [n]o / [a]ll / [q]uit` prompt as a shell command, where `a` approves that one server for the rest of the run. `allow` never asks. `deny` keeps a server's tools from the model, while `airlok mcp list` still shows them.
200
+ `trust` decides the gate. The default, `prompt`, shows the server, the tool, what the server can reach, the place each argument names, and the arguments themselves, then asks with `[y]es / [n]o / [a]ll / [s]ave / [q]uit`. `a` covers that tool on that server for the rest of the run, for the places that call named and no others. `s` remembers the same approval past the run in `.airlok/mcp-trust.json`, which is gitignored and written 0600; it records what the server was, so changing its command or url makes it ask again. `allow` never asks, and `deny` keeps a server's tools from the model while `airlok mcp list` still shows them.
146
201
 
147
- `rehydrate` decides what the server receives. By default airlok sends placeholders: a secret found in your files leaves as `<<SECRET_1>>` rather than as the value, because an MCP server is a third party in the same way the model is, and the confirmation prompt shows you exactly what will be sent. Set `rehydrate = true` for a server that genuinely needs the value. The provider API key is refused either way, as it is for every tool.
202
+ `rehydrate` decides what the server receives. By default airlok sends placeholders: a secret found in your files leaves as `<<SECRET_1>>` rather than as the value, and the confirmation shows you exactly what will be sent. Set `rehydrate = true` for a server that genuinely needs the value. The provider API key is refused either way.
148
203
 
149
204
  A server is untrusted input. Its tool descriptions and its results reach the model inside markers saying they are data from that server, so a description reading "ignore previous instructions" is quoted text and nothing more. Nothing a server sends changes the deny list, the confirmations, or anything else about how airlok behaves.
150
205
 
151
- | Command | What it does |
152
- |---|---|
153
- | `airlok mcp list` | every configured server, whether it answers, and the tools it offers |
154
- | `airlok mcp call <server> <tool> '<json>'` | call one tool with the same gates, for debugging |
155
- | `/mcp` | in a session, the same list; `/mcp <name>` enables a disabled server for this session |
156
-
157
206
  Plan mode offers no MCP tools, just as it offers no write tools, and it starts no servers.
158
207
 
159
208
  ## Context and AIRLOK.md
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT OR Apache-2.0",
21
21
  "name": "airlok",
22
- "version": "0.6.0"
22
+ "version": "0.7.0"
23
23
  },
24
24
  "node_modules/detect-libc": {
25
25
  "engines": {
@@ -48,5 +48,5 @@
48
48
  }
49
49
  },
50
50
  "requires": true,
51
- "version": "0.6.0"
51
+ "version": "0.7.0"
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/airlok-dev/airlok/releases/download/v0.6.0"
3
+ "https://github.com/airlok-dev/airlok/releases/download/v0.7.0"
4
4
  ],
5
5
  "bin": {
6
6
  "airlok": "run-airlok.js"
@@ -60,7 +60,7 @@
60
60
  "zipExt": ".tar.xz"
61
61
  }
62
62
  },
63
- "version": "0.6.0",
63
+ "version": "0.7.0",
64
64
  "volta": {
65
65
  "node": "18.14.1",
66
66
  "npm": "9.5.0"