airlok 0.6.1 → 0.8.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,33 @@
2
2
 
3
3
  All notable changes to airlok. The format follows Keep a Changelog; versions follow SemVer.
4
4
 
5
+ ## [0.8.0] - 2026-09-12
6
+
7
+ ### Changed
8
+
9
+ - A server from the project's own `.mcp.json` no longer starts until this checkout has been asked about it. On first sight of one, or of a definition that changed since the last answer, airlok lists the servers and the commands they would run and asks once; the answer is recorded per repository in `.airlok/`, which is gitignored. Until then the server is pending and nothing is started. This is a behaviour change: a project file that used to start servers on the first turn now waits for an answer. Servers from the user file, the local file, or a `[[mcp]]` block are unaffected.
10
+ - `airlok mcp list` shows a pending server and what it would run, and listing never approves anything. `airlok mcp call` refuses a server this repository has not approved.
11
+
12
+ ### Added
13
+
14
+ - `airlok mcp reset-project-choices` forgets the answer, so the next run asks again.
15
+ - `airlok mcp add-json <name> '<json>'` writes a server from an entry pasted as JSON, which is how one is usually shared.
16
+
17
+ ## [0.7.0] - 2026-09-12
18
+
19
+ ### Changed
20
+
21
+ - 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.
22
+
23
+ ### Added
24
+
25
+ - 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.
26
+ - 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.
27
+ - Airlok's own options live under an `airlok` key inside a JSON entry, so a plain config stays plain.
28
+ - `${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.
29
+ - `airlok mcp add`, `remove`, `get`, `import`, and `export` manage those files, and `airlok mcp list` now shows which scope each server came from.
30
+ - 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.
31
+
5
32
  ## [0.6.1] - 2026-09-12
6
33
 
7
34
  ### Fixed
package/README.md CHANGED
@@ -115,45 +115,98 @@ 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
+ A `.mcp.json` arrives with a clone, so a project-scoped server starts only once this checkout has been asked about it. On first sight of one, or of a definition that changed since the last answer, airlok lists the servers and the commands they would run and asks once. The answer is recorded in `.airlok/`, per repository and gitignored. Until then the server shows as pending in `airlok mcp list` and nothing is started, and listing never counts as approving. `airlok mcp reset-project-choices` forgets the answer so the next run asks again. Servers from your own user file, your local file, or a `[[mcp]]` block are not gated: you wrote those.
146
+
147
+ `${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.
148
+
149
+ Managing them:
150
+
151
+ | Command | What it does |
152
+ |---|---|
153
+ | `airlok mcp list` | every server, its scope, whether it answers, and its tools |
154
+ | `airlok mcp add <name> --scope user -- npx -y server-filesystem .` | write a stdio server; `--url` with `--transport http` for an http one |
155
+ | `airlok mcp add-json <name> '<json>'` | write a server from an entry pasted as JSON |
156
+ | `airlok mcp reset-project-choices` | forget whether this repository's own `.mcp.json` servers may start |
157
+ | `airlok mcp get <name>` | the resolved entry and which file it came from |
158
+ | `airlok mcp remove <name> [--scope ...]` | take it out again |
159
+ | `airlok mcp import <path>` | merge another tool's `mcpServers` file into a scope |
160
+ | `airlok mcp export [--scope ...]` | print standard JSON, to hand to another tool |
161
+ | `airlok mcp call <server> <tool> '<json>'` | call one tool through the same gates, for debugging |
162
+ | `airlok mcp trust list` and `trust revoke <server>` | approvals remembered past a run |
163
+ | `/mcp` | in a session, the same list; `/mcp <name>` enables a disabled server for that session |
164
+
165
+ ### airlok's own options
166
+
167
+ Everything airlok adds lives under an `airlok` key, so a plain config stays plain:
168
+
169
+ ```json
170
+ {
171
+ "mcpServers": {
172
+ "docs": {
173
+ "type": "http",
174
+ "url": "https://example.com/mcp",
175
+ "airlok": {
176
+ "trust": "prompt",
177
+ "rehydrate": false,
178
+ "tools": ["search"],
179
+ "header_cmd": { "Authorization": "printf 'Bearer %s' $(cat ~/.docs-token)" }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ 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
187
 
120
188
  ```toml
121
189
  [[mcp]]
122
- name = "files" # its tools reach the model as files__<tool>
190
+ name = "files"
123
191
  command = "npx"
124
192
  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
193
+ trust = "prompt" # "prompt" (default), "allow", "deny"
194
+ rehydrate = false # the default
195
+ tools = "all" # or a list: ["read_text_file"]
196
+ timeout_secs = 30
138
197
  enabled = true
139
198
  ```
140
199
 
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.
200
+ ### What a server may and may not do
142
201
 
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.
202
+ 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
203
 
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.
204
+ `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
205
 
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.
206
+ `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
207
 
149
208
  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
209
 
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
210
  Plan mode offers no MCP tools, just as it offers no write tools, and it starts no servers.
158
211
 
159
212
  ## 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.1"
22
+ "version": "0.8.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.1"
51
+ "version": "0.8.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.1"
3
+ "https://github.com/airlok-dev/airlok/releases/download/v0.8.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.1",
63
+ "version": "0.8.0",
64
64
  "volta": {
65
65
  "node": "18.14.1",
66
66
  "npm": "9.5.0"