Skip to content

Commit eb87f60

Browse files
ARHAEEMclaude
andcommitted
fix(extension): fail closed on the recordDestructive/sync setting fallback
cfg.get(...) fallbacks for mcp.categories.recordDestructive and .sync were still `true`, contradicting Task 1's intent that both default off. Inert today (package.json registers a real default of false, which wins), but would silently widen if that settings block were ever renamed/unregistered. Flip both to `false`. docs(mcp-server): document the LEGACY_CATEGORIES_DEFAULT_ON granularity ceiling LEGACY_CATEGORIES_DEFAULT_ON gates absent tools-config.json keys by category, not tool name — so a standalone npm user with an on-disk custom profile who explicitly disabled Record Write can silently regain a *new* tool added to that already-adopted category (upload_attachment, and create_records/update_records if they postdate the user's config). Never affects delete_records (record-destructive is a new category, correctly excluded) and never affects VS Code extension users (syncSettingsToFile always writes an explicit key for all 71 tools). Strictly better than pre-pass behavior, which had no category gate at all. No resolution logic changed — documents the known limitation in CHANGELOG.md for npm consumers and adds a ponytail: comment at the allowlist definition naming the ceiling and the upgrade path (a frozen tool-name allowlist). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 58e5efd commit eb87f60

3 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/extension/src/mcp/tool-profile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,15 @@ export class ToolProfileManager implements vscode.Disposable {
270270
tableDestructive: cfg.get('mcp.categories.tableDestructive', true),
271271
fieldWrite: cfg.get('mcp.categories.fieldWrite', true),
272272
fieldDestructive: cfg.get('mcp.categories.fieldDestructive', true),
273-
recordDestructive: cfg.get('mcp.categories.recordDestructive', true),
273+
recordDestructive: cfg.get('mcp.categories.recordDestructive', false),
274274
viewWrite: cfg.get('mcp.categories.viewWrite', true),
275275
viewDestructive: cfg.get('mcp.categories.viewDestructive', true),
276276
viewSection: cfg.get('mcp.categories.viewSection', true),
277277
viewSectionDestructive: cfg.get('mcp.categories.viewSectionDestructive', true),
278278
formWrite: cfg.get('mcp.categories.formWrite', true),
279279
extension: cfg.get('mcp.categories.extension', true),
280280
recordWrite: cfg.get('mcp.categories.recordWrite', true),
281-
sync: cfg.get('mcp.categories.sync', true),
281+
sync: cfg.get('mcp.categories.sync', false),
282282
};
283283
return {
284284
profile,

packages/mcp-server/CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,49 @@
22

33
## [Unreleased]
44

5+
### Known limitations (2026-07-25 tool profile safety — standalone CLI users)
6+
7+
- **`LEGACY_CATEGORIES_DEFAULT_ON` is category-granular, not tool-name-granular —
8+
a *new tool in an already-adopted category* can still be silently regained.**
9+
This only affects **standalone `airtable-user-mcp` npm/CLI users** with a
10+
hand-edited or pre-existing `~/.airtable-user-mcp/tools-config.json` on
11+
`activeProfile: "custom"`**not** the VS Code extension, whose
12+
`syncSettingsToFile()` always writes an explicit `true`/`false` for all 71
13+
tools, so it never hits the absent-key resolution path described below.
14+
- **Who's affected:** a standalone user who set `activeProfile: "custom"` and
15+
explicitly turned the **Record Write** category off (`table-write`,
16+
`field-write`, `view-write`, etc. are equally affected — Record Write is
17+
just the concrete case that shipped on this branch) before this branch's
18+
new tools existed in their on-disk config.
19+
- **What they gain:** on upgrade, `upload_attachment` (new tool) — and
20+
`create_records`/`update_records` if those also postdate their config —
21+
resolve their absent `customTools` key to *enabled*, because
22+
`LEGACY_CATEGORIES_DEFAULT_ON` allowlists the whole `record-write`
23+
*category* (frozen at the 13 categories that existed when per-tool
24+
overrides were introduced), not the individual tool names within it. A
25+
tool with no key at all is judged solely by whether its *category* is
26+
legacy — it has no way to know the user's config predates the *tool*
27+
specifically, only the *category*.
28+
- **What they do NOT gain:** `delete_records``record-destructive` is a
29+
brand-new category on this branch and is correctly excluded from the
30+
allowlist, so it resolves absent keys to disabled as intended. Only
31+
record-**write** tools are affected, never destructive ones.
32+
- **Net effect vs. pre-hardening-pass behavior:** strictly better. Before this
33+
pass, ALL new tools (including `sync_base` and `delete_records`) resolved
34+
absent keys to enabled with no category gate at all; this pass closed that
35+
for every *new category*. The residual is narrower: only new *tools* inside
36+
a category the user had already adopted (and then explicitly disabled) can
37+
still slip through.
38+
- **Remedy:** re-run `manage_tools` (`get_tool_status` to see what's enabled,
39+
`toggle_tool`/`toggle_category` to turn `upload_attachment` back off if
40+
unwanted) or hand-edit `~/.airtable-user-mcp/tools-config.json` to add an
41+
explicit `"upload_attachment": false` entry. The real fix — a frozen
42+
tool-**name** allowlist alongside (or instead of) the category allowlist —
43+
is deliberately deferred: it is a semantic change to credential-adjacent
44+
tool-gating logic and does not belong in the closing commits of a
45+
pre-merge hardening pass. See the `ponytail:` comment on
46+
`LEGACY_CATEGORIES_DEFAULT_ON` in `src/tool-config.js`.
47+
548
### Changed (2026-07-25 tool profile safety, pre-merge hardening pass)
649

750
- **`safe-write` no longer includes `sync_base`.** `sync_base` reaches

packages/mcp-server/src/tool-config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,19 @@ export const CATEGORY_LABELS = {
153153
* custom profile" invariant is self-maintaining and can't be forgotten by a
154154
* future change the way a hand-maintained "which categories are new" list
155155
* could be. See CLAUDE.md's "Keeping tool categories in sync" checklist.
156+
*
157+
* ponytail: this allowlist is CATEGORY-granular, not tool-name-granular — its
158+
* ceiling. A new tool added to an already-legacy category (e.g. `upload_attachment`
159+
* / `create_records` / `update_records` joining `record-write`) is indistinguishable
160+
* here from a tool that predates the config; both resolve an absent key to enabled.
161+
* So a standalone npm user with an on-disk `custom` profile who explicitly turned
162+
* a legacy category off can silently regain a *new* tool in that category on
163+
* upgrade (never a new category — that's what this allowlist does guard). Affects
164+
* `airtable-user-mcp` CLI users only: the VS Code extension's `syncSettingsToFile`
165+
* always writes an explicit key for every tool, so it never hits the absent-key
166+
* path. See packages/mcp-server/CHANGELOG.md for the specific case this shipped
167+
* with. Real fix: freeze a tool-NAME allowlist instead of (or in addition to) this
168+
* category allowlist — deferred as a deliberate scope cut, not an oversight.
156169
*/
157170
const LEGACY_CATEGORIES_DEFAULT_ON = Object.freeze(new Set([
158171
'read', 'record-read',

0 commit comments

Comments
 (0)