Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9366394
feat(policies): add permission support for MCP server connections
laststylebender14 May 12, 2026
1eeae1e
refactor(policies): remove directory scoping from MCP permission rules
laststylebender14 May 12, 2026
98f4a7c
feat(policies): differentiate user and local MCP server permissions
laststylebender14 May 12, 2026
6e4390b
feat(policies): add directory glob matching to MCP permission rules
laststylebender14 May 12, 2026
26e571a
feat(policies): add allow_operation for explicit MCP opt-ins
laststylebender14 May 12, 2026
77685fa
feat(policies): add allow_operation method for permission handling in…
laststylebender14 May 12, 2026
efa3e45
feat(tests): update MCP rule directory path in tests
laststylebender14 May 12, 2026
cd349bd
[autofix.ci] apply automated fixes
autofix-ci[bot] May 12, 2026
b536d2b
feat(policies): add is_operation_permitted for non-interactive checks
laststylebender14 May 13, 2026
d0f94df
feat(policies): replace MCP scope filtering with config-based matching
laststylebender14 May 13, 2026
1b1376b
feat(mcp): show permission-denied warnings for blocked servers
laststylebender14 May 13, 2026
6b27681
feat(mcp): show actual permissions path in blocked server warning
laststylebender14 May 13, 2026
a207aa5
feat(ui): add PolicyNotice component for policy-blocked items
laststylebender14 May 13, 2026
70202a5
feat(ui): allow PolicyNotice row with empty value as section header
laststylebender14 May 13, 2026
4e80cf5
feat(mcp): trust user-scoped servers by default, policy only for local
laststylebender14 May 13, 2026
11e7662
refactor(ui): make PolicyNotice docs rows composable with labels
laststylebender14 May 13, 2026
7a94339
Merge branch 'main' into feat/use-permissions-for-mcp-connections
laststylebender14 May 13, 2026
65e1f9c
refactor(policy_notice): use Path instead of PathBuf in tilde_path
laststylebender14 May 13, 2026
c9ec4ef
[autofix.ci] apply automated fixes
autofix-ci[bot] May 13, 2026
b175ce4
refactor(mcp): parallelize server permission checks
laststylebender14 May 13, 2026
f511f06
[autofix.ci] apply automated fixes
autofix-ci[bot] May 13, 2026
454c658
fix(ui): clarify MCP warning to specify local scope servers
laststylebender14 May 13, 2026
b818ce4
refactor(mcp): remove permission warning handling and related structures
laststylebender14 May 13, 2026
e4e8050
feat(ui): implement SelectPrompt for user prompts and update selectio…
laststylebender14 May 13, 2026
bfa4255
refactor(mcp): spawn server connections sequentially instead of joining
laststylebender14 May 14, 2026
cbbe56c
refactor(mcp): only cache servers when permissions are persisted
laststylebender14 May 14, 2026
808245f
[autofix.ci] apply automated fixes
autofix-ci[bot] May 14, 2026
f5bb712
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] May 14, 2026
1d5537d
Merge branch 'main' into feat/use-permissions-for-mcp-connections
laststylebender14 May 14, 2026
506576a
refactor(ui): remove load_tools wrapper method
laststylebender14 May 14, 2026
c50d165
refactor(ui): remove obsolete MCP initialization comment
laststylebender14 May 14, 2026
14e7ab9
refactor(mcp): use merged config for cache key computation
laststylebender14 May 14, 2026
622cf86
refactor(mcp): remove redundant comments from service cache logic
laststylebender14 May 14, 2026
903d7fa
refactor(infra): make with_header replace instead of extend
laststylebender14 May 14, 2026
0a1e4f6
refactor(policy): simplify allow_operation return handling
laststylebender14 May 14, 2026
ad2818c
refactor(mcp): remove JsonSchema derive from Scope enum
laststylebender14 May 14, 2026
2517ce9
feat(policy): persist MCP connection permission decisions
laststylebender14 May 14, 2026
e1656e4
[autofix.ci] apply automated fixes
autofix-ci[bot] May 14, 2026
ce023b7
refactor(mcp): use join_all instead of spawn for connection handling
laststylebender14 May 14, 2026
a45235f
ext: ask permissions first approach (#3334)
laststylebender14 May 14, 2026
43749da
[autofix.ci] apply automated fixes
autofix-ci[bot] May 14, 2026
93797ff
refactor(policy): use imported types instead of fully-qualified paths…
laststylebender14 May 14, 2026
0f7443c
docs(skills): add test-mcp-permissions skill with e2e test script
laststylebender14 May 14, 2026
5b5868d
refactor(skills): simplify and deduplicate mcp permissions test script
laststylebender14 May 14, 2026
dbb9eca
refactor(skills): clean up mcp permissions test script
laststylebender14 May 14, 2026
311e202
refactor(mcp): cache McpApp instance in executor to avoid redundant r…
laststylebender14 May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions .forge/skills/test-mcp-permissions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
name: test-mcp-permissions
description: Test the MCP server permission policy feature end-to-end. Use when asked to test MCP permissions, verify that local MCP servers are gated by policy, or validate the allow/deny/prompt behavior for MCP connections introduced in PR #3324.
---

# Test MCP Permissions

This skill validates the MCP server permission policy feature (PR #3324). The feature gates **local-scope** MCP servers (`.mcp.json` in the project directory) through a permission prompt at startup, while **user-scope** servers (`~/.forge/.mcp.json`) are trusted unconditionally.

## How the Feature Works

1. **Startup flow**: `UI::request_local_mcp_permissions()` reads the local `.mcp.json` and calls `McpApp::request_mcp_permissions(cfg)` **before** the REPL starts, so the prompt never races with user input.
2. **Permission check**: For each enabled local server, a `PermissionOperation::Mcp` is evaluated against `~/.forge/permissions.yaml` by `PolicyEngine`.
3. **Policy result**:
- `Allow` → server connects silently
- `Deny` → server is filtered out silently
- `Confirm` (no matching rule) → user is prompted
4. **Prompt**: A two-choice `ConfirmPermission` (Accept / Reject) is shown with the server's command/url as a header. **Both** choices are persisted — the user is never asked again for the same server+cwd combination.
5. **Import shortcut**: `/mcp import` auto-persists `Allow` via `allow_mcp_servers()` — importing itself counts as consent, no prompt shown.

## Permissions File

`~/.forge/permissions.yaml` — written decisions look like:

```yaml
# stdio server (Allow)
policies:
- permission: allow
rule:
mcp:
command: npx
args: ["-y", "@github/mcp"]
dir: /path/to/project

# HTTP server (Deny)
- permission: deny
rule:
mcp:
url: "https://untrusted.example.com/sse"
dir: /path/to/project
```

Glob patterns work in all fields (`command: "np*"`, `url: "https://trusted.com/*"`).

## Test Scenarios

### Scenario 1 — No permissions.yaml: prompt fires

```bash
rm -f ~/.forge/permissions.yaml
# Add a local MCP server to .mcp.json in the project dir:
echo '{"mcpServers":{"test-server":{"command":"npx","args":["-y","@github/mcp"]}}}' > .mcp.json
forge
```

**Expected:** Prompt appears — `Allow MCP server "test-server" to connect?` with `command: npx` shown as a header line. Choose **Accept**.

**Verify:**
```bash
cat ~/.forge/permissions.yaml
# → contains: permission: allow, mcp: {command: npx, args: ["-y", "@github/mcp"], dir: <cwd>}
```

---

### Scenario 2 — Accept persisted: no prompt on second run

After Scenario 1 (accepted), restart forge in the same directory.

**Expected:** No prompt. Server connects silently.

---

### Scenario 3 — Reject persisted: server silently blocked

Run Scenario 1 again (`rm ~/.forge/permissions.yaml`, restart forge), choose **Reject**.

**Expected:** Forge starts without the server's tools available.

**Verify:**
```bash
cat ~/.forge/permissions.yaml
# → contains: permission: deny
```

Ask forge to use a tool from that server — it should report it as unavailable.

---

### Scenario 4 — User-scope server: never prompted

Add a server to `~/.forge/.mcp.json` (user scope, not `.mcp.json` in cwd).

```bash
rm -f ~/.forge/permissions.yaml
forge
```

**Expected:** No prompt. User-scope servers bypass the permission gate and always connect.

---

### Scenario 5 — `mcp import` auto-approves

```bash
rm -f ~/.forge/permissions.yaml
forge
# Inside forge REPL:
/mcp import
```

**Expected:** No permission prompt during import. After import, `~/.forge/permissions.yaml` contains `allow` rules for each imported server.

---

### Scenario 6 — Glob rule pre-set in permissions.yaml

```bash
cat > ~/.forge/permissions.yaml << 'EOF'
policies:
- permission: allow
rule:
mcp:
command: "np*"
EOF
forge
```

**Expected:** No prompt for any stdio server whose command starts with `np` (e.g. `npx`). The glob match skips the prompt entirely.

---

### Scenario 7 — HTTP MCP server

```bash
rm -f ~/.forge/permissions.yaml
echo '{"mcpServers":{"http-server":{"url":"https://mcp.example.com/sse"}}}' > .mcp.json
forge
```

**Expected:** Prompt shows `url: https://mcp.example.com/sse` as header. Accepting writes:
```yaml
- permission: allow
rule:
mcp:
url: "https://mcp.example.com/sse"
dir: <cwd>
```

---

## Quick Reset Between Tests

```bash
rm -f ~/.forge/permissions.yaml
rm -f .mcp.json
```

## Key Code Locations

| What | File |
|---|---|
| Startup permission gate | `crates/forge_main/src/ui.rs:445-457` |
| McpApp orchestration | `crates/forge_app/src/mcp_app.rs` |
| Policy prompt logic | `crates/forge_services/src/policy.rs:218-244` |
| MCP rule matching | `crates/forge_domain/src/policies/rule.rs:111-116` |
| MCP filter (glob match) | `crates/forge_domain/src/policies/rule.rs:159-181` |
| Default permissions | `crates/forge_services/src/permissions.default.yaml` |
Loading
Loading