Skip to content

Commit 004fa59

Browse files
author
王璨
committed
feat(permissions): fuzzy pattern matching for tool permissions
Add fuzzy/glob pattern support to the permission system, allowing users to grant broad patterns (e.g. "mcp__playcanvas__*", "git.*", "/src/*") instead of only exact tool+args matches. Core changes: - `deriveFuzzyPattern()` — auto-derives glob from tool name structure (MCP: mcp__<server>__*, non-MCP: tool name itself) - `deriveFuzzyArgPattern()` — derives regex from args (bash: first-word prefix, file tools: directory prefix) - `describeFuzzyArgPattern()` — human-readable version for UI display - Three-tier save in TUI/Web: exact / fuzzy tool / fuzzy args - Session grants extended with glob matching (sessionGrantPatterns) - All three permission levels (Allow/Always Allow/Save) support fuzzy LLM supplement: - `prefetchLlmSuggestions()` — async fire-and-forget LLM call to derive smarter patterns (e.g. "git push.*" vs "git.*"), cached per session - Results appear as [AI] options on subsequent prompts for same tool Bug fixes: - TUI sub-menu Escape now cancels sub-mode instead of denying - Sub-menu arrow navigation debounced (120ms) to prevent double-jumps - Web UI sub-menu pattern consistent with TUI (expand-on-click) Files: - src/permissions/fuzzy.ts, fuzzy-llm.ts (new) - src/permissions/manager.ts (session grants + glob, persistRule override) - src/ui/conversation.ts (sub-menu state machine, LLM option rendering) - src/ui/tui-app.ts (three-tier save, session grant options) - src/ui/web/web-backend.ts (fuzzy in protocol, LLM prefetch) - src/ui/shared/types.ts (PermissionPrompt, ClientCommand, ServerEvent) - web/src/components/* (sub-menu UI, LLM suggestions)
1 parent 9bbc7bd commit 004fa59

41 files changed

Lines changed: 2171 additions & 98 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-06-10
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## Context
2+
3+
`tool-name-glob``claude-code-permission-format` 已定义了 glob 规则存储和匹配能力。`permission-persist-ui` 定义了 "Always Allow (save)" 一键保存精确工具名。
4+
5+
缺口:保存时只有精确名选项。用户想保存 `mcp__playcanvas__*` 只能手动编辑 settings.json。
6+
7+
本变更在保存时自动推导模糊模式,与精确名并列为两个选项,用户一键选择,零编辑。
8+
9+
## Goals / Non-Goals
10+
11+
**Goals:**
12+
- "Always Allow (save)" 时同时展示精确和模糊两个选项
13+
- 模糊模式由系统自动推导,用户无需编辑
14+
- 对 MCP 工具推导 `mcp__<server>__*`;非 MCP 工具不展示模糊选项
15+
- TUI 和 Web 均支持
16+
17+
**Non-Goals:**
18+
- 手动编辑模式(不需要)
19+
- 多层模糊(不需要 mid-level glob)
20+
- 正则表达式
21+
- 会话级授权变更
22+
23+
## Decisions
24+
25+
### Decision 1: 自动推导,不做手动编辑
26+
27+
模糊模式完全由系统根据工具名结构推导。用户只需在精确/模糊之间二选一。
28+
29+
**Rationale:** 90% 的模糊需求是「允许这个 MCP server 的所有工具」——`mcp__<server>__*`。不需要用户自己敲 `*`
30+
31+
### Decision 2: 推导规则
32+
33+
- MCP 工具 (`mcp__<server>__<rest>`) → 模糊选项: `mcp__<server>__*`
34+
- 非 MCP 工具(无 `__` 分隔符)→ 不展示模糊选项
35+
- 未来可扩展:识别更多命名空间模式
36+
37+
### Decision 3: TUI 交互
38+
39+
`s` 后展开子选项:
40+
```
41+
[s] Always Allow (save)
42+
[1] exact: mcp__playcanvas__create_scene
43+
[2] fuzzy: mcp__playcanvas__*
44+
```
45+
`1` 保存精确,按 `2` 保存模糊。ESC 返回主选项。
46+
47+
### Decision 4: Web 交互
48+
49+
点击 "Save as Rule" 展开两个子按钮:
50+
- "Exact: mcp__playcanvas__create_scene"
51+
- "Fuzzy: mcp__playcanvas__*"
52+
53+
点击即保存,无额外确认。
54+
55+
## Risks / Trade-offs
56+
57+
- **Risk:** 用户误选模糊选项,授权范围超出预期 → **Mitigation:** 模糊选项始终显示完整的 pattern 字符串,用户看清楚再选
58+
- **Risk:** 非 MCP 工具的模糊模式不够明显 → 当前不做,后续可加
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## Why
2+
3+
The permission tool's "Always Allow (save)" only saves exact tool name matches (e.g., `mcp__playcanvas__create_scene`). When a different tool from the same MCP server is invoked, the user must re-grant permission. Users want to save a fuzzy pattern like `mcp__playcanvas__*` directly from the permission prompt — without manual editing or opening settings.json.
4+
5+
## What Changes
6+
7+
- When user selects "Always Allow (save)", present **two options**: exact (`mcp__playcanvas__create_scene`) and fuzzy (`mcp__playcanvas__*`)
8+
- The fuzzy pattern is **auto-derived** from the tool name structure — no manual editing needed
9+
- For MCP tools (`mcp__<server>__<tool>`), the fuzzy pattern is `mcp__<server>__*`
10+
- For non-namespaced tools (e.g., `bash`, `read_file`), only the exact option is shown (no fuzzy)
11+
- TUI: pressing `s` shows exact/fuzzy sub-options; user picks one with a key
12+
- Web: "Save as Rule" expands to show two buttons: "Exact" and "Fuzzy (`mcp__<server>__*`)"
13+
14+
## Capabilities
15+
16+
### New Capabilities
17+
18+
- `permission-fuzzy-save`: Auto-derived fuzzy pattern option in the "Always Allow (save)" flow. System derives the fuzzy pattern from the tool name and presents it alongside the exact option as a simple two-way choice.
19+
20+
### Modified Capabilities
21+
22+
- `permission-persist-ui`: "Always Allow (save)" now expands to a two-option choice (exact / fuzzy) instead of immediately saving the exact name. The persist payload carries the selected pattern.
23+
- `claude-code-permission-format`: `persistRule` now accepts an optional `toolNamePattern` override — when provided, it is written to settings instead of the prompt's exact tool name.
24+
25+
## Impact
26+
27+
- **TUI**: permission prompt key handler — `s` expands to sub-options
28+
- **Web**: `PermissionDialog` — "Save as Rule" expands to two buttons
29+
- **Shared types**: `PermissionPrompt` may carry an optional `fuzzyPattern: string` field
30+
- **Wire protocol**: `ClientCommand.permission` may need a `toolNamePattern` field
31+
- **PermissionManager**: `persistRule()` accepts optional pattern override
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: PersistRule writes allow/deny format
4+
When `persistRule()` writes a new permission rule to settings.json, it SHALL use the `allow` or `deny` array format rather than the `rules` object array. The tool name SHALL be written as-is (including any `*` wildcard). If `persistRule()` receives an optional `toolNamePattern` parameter, that pattern SHALL be used as the tool name in the persisted rule instead of the prompted tool name.
5+
6+
#### Scenario: Persist allow rule to settings
7+
- **WHEN** user selects "Always Allow (save)" with exact for `mcp__lsp__textDocument_hover`
8+
- **THEN** settings.json `permissions.allow` SHALL contain `"mcp__lsp__textDocument_hover"` and `permissions.rules` SHALL be unchanged
9+
10+
#### Scenario: Persist allow rule with fuzzy pattern
11+
- **WHEN** `persistRule()` is called with `{tool: "mcp__lsp__textDocument_hover", decision: "allow"}` and `toolNamePattern: "mcp__lsp__*"`
12+
- **THEN** settings.json `permissions.allow` SHALL contain `"mcp__lsp__*"` (NOT the original `mcp__lsp__textDocument_hover`)
13+
14+
#### Scenario: Persist deny rule to settings
15+
- **WHEN** a deny rule with `{tool: "mcp__danger__*", decision: "deny"}` is persisted
16+
- **THEN** settings.json `permissions.deny` SHALL contain `"mcp__danger__*"`
17+
18+
#### Scenario: Existing allow array is appended
19+
- **WHEN** settings.json already has `"allow": ["read_file"]` and a new allow rule for `bash` is persisted
20+
- **THEN** `"allow"` SHALL become `["read_file", "bash"]` (no duplicates)
21+
22+
#### Scenario: Duplicate fuzzy pattern not appended
23+
- **WHEN** settings.json already has `"allow": ["mcp__lsp__*"]` and `persistRule()` is called with `toolNamePattern: "mcp__lsp__*"`
24+
- **THEN** `"allow"` SHALL remain `["mcp__lsp__*"]` (no duplicate entry)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Fuzzy pattern auto-derived for MCP tools
4+
When a permission prompt is shown for an MCP tool (name matching `mcp__<server>__<rest>`), the system SHALL auto-derive a fuzzy glob pattern `mcp__<server>__*` and present it alongside the exact tool name as a save option.
5+
6+
#### Scenario: Fuzzy pattern derived for MCP tool
7+
- **WHEN** permission is prompted for `mcp__playcanvas__create_scene`
8+
- **THEN** the fuzzy save option SHALL display `mcp__playcanvas__*`
9+
10+
#### Scenario: No fuzzy pattern for non-MCP tool
11+
- **WHEN** permission is prompted for `bash`
12+
- **THEN** no fuzzy save option SHALL be presented (only exact)
13+
14+
#### Scenario: No fuzzy pattern for non-namespaced tool
15+
- **WHEN** permission is prompted for `read_file`
16+
- **THEN** no fuzzy save option SHALL be presented (only exact)
17+
18+
### Requirement: TUI presents exact/fuzzy sub-options on save
19+
When the user presses `s` ("Always Allow (save)") in the TUI permission prompt and a fuzzy pattern can be derived, the TUI SHALL display sub-options: `[1] exact: <toolName>` and `[2] fuzzy: <fuzzyPattern>`. The TUI SHALL accept `1` to save the exact name, `2` to save the fuzzy pattern, and `Escape` to return to the main prompt.
20+
21+
#### Scenario: TUI shows fuzzy sub-option
22+
- **WHEN** user presses `s` for `mcp__playcanvas__create_scene`
23+
- **THEN** the TUI SHALL display:
24+
```
25+
[1] exact: mcp__playcanvas__create_scene
26+
[2] fuzzy: mcp__playcanvas__*
27+
```
28+
29+
#### Scenario: TUI saves exact on key 1
30+
- **WHEN** user presses `1` in the sub-option menu
31+
- **THEN** `persistRule` SHALL contain `{tool: "mcp__playcanvas__create_scene", decision: "allow"}`
32+
33+
#### Scenario: TUI saves fuzzy on key 2
34+
- **WHEN** user presses `2` in the sub-option menu
35+
- **THEN** `persistRule` SHALL contain `{tool: "mcp__playcanvas__*", decision: "allow"}`
36+
37+
#### Scenario: TUI Escape returns to main prompt
38+
- **WHEN** the sub-option menu is shown and user presses Escape
39+
- **THEN** the main permission prompt options SHALL be re-displayed
40+
41+
#### Scenario: TUI skips sub-options when no fuzzy pattern
42+
- **WHEN** user presses `s` for `bash` (no fuzzy derivable)
43+
- **THEN** the exact name SHALL be saved immediately (no sub-option menu)
44+
45+
### Requirement: Web presents exact/fuzzy sub-buttons on save
46+
When the user clicks "Save as Rule" in the Web permission dialog and a fuzzy pattern can be derived, the dialog SHALL display two buttons: "Exact: `<toolName>`" and "Fuzzy: `<fuzzyPattern>`". Clicking either button SHALL immediately send the persist command with the corresponding pattern.
47+
48+
#### Scenario: Web shows fuzzy sub-button
49+
- **WHEN** user clicks "Save as Rule" for `mcp__playcanvas__create_scene`
50+
- **THEN** two buttons SHALL appear:
51+
- "Exact: mcp__playcanvas__create_scene"
52+
- "Fuzzy: mcp__playcanvas__*"
53+
54+
#### Scenario: Web sends exact persist
55+
- **WHEN** user clicks "Exact: mcp__playcanvas__create_scene"
56+
- **THEN** a `ClientCommand` of type `permission` with `decision: "always_allow_save"` SHALL be sent (no `toolNamePattern`)
57+
58+
#### Scenario: Web sends fuzzy persist
59+
- **WHEN** user clicks "Fuzzy: mcp__playcanvas__*"
60+
- **THEN** a `ClientCommand` of type `permission` with `decision: "always_allow_save"` and `toolNamePattern: "mcp__playcanvas__*"` SHALL be sent
61+
62+
#### Scenario: Web skips sub-buttons when no fuzzy pattern
63+
- **WHEN** user clicks "Save as Rule" for `bash` (no fuzzy derivable)
64+
- **THEN** the exact name SHALL be saved immediately (no sub-buttons)
65+
66+
### Requirement: Fuzzy pattern derivation function
67+
The system SHALL provide a `deriveFuzzyPattern(toolName: string): string | null` function. For tool names matching `mcp__<server>__<rest>`, it SHALL return `mcp__<server>__*`. For all other tool names, it SHALL return `null`.
68+
69+
#### Scenario: Derive from MCP tool
70+
- **WHEN** `deriveFuzzyPattern("mcp__lsp__textDocument_hover")` is called
71+
- **THEN** it SHALL return `"mcp__lsp__*"`
72+
73+
#### Scenario: Null for non-MCP tool
74+
- **WHEN** `deriveFuzzyPattern("bash")` is called
75+
- **THEN** it SHALL return `null`
76+
77+
#### Scenario: Null for bare mcp prefix
78+
- **WHEN** `deriveFuzzyPattern("mcp__")` is called
79+
- **THEN** it SHALL return `null`
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: TUI permission dialog includes save option
4+
The TUI permission prompt SHALL present options: "Allow", "Always Allow", "Always Allow (save)", and "Deny". When the user selects "Always Allow (save)" and a fuzzy pattern can be derived from the tool name, the TUI SHALL display a sub-menu with exact and fuzzy options before saving. The "Input Idea" option SHALL be removed. The save option SHALL use key "s" with a distinct color.
5+
6+
#### Scenario: TUI shows save option
7+
- **WHEN** the agent requests permission for tool `mcp__lsp__textDocument_definition`
8+
- **THEN** the TUI SHALL display options: `[Allow] [Always Allow] [Always Allow (save)] [Deny]`
9+
10+
#### Scenario: Save option writes exact name to settings.json
11+
- **WHEN** user selects "Always Allow (save)" for `mcp__lsp__textDocument_definition` and chooses exact
12+
- **THEN** `PermissionPromptResult` SHALL have `decision: "allow"`, `rememberForSession: true`, and `persistRule: {tool: "mcp__lsp__textDocument_definition", decision: "allow"}`
13+
14+
#### Scenario: Save option writes fuzzy pattern to settings.json
15+
- **WHEN** user selects "Always Allow (save)" for `mcp__lsp__textDocument_definition` and chooses fuzzy
16+
- **THEN** `PermissionPromptResult` SHALL have `decision: "allow"`, `rememberForSession: true`, and `persistRule: {tool: "mcp__lsp__*", decision: "allow"}`
17+
18+
#### Scenario: Save option key binding
19+
- **WHEN** user presses "s" during permission prompt
20+
- **THEN** the "Always Allow (save)" SHALL be activated (showing sub-options if fuzzy is derivable)
21+
22+
### Requirement: Web UI permission dialog includes save button
23+
The Web `PermissionDialog` component SHALL present a "Save as Rule" button alongside "Allow", "Always Allow", and "Deny". When clicked and a fuzzy pattern can be derived, the save area SHALL expand to show "Exact" and "Fuzzy" sub-buttons.
24+
25+
#### Scenario: Web UI shows save button
26+
- **WHEN** a permission prompt is active in the Web UI
27+
- **THEN** four buttons SHALL be displayed: `[Allow] [Always Allow] [Save as Rule] [Deny]`
28+
29+
#### Scenario: Web save button sends persist command (exact)
30+
- **WHEN** user clicks "Save as Rule" then "Exact" in Web UI
31+
- **THEN** a `ClientCommand` of type `permission` with `decision: "always_allow_save"` SHALL be sent to the backend
32+
33+
#### Scenario: Web save button sends persist command (fuzzy)
34+
- **WHEN** user clicks "Save as Rule" then "Fuzzy: mcp__playcanvas__*" in Web UI
35+
- **THEN** a `ClientCommand` of type `permission` with `decision: "always_allow_save"` and `toolNamePattern: "mcp__playcanvas__*"` SHALL be sent to the backend
36+
37+
### Requirement: Wire protocol supports always_allow_save
38+
The `ClientCommand.permission.decision` type SHALL include `"always_allow_save"` as a valid value. The `PermOption.value` type in shared types SHALL include `"always_allow_save"`. The `ClientCommand.permission` payload SHALL include an optional `toolNamePattern: string` field.
39+
40+
#### Scenario: Backend receives always_allow_save
41+
- **WHEN** Web UI sends `{type: "permission", decision: "always_allow_save"}`
42+
- **THEN** the web backend SHALL resolve the permission with `decision: "allow"`, `rememberForSession: true`, and `persistRule: {tool: <current tool>, decision: "allow"}`
43+
44+
#### Scenario: Backend receives always_allow_save with toolNamePattern
45+
- **WHEN** Web UI sends `{type: "permission", decision: "always_allow_save", toolNamePattern: "mcp__lsp__*"}`
46+
- **THEN** the web backend SHALL resolve the permission with `decision: "allow"`, `rememberForSession: true`, and `persistRule: {tool: "mcp__lsp__*", decision: "allow"}`
47+
48+
### Requirement: Session grants remain exact match
49+
The "Always Allow" (session-level) option SHALL continue to use `sessionGrants` which stores exact tool names only. Session grants SHALL NOT support glob patterns.
50+
51+
#### Scenario: Session grant is exact
52+
- **WHEN** user selects "Always Allow" for `mcp__lsp__textDocument_hover`
53+
- **THEN** only `mcp__lsp__textDocument_hover` is added to session grants; `mcp__lsp__textDocument_definition` is NOT automatically granted
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## 1. Shared types & protocol
2+
3+
- [x] 1.1 Add optional `toolNamePattern: string` field to `ClientCommand.permission` in shared types
4+
- [x] 1.2 Add optional `toolNamePattern` parameter to `persistRule` function signature
5+
- [x] 1.3 Update `PermissionPromptResult` to carry `toolNamePattern` when fuzzy is selected
6+
7+
## 2. Fuzzy pattern derivation
8+
9+
- [x] 2.1 Add `deriveFuzzyPattern(toolName: string): string | null` — for MCP tools (`mcp__<server>__<rest>`) returns `mcp__<server>__*`, otherwise `null`
10+
- [x] 2.2 Add `fuzzyPattern` to `PermissionPrompt` type so UI can display it
11+
12+
## 3. PermissionManager persistRule changes
13+
14+
- [x] 3.1 Update `persistRule()` to accept optional `toolNamePattern` override
15+
- [x] 3.2 When `toolNamePattern` provided, write it to `permissions.allow`/`permissions.deny` instead of prompt tool name
16+
- [x] 3.3 Skip appending if `toolNamePattern` already exists in the target array
17+
18+
## 4. TUI permission prompt
19+
20+
- [x] 4.1 When user presses `s` and `fuzzyPattern` exists, show sub-options: `[1] exact: <tool>` / `[2] fuzzy: <pattern>`
21+
- [x] 4.2 Key `1` saves exact name, key `2` saves fuzzy pattern, Escape returns to main prompt
22+
- [x] 4.3 When `fuzzyPattern` is null, `s` saves exact name immediately (no sub-menu)
23+
- [x] 4.4 Wire result into `PermissionPromptResult` with appropriate `toolNamePattern`
24+
25+
## 5. Web permission dialog
26+
27+
- [x] 5.1 When user clicks "Save as Rule" and `fuzzyPattern` exists, expand to show "Exact" and "Fuzzy" buttons
28+
- [x] 5.2 Click "Exact" → send persist without `toolNamePattern`
29+
- [x] 5.3 Click "Fuzzy" → send persist with `toolNamePattern`
30+
- [x] 5.4 When `fuzzyPattern` is null, "Save as Rule" saves exact name immediately (no expansion)
31+
32+
## 6. Integration & cleanup
33+
34+
- [x] 6.1 Ensure "Always Allow (save)" exact path works identically to before — no regression
35+
- [x] 6.2 Ensure session grants (`sessionGrants`) remain exact match only
36+
- [x] 6.3 End-to-end: prompt `mcp__playcanvas__create_scene` → save fuzzy `mcp__playcanvas__*` → next `mcp__playcanvas__delete_scene` call auto-allowed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-06-10
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
## Context
2+
3+
The MCP tool naming convention uses `__` (double underscore) as the delimiter: `mcp__<server>__<tool>`. This pattern is already documented in existing specs (`tool-name-glob`, `permission-persist-ui`, `permission-project-persist`), but the implementation in `src/mcp/manager.ts` and `src/drivers/tool-registry.ts` uses single `_` instead.
4+
5+
Single underscore is ambiguous: for a tool like `mcp_lsp_textDocument_hover`, it's impossible to determine where the server name ("lsp") ends and the tool name begins without external knowledge. With `__`, the boundary is explicit: `mcp__lsp__textDocument_hover`.
6+
7+
## Goals / Non-Goals
8+
9+
**Goals:**
10+
- Use `__` as the separator for MCP tool names and driver names, matching the spec convention
11+
- Update prefix extraction in `tool-registry.ts` to split on `__` for correct server grouping
12+
13+
**Non-Goals:**
14+
- No migration tool for existing persisted permissions (low blast radius at this stage)
15+
- No change to the MCP wire protocol or MCP server configurations
16+
- No change to how tools are discovered or loaded — only the naming changes
17+
18+
## Decisions
19+
20+
### Decision 1: Separator format: `mcp__<server>__<tool>`
21+
22+
Both boundaries use `__`: between `mcp` and `<server>`, and between `<server>` and `<tool>`.
23+
24+
```typescript
25+
// Before (buggy):
26+
const toolName = `mcp_${serverName}_${def.name}`;
27+
// Result: mcp_lsp_textDocument_hover — ambiguous
28+
29+
// After (correct):
30+
const toolName = `mcp__${serverName}__${def.name}`;
31+
// Result: mcp__lsp__textDocument_hover — unambiguous
32+
```
33+
34+
The driver name also uses `__`:
35+
```typescript
36+
// Before:
37+
name: `mcp_${serverName}` // "mcp_lsp"
38+
// After:
39+
name: `mcp__${serverName}` // "mcp__lsp"
40+
```
41+
42+
### Decision 2: Prefix extraction
43+
44+
The grouping logic in `tool-registry.ts` must split on `__`:
45+
46+
```typescript
47+
// Before:
48+
const prefix = name.startsWith("mcp_")
49+
? name.split("_").slice(0, 2).join("_")
50+
: "other";
51+
52+
// After:
53+
const prefix = name.startsWith("mcp__")
54+
? name.split("__").slice(0, 2).join("__")
55+
: "other";
56+
```
57+
58+
For `mcp__lsp__textDocument_hover`, `split("__")` gives `["mcp", "lsp", "textDocument_hover"]`, and `slice(0,2).join("__")` gives `"mcp__lsp"` — the correct server group.
59+
60+
### Decision 3: No migration tool
61+
62+
Persisted permissions in `.dscode/settings.json` that use the old single-underscore format will silently stop matching because `matchesTool` does exact/glob comparison. Users will need to re-save their MCP tool permissions. Given the early stage of the project, this is acceptable — the alternative (writing a migration) adds complexity with minimal benefit.
63+
64+
## Risks / Trade-offs
65+
66+
- **[Risk]** Users with existing `permissions.allow` entries using `mcp_lsp_*` will have broken rules → **Mitigation**: Document in release notes. This is a developer tool in early development; blast radius is minimal.
67+
- **[Risk]** External code or scripts that depend on the old naming format → **Mitigation**: The naming is internal-only. No external API is affected.
68+
- **[Trade-off]** `__` is visually subtle but unambiguous → Chosen over alternatives like `:` (which could conflict with MCP URI schemes) or `/` (which looks like a path).

0 commit comments

Comments
 (0)