You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(tools): move download_* to a new local-write category
download_formula_field and download_base_formulas READ from Airtable but
WRITE .formula files to a caller-chosen path, so they did not belong in the
`read` category -- the read-only profile is advertised as "Schema inspection,
formula validation, and record reading only". Correcting the readOnlyHint
annotation (previous commit) restored the MCP client's consent prompt, but the
profile itself still claimed a file-writing tool was read-only.
New `local-write` category, included in safe-write and full, so those tool
sets are unchanged (54 / 72). Only read-only changes: 12 -> 10.
Deliberately NOT added to LEGACY_CATEGORIES_DEFAULT_ON, per the invariant
documented there -- a pre-existing on-disk `custom` profile must not silently
widen to include a category it never opted into.
Propagated through every mirror the sync guard checks: tool-config.js,
tool-profile.ts, shared types, extension package.json (new
mcp.categories.localWrite setting + enumDescriptions), webview store +
Settings toggle + store test, CLAUDE.md, all three READMEs, skill templates
and the three banner SVGs.
Verified: check:tool-sync green at 10/54/72 with 17 labels, 1451 mcp-server
+ 421 extension + 88 webview tests pass, pnpm build clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,7 +51,7 @@ Typed message protocol between extension host and webview. Exports `ExtensionMes
51
51
React 19 + Vite 6 + Tailwind CSS v4 + Zustand 5 dashboard. Three tabs: Overview, Setup, Settings. Builds directly into `packages/extension/dist/webview/`. Communicates with the extension host via `acquireVsCodeApi().postMessage()` — messages are typed through the shared package.
52
52
53
53
### packages/mcp-server
54
-
The Airtable MCP server itself — ES modules Node app, **published to npm as `airtable-user-mcp`**. Provides **72 tools** across 16 categories (read, record-read, table-write, table-destructive, field-write, field-destructive, view-write, view-destructive, view-section, view-section-destructive, form-write, extension, record-write, record-destructive, sync, daemon) via `@modelcontextprotocol/sdk`. Includes `upload_attachment` (record-write) which writes attachment cells by URL — the general `update_records` tool cannot set attachment cells. The `daemon` category holds exactly one tool, `manage_daemon` (`src/daemon/manage.js`) — the only way the model can see its own runtime: `action=status` reports daemon liveness/holder/transport/uptime/tunnel plus the live session state (`sessionDead`, the last breaker trip including Airtable's captured response body, the browser busy queue), which is what distinguishes *daemon gone* from *session dead* from *browser busy*. `start`/`restart`/`stop`/`tunnel_*`/`token_rotate` administer the process; `stop`/`restart` answer first and exit from a `res.on('finish')` hook in `daemon/server.js` (exiting from the handler deadlocks the SDK's `enableJsonResponse` promise), `stop` writes a `daemon.stopped` sentinel so the extension does not silently respawn, and `token_rotate`/`tunnel_*` are refused for tunnel-origin callers. `daemon` is **`full`-profile only** and defaults off for pre-existing `custom` profiles. Uses `patchright` (Chromium stealth fork) with a persistent profile for browser-based authentication against Airtable's internal API. **Transport:** API calls run through a direct node HTTP transport (`src/http-transport.js`; `fetch` default, `impit` Chrome-TLS-impersonation via `AIRTABLE_HTTP_CLIENT=impit`) — NOT through the browser page; the browser mints/refreshes the session cookie only. Proxy env (`HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`) is honored by the `fetch` client via undici's `EnvHttpProxyAgent` (`src/proxy.js`; `undici` is an optionalDependency — absent → direct, one warning); the `impit` client is NOT proxy-aware, and TLS-inspecting proxies additionally need `NODE_EXTRA_CA_CERTS` (Node ignores the OS trust store). Auth is cookie-only (no bearer/API key). `AIRTABLE_AUTH_MODE`: `browser` (default) | `byo` (cookie-only, no browser — `AIRTABLE_COOKIE` or `~/.airtable-user-mcp/credentials.json`, csrf auto-scraped; `src/byo-credentials.js`) | `direct-login` (browser-free login via impit + otpauth TOTP replaying the HTTP login flow; `src/direct-login.js`).
54
+
The Airtable MCP server itself — ES modules Node app, **published to npm as `airtable-user-mcp`**. Provides **72 tools** across 17 categories (read, record-read, table-write, table-destructive, field-write, field-destructive, view-write, view-destructive, view-section, view-section-destructive, form-write, extension, record-write, record-destructive, sync, daemon, local-write) via `@modelcontextprotocol/sdk`. Includes `upload_attachment` (record-write) which writes attachment cells by URL — the general `update_records` tool cannot set attachment cells. The `daemon` category holds exactly one tool, `manage_daemon` (`src/daemon/manage.js`) — the only way the model can see its own runtime: `action=status` reports daemon liveness/holder/transport/uptime/tunnel plus the live session state (`sessionDead`, the last breaker trip including Airtable's captured response body, the browser busy queue), which is what distinguishes *daemon gone* from *session dead* from *browser busy*. `start`/`restart`/`stop`/`tunnel_*`/`token_rotate` administer the process; `stop`/`restart` answer first and exit from a `res.on('finish')` hook in `daemon/server.js` (exiting from the handler deadlocks the SDK's `enableJsonResponse` promise), `stop` writes a `daemon.stopped` sentinel so the extension does not silently respawn, and `token_rotate`/`tunnel_*` are refused for tunnel-origin callers. `daemon` is **`full`-profile only** and defaults off for pre-existing `custom` profiles. Uses `patchright` (Chromium stealth fork) with a persistent profile for browser-based authentication against Airtable's internal API. **Transport:** API calls run through a direct node HTTP transport (`src/http-transport.js`; `fetch` default, `impit` Chrome-TLS-impersonation via `AIRTABLE_HTTP_CLIENT=impit`) — NOT through the browser page; the browser mints/refreshes the session cookie only. Proxy env (`HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`) is honored by the `fetch` client via undici's `EnvHttpProxyAgent` (`src/proxy.js`; `undici` is an optionalDependency — absent → direct, one warning); the `impit` client is NOT proxy-aware, and TLS-inspecting proxies additionally need `NODE_EXTRA_CA_CERTS` (Node ignores the OS trust store). Auth is cookie-only (no bearer/API key). `AIRTABLE_AUTH_MODE`: `browser` (default) | `byo` (cookie-only, no browser — `AIRTABLE_COOKIE` or `~/.airtable-user-mcp/credentials.json`, csrf auto-scraped; `src/byo-credentials.js`) | `direct-login` (browser-free login via impit + otpauth TOTP replaying the HTTP login flow; `src/direct-login.js`).
55
55
56
56
Standalone users install via `npx airtable-user-mcp` or `npm i -g airtable-user-mcp`. The CLI exposes subcommands: `login`, `logout`, `status`, `doctor`, `install-browser`, `daemon start/stop/status`. Config and session data live in `~/.airtable-user-mcp/`.
57
57
@@ -267,8 +267,8 @@ Located at `packages/mcp-server/dev-tools/` (gitignored):
267
267
268
268
All under `airtableFormula.*`:
269
269
-`mcp.autoConfigureOnInstall` — auto-write MCP config to detected IDEs on first launch
-`mcp.categories.{read,recordRead,recordWrite,recordDestructive,tableWrite,tableDestructive,fieldWrite,fieldDestructive,viewWrite,viewDestructive,viewSection,viewSectionDestructive,formWrite,extension,sync,daemon}` — per-category toggles when profile is `custom`. `sync`, `recordDestructive` and `daemon` default to **off**; the other 13 default to on.
-`mcp.categories.{read,recordRead,recordWrite,recordDestructive,tableWrite,tableDestructive,fieldWrite,fieldDestructive,viewWrite,viewDestructive,viewSection,viewSectionDestructive,formWrite,extension,sync,daemon,localWrite}` — per-category toggles when profile is `custom`. `sync`, `recordDestructive` and `daemon` default to **off**; the other 13 default to on.
272
272
-`mcp.daemonPort` — fixed TCP port for the shared MCP daemon HTTP server (default 8723, kept stable across restarts; 0 = automatic/ephemeral; falls back to an automatic port if the chosen port is busy; takes effect on next daemon restart)
273
273
-`mcp.authMode` — `browser` (default; headless Chrome mints the cookie, calls go direct-HTTP) / `byo` (cookie-only, no browser; cookie from `~/.airtable-user-mcp/credentials.json` or `AIRTABLE_COOKIE`) / `direct-login` (browser-free email+password+TOTP; `login.json` or `AIRTABLE_EMAIL/PASSWORD/TOTP_SECRET`). Injected as `AIRTABLE_AUTH_MODE` into the spawned server via `buildDaemonEnv`/`registration.ts` (non-default only). Takes effect on next daemon/server restart.
274
274
-`mcp.httpClient` — `fetch` (default) / `impit` (Chrome-TLS impersonation fallback). Injected as `AIRTABLE_HTTP_CLIENT`. `impit` must be available to the server (optionalDependency; add to the vendored deps for the bundled path).
Copy file name to clipboardExpand all lines: packages/extension/package.json
+6-1Lines changed: 6 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -531,7 +531,7 @@
531
531
"custom"
532
532
],
533
533
"enumDescriptions": [
534
-
"Schema inspection, formula validation, and record reading only (12 tools)",
534
+
"Schema inspection, formula validation, and record reading only (10 tools)",
535
535
"Read + record read/write + create/update tables, fields, views, sidebar sections, and record templates, no deletes, no form metadata (54 tools)",
536
536
"All tools enabled including destructive ops, form metadata, extensions, and daemon control (72 tools)",
537
537
"User-defined per-tool selection"
@@ -618,6 +618,11 @@
618
618
"default": false,
619
619
"description": "Daemon Control tool: manage_daemon (inspect and administer the MCP daemon itself — not Airtable). action=status is read-only and reports daemon liveness, transport, uptime, tunnel URL and the live session/browser state; start, restart, stop, tunnel_enable, tunnel_disable and token_rotate control the running process. Off by default: this lets an AI agent stop or restart the server it is talking through."
620
620
},
621
+
"airtableFormula.mcp.categories.localWrite": {
622
+
"type": "boolean",
623
+
"default": true,
624
+
"description": "Local File Write tools: download_formula_field, download_base_formulas. These READ from Airtable but WRITE .formula files to a path the caller chooses, so they are not part of the read-only profile. Included in safe-write and full."
'read-only': {description: 'Schema inspection, formula validation, and record reading only',categories: ['read','record-read']},
151
152
'safe-write':{description: 'Read + record read/write + create/update tables, fields, views, sidebar sections, and record templates (no deletes, no form metadata)',
0 commit comments