Skip to content

Commit e69ea0e

Browse files
authored
test: verify native MCP approval and denial flows (#261)
* test: validate native tool approval decisions * test: fix and verify native approval workflows * test: handle native UI connection shutdown races * docs: record final native consent validation
1 parent 5f193ee commit e69ea0e

15 files changed

Lines changed: 798 additions & 41 deletions

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ published section (it happened to #170).
1818

1919
### Added
2020

21+
- Separate native default-consent validation for VS Code and Claude Code:
22+
one-shot approval, denial/Escape before dispatch, exact-tool persistence and
23+
guard blocking after client approval. Uses actual native UI with synthetic
24+
tools/model responses in disposable macOS VMs. Reviewed measurements pass
25+
four cases per client. VS Code uses custom in-app dialogs; live providers,
26+
native macOS dialogs, workspace/server trust and inline chat/Agent Host
27+
remain outside its scope.
2128
- Explicit candidate-artifact native validation, with archive integrity and
2229
complete executable file-set hashes, plus realistic JSONC/input/environment-variable fixtures.
2330
The default published baseline remains 0.47.1; live model and normal approval

‎CLAUDE.md‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# MCP Registry — Project Context
22

33
> This file is automatically read by Claude Code on every session.
4-
> Keep it updated as decisions are made. Last updated: 2026-10-12.
4+
> Keep it updated as decisions are made. Last updated: 2026-10-13.
55
66
---
77

@@ -45,8 +45,34 @@ final candidate and default published-baseline runs both pass 8/8 VS Code and
4545
checks: 3,588 tests, typecheck/build and 13 harness self-checks pass. Astra fixed
4646
inherited-key removal, incomplete chunk binding and a pre-wire cancellation
4747
fixture race; cancellation requires the current matching request before firing.
48-
User-global paths are unchanged; portable/Agent Host routes and normal approval,
49-
provider/model/usage-skill behavior remain unverified.
48+
User-global paths are unchanged; portable/Agent Host routes and
49+
provider/model/usage-skill behavior remain unverified. Normal tool consent is
50+
measured separately in Phase 2C below.
51+
52+
**Unreleased Phase 2C:** Native default-mode consent passes 4/4 cases in each of
53+
VS Code 1.141.0 and Claude Code 2.1.296: one-shot re-prompting in one session,
54+
deny/Escape before dispatch, exact echo grants surviving restart while warn
55+
still prompts, and guard blocking after client approval with echo recovery.
56+
The reviewed macOS 26.6.2/arm64 run has matching host/guest hashes and clean
57+
clone cleanup. The unchanged published-baseline suite also passes 8/8 VS Code
58+
and 7/7 Claude Code; 22 harness checks pass on Node 22/24/26. VS Code uses custom
59+
in-app Tool API dialogs and the normal profile approval manager; native macOS
60+
dialog rendering, workspace/server trust integration, inline chat and Agent
61+
Host remain unverified. Claude uses a real interactive PTY and synthetic
62+
loopback model/title responses. No live provider/model/usage-skill evidence.
63+
Review fixed native UI selectors, pointer/input timing, discovery readiness,
64+
startup retry and evidence gaps. An independent candidate repeat exposed a
65+
CDP shutdown error event recorded as an empty decision; the focused fix
66+
preserves fatal diagnostics and passes a candidate repeat with strict checks.
67+
See `docs/NATIVE-CLIENT-VALIDATION.md`.
68+
69+
Independent final candidate validation repeats 4/4 cases per client against
70+
clean reviewed `ed02ef2`. The fresh pnpm archive SHA-256 is
71+
`d1e6f4ac327ec85fc34561010950149612e1e5857b91befa5d3bbf299655a9de`; all 134
72+
package/distribution files and native host/guest evidence match. Parent
73+
verification also passes 22 harness checks, typecheck/build and 3,588 source
74+
tests, and checks the reviewed 8/8 + 7/7 ordinary baseline receipts. This is
75+
local candidate proof, not a new release or signed build provenance.
5076

5177
**Released native validation baseline (v0.47.2):** `scripts/native-client/` retains published mcpm
5278
0.47.1 as its fixed baseline. VS Code 1.141.0 passed 8/8 and Claude Code 2.1.296
@@ -64,8 +90,8 @@ two expected unsupported combinations on Linux/arm64 and Linux/amd64, plus seven
6490
self-checks and a launcher regression. See `docs/PROTOCOL-VALIDATION.md`.
6591

6692
No SDK upgrade, HTTP relay or full protocol-conformance claim. Live provider,
67-
model/usage-skill behavior, ordinary approval flows and other client/platform
68-
combinations remain unverified. Jev/Clef stays deferred for subscription/access.
93+
model/usage-skill behavior, approval paths beyond the named Phase 2C suite
94+
and other client/platform combinations remain unverified. Jev/Clef stays deferred for subscription/access.
6995

7096
**Previous release context (historical):**
7197

‎CONTRIBUTING.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ self-checks run with `node --test scripts/native-client/self-check.mjs`; native
7070
VM runs require the separately pinned public client/runtime payload.
7171
Candidate changes must use the explicit candidate mode and a fresh packed CLI;
7272
a passing published-baseline run does not validate changed production code.
73+
Use the separate `--approvals` native suite for default tool consent; the
74+
ordinary lifecycle suite preapproves fixture tools and cannot establish that
75+
permission behavior.
7376

7477
## Ground rules
7578

‎docs/NATIVE-CLIENT-VALIDATION.md‎

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ The runner refuses existing VM storage without its ownership marker. A prepared
3030
payload may be reused only with matching pins. Provisioning downloads public
3131
artifacts; native execution is offline except for guest loopback communication.
3232

33+
A slow cold download can exceed the runner's 30-minute clone deadline. Prewarm
34+
only the pinned public image in fresh task-owned storage before running tests:
35+
36+
```sh
37+
NATIVE_VM_STORAGE="$(mktemp -d /tmp/mcpm-native-vms.XXXXXX)"
38+
printf 'mcpm-native-client-validation\n' > "$NATIVE_VM_STORAGE/.mcpm-native-owner"
39+
NATIVE_IMAGE="$(node -p "JSON.parse(require('node:fs').readFileSync('scripts/native-client/pins.json')).image")"
40+
TART_HOME="$NATIVE_VM_STORAGE" TART_NO_AUTO_PRUNE=1 /path/to/tart clone "$NATIVE_IMAGE" mcpm-native-prewarm
41+
TART_HOME="$NATIVE_VM_STORAGE" TART_NO_AUTO_PRUNE=1 /path/to/tart delete mcpm-native-prewarm
42+
node scripts/native-client/run-vm.mjs /path/to/tart \
43+
"$NATIVE_VM_STORAGE" /tmp/mcpm-native-payload /tmp/mcpm-native-evidence
44+
```
45+
46+
Wait for the clone to finish successfully before deleting its temporary local
47+
name or running the suite. The pinned image cache remains in that task storage;
48+
no VM is booted during prewarm. Guest and test deadlines remain unchanged.
49+
3350
The private npm manifest/lockfile pins published mcpm 0.47.1 and Claude Code
3451
2.1.296, including transitive dependencies. VS Code 1.141.0 (commit
3552
`2a59476c9bfcb90b3ddc372c36762471b7dfad1c`), Node 26.11.1, Tart and the public
@@ -97,6 +114,94 @@ the original raw backup are preserved.
97114

98115
## Boundary and fixture scope
99116

117+
### Default consent suite (Phase 2C)
118+
119+
Run the separate approval suite with the same prepared payload and a fresh
120+
evidence directory:
121+
122+
```sh
123+
node scripts/native-client/run-vm.mjs /path/to/tart \
124+
/tmp/mcpm-native-vms /tmp/mcpm-native-payload /tmp/mcpm-consent-evidence \
125+
--approvals
126+
```
127+
128+
Add `--candidate` when using a freshly packed candidate payload. `--approvals`
129+
and `--realistic-config` are separate suites. The default baseline and existing
130+
lifecycle cases retain their original behavior and pins.
131+
132+
The consent suite disables VS Code global auto-approval and uses native Tool
133+
API confirmation dialogs with the supported guest-only `window.dialogStyle: custom`
134+
setting, so VS Code renders the prompt in its window. This changes dialog
135+
rendering without granting tool permission; native macOS dialog rendering is
136+
outside this suite. An exact echo grant is made through the normal
137+
**Manage Tool Approval** UI at profile scope. It does not edit the approval
138+
database. The fixture extension trusts the known server startup and disables
139+
VS Code workspace trust in the guest; workspace/server trust integration,
140+
inline chat and Agent Host permission flows remain unverified.
141+
142+
Claude Code runs its interactive terminal UI with `--permission-mode default`,
143+
without print mode, permission bypass flags, or seeded tool grants. The guest's
144+
built-in Expect provides a real 120×40 PTY; the driver records raw output and
145+
the current rendered screen, including Unicode, cursor movement and erasure.
146+
Only the exact synthetic fixture choices are selected. Workspace trust and
147+
the synthetic loopback API-key prompt are recorded separately. The native
148+
client writes its exact-tool grant to local settings; the harness only reads it.
149+
The driver waits for the same peer's completed `tools/list` and an idle terminal
150+
prompt before typing the exact fixture request. Launch arguments, discovery
151+
readiness and the typed request are recorded. The Messages gateway supplies
152+
deterministic tool rounds and the separately identified session-title response
153+
without a live provider; a main request missing the fixture tool still fails.
154+
155+
| Case | Required observation |
156+
| --- | --- |
157+
| One-shot re-prompts | Two different echo arguments in one client session each require visible approval, dispatch once and return the exact value from the same peer PID. |
158+
| Deny and Escape | Refuse and cancel separately at the visible permission prompt; neither dispatches a tool call. Initialization/discovery traffic is allowed. |
159+
| Persistent exact-tool grant | Grant echo through the client UI, restart the client with a new nonce and peer PID, and call echo without another prompt. An ungranted warn tool still prompts and is denied. |
160+
| Guard after approval | Approve the attack fixture through the native UI, observe the peer call and guard block, then recover with the previously approved echo. |
161+
162+
Consent decisions must precede dispatch. Missing/repeated decisions, mismatched
163+
arguments, unexpected grants, timeout kills, reused observations and incomplete
164+
case reports fail validation. Negative Claude decisions can stop the turn
165+
without returning a tool result to the gateway. They require a usable native
166+
prompt and orderly exit, rather than an invented denial response.
167+
168+
Reviewed local measurement on 2026-10-13 passed **4/4 VS Code and 4/4 Claude
169+
Code approval cases** using published mcpm 0.47.1, with seven exported
170+
observations per client, on macOS
171+
26.6.2/arm64 with Node 26.11.1 and the pinned client versions. Both clients
172+
finished normally without deadline kills. Host/guest fixture hashes match,
173+
and both clones were stopped/deleted. The original published-baseline suite
174+
was then repeated against the same harness: **8/8 VS Code and 7/7 Claude Code**
175+
passed, with matching hashes and cleanup. All 22 account/VM-free harness checks
176+
pass on Node 22, 24 and 26; mocks remain separate from the native evidence.
177+
178+
Independent final candidate validation on 2026-10-13 repeated **4/4 VS Code
179+
and 4/4 Claude Code**, with seven observations each, normal exits and clean
180+
clone teardown. The fresh pnpm-packed archive from clean Astra-reviewed
181+
`ed02ef22380d5f6c919050bb82ccd476e4f737bf` has SHA-256
182+
`d1e6f4ac327ec85fc34561010950149612e1e5857b91befa5d3bbf299655a9de`. The candidate uses
183+
the repository placeholder version 0.15.0; it is not the published npm version.
184+
All 134 installed package/distribution files, host/stage/guest harness hashes,
185+
raw approval/refusal receipts, restart peer IDs and guard block/recovery
186+
observations match. Parent checks also pass all 22 harness regressions,
187+
typecheck/build and 3,588 source tests; the reviewed 8/8 + 7/7 ordinary baseline
188+
receipts were independently checked. Source/archive records identify this
189+
local build, not signed provenance. Raw native evidence stays local.
190+
191+
Review corrected native dialog title ordering/capitalization, approval-tree
192+
pointer interaction and accessible labels, terminal input/redraw timing,
193+
interactive startup racing MCP discovery, and the separate synthetic title
194+
request. Validation now requires the actual Claude launch/prompt readiness,
195+
the VS Code checkbox transition, and a refusal receipt. Earlier exploratory
196+
failures and one interrupted attempt are superseded; none count as passes. An
197+
independent candidate repeat later exposed a shutdown race: a raw CDP socket
198+
error event had no stack and serialized as an empty approval record, falsely
199+
failing a successful persisted echo. Connection errors now follow the bounded
200+
transport retry path, and fatal errors always retain a diagnostic. The
201+
zero-decision requirement after restart is unchanged. The same candidate
202+
then passed all four cases in both clients. The unchanged published baseline
203+
also passed 8/8 VS Code and 7/7 Claude Code again after this fix.
204+
100205
The runner clones the immutable image independently for **VS Code first, then
101206
Claude Code**, using the base image's synthetic `admin` account. Each clone is
102207
stopped/deleted after its client completes, fails, or the runner receives
@@ -191,8 +296,8 @@ evidence local; publish scoped results rather than entire client transcripts.
191296

192297
## Remaining verification
193298

194-
Live providers and actual model/usage-skill behavior, interactive chat/default
195-
approval workflows, other clients/platforms, additional configuration variants,
299+
Live providers and actual model/usage-skill behavior, interactive chat and
300+
approval paths beyond the measured suite, other clients/platforms, additional configuration variants,
196301
and complete protocol conformance remain unverified. Native client versions
197302
and observed wire protocol must be recorded rather than inferred from package
198303
versions. Updating pins requires repeating the native matrix. SIGKILL, host

0 commit comments

Comments
 (0)