Skip to content

Commit 5f35101

Browse files
feat(webauthn): add webauthn_enable tool skeleton
- Create src/tools/webauthn.ts with enableWebAuthn tool - Export from src/tools/tools.ts - Add basic test in tests/tools/webauthn.test.ts The tool currently does nothing - just returns success message. Next step: implement actual WebAuthn.enable CDP call. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4b8e9f2 commit 5f35101

4 files changed

Lines changed: 297 additions & 0 deletions

File tree

WEBAUTHN_IMPLEMENTATION.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# WebAuthn MCP Tools Implementation Plan
2+
3+
## Overview
4+
5+
Adding WebAuthn CDP domain support to chrome-devtools-mcp using strict outside-in behavior- and test-driven development.
6+
7+
**Branch**: `feat/webauthn-support`
8+
**Fork**: `git@github.com:ed-lepedus-thenvoi/chrome-devtools-mcp.git`
9+
10+
## Goal (Definition of Done)
11+
12+
A user can:
13+
1. Enable WebAuthn virtual authenticator environment via MCP tool
14+
2. Add a virtual authenticator (CTAP2/U2F, internal/USB/BLE/NFC)
15+
3. Use WebAuthn on a webpage (e.g., webauthn.io) with the virtual authenticator responding
16+
4. Optionally: add pre-seeded credentials, get/remove credentials
17+
18+
## Key Architecture Findings
19+
20+
### How Tools Are Structured
21+
22+
- **Tool Registry**: `src/tools/tools.ts` - exports all tools as array
23+
- **Tool Definition**: Use `defineTool()` helper from `src/tools/ToolDefinition.ts`
24+
- **Categories**: Defined in `src/tools/categories.ts` (use `EMULATION` for WebAuthn)
25+
26+
### How to Access CDP Session
27+
28+
```typescript
29+
const page = context.getSelectedPage();
30+
const session = page._client() as CDPSession;
31+
await session.send('WebAuthn.enable');
32+
```
33+
34+
This pattern is used in `src/PageCollector.ts` for `Audits.enable`.
35+
36+
### Test Pattern
37+
38+
Tests use `withMcpContext()` helper from `tests/utils.ts`:
39+
40+
```typescript
41+
import {describe, it} from 'node:test';
42+
import assert from 'node:assert';
43+
import {withMcpContext} from '../utils.js';
44+
import {enableWebAuthn} from '../../src/tools/webauthn.js';
45+
46+
describe('webauthn', () => {
47+
it('enables WebAuthn CDP domain', async () => {
48+
await withMcpContext(async (response, context) => {
49+
await enableWebAuthn.handler({params: {}}, response, context);
50+
// Verify by checking response or trying CDP operations
51+
});
52+
});
53+
});
54+
```
55+
56+
### WebAuthn CDP Commands Available
57+
58+
- `WebAuthn.enable` / `WebAuthn.disable`
59+
- `WebAuthn.addVirtualAuthenticator` → returns `{authenticatorId: string}`
60+
- `WebAuthn.removeVirtualAuthenticator`
61+
- `WebAuthn.addCredential`
62+
- `WebAuthn.getCredentials`
63+
- `WebAuthn.removeCredential`
64+
- `WebAuthn.clearCredentials`
65+
- `WebAuthn.setUserVerified`
66+
67+
## Implementation Steps (Outside-In TDD)
68+
69+
### Phase 1: Minimal Vertical Slice
70+
71+
#### Step 1.1: Observe Missing Functionality
72+
- [x] Verify no `webauthn_*` tools exist in MCP
73+
- [x] Navigate to webauthn.io, confirm we can't do WebAuthn without virtual authenticator
74+
75+
#### Step 1.2: Failing Test - Tool Exists
76+
Create `tests/tools/webauthn.test.ts`:
77+
```typescript
78+
it('webauthn_enable tool can be called')
79+
```
80+
Run: `npm run test -- --test-name-pattern="webauthn"`
81+
Expected: FAIL (module not found)
82+
83+
#### Step 1.3: Implement - Minimal Tool Skeleton
84+
- Create `src/tools/webauthn.ts` with `enableWebAuthn` tool (no-op handler)
85+
- Export from `src/tools/tools.ts`
86+
- Run test: Should PASS
87+
- Commit: `feat(webauthn): add webauthn_enable tool skeleton`
88+
89+
#### Step 1.4: Verify Tool Appears in MCP
90+
- Rebuild: `npm run build`
91+
- Check if MCP picks up changes (may need restart)
92+
- Verify tool appears
93+
94+
#### Step 1.5: Failing Test - Enable Actually Works
95+
```typescript
96+
it('enables WebAuthn so addVirtualAuthenticator succeeds', async () => {
97+
await withMcpContext(async (response, context) => {
98+
await enableWebAuthn.handler({params: {}}, response, context);
99+
const session = context.getSelectedPage()._client();
100+
// This should succeed only if WebAuthn.enable was called
101+
const result = await session.send('WebAuthn.addVirtualAuthenticator', {
102+
options: { protocol: 'ctap2', transport: 'internal' }
103+
});
104+
assert.ok(result.authenticatorId);
105+
});
106+
});
107+
```
108+
Run: FAIL (WebAuthn not enabled)
109+
110+
#### Step 1.6: Implement - CDP Call
111+
Add to handler:
112+
```typescript
113+
await context.getSelectedPage()._client().send('WebAuthn.enable');
114+
```
115+
Run test: PASS
116+
Commit: `feat(webauthn): implement WebAuthn.enable CDP call`
117+
118+
#### Step 1.7: Verify via MCP
119+
- Call `webauthn_enable` tool
120+
- Confirm no error
121+
122+
#### Step 1.8: Failing Test - Add Authenticator Tool
123+
```typescript
124+
it('adds virtual authenticator and returns ID')
125+
```
126+
Run: FAIL (tool doesn't exist)
127+
128+
#### Step 1.9: Implement - Add Authenticator
129+
- Add `addVirtualAuthenticator` tool with params: protocol, transport, hasResidentKey, hasUserVerification, isUserVerified
130+
- Run test: PASS
131+
- Commit: `feat(webauthn): add webauthn_add_authenticator tool`
132+
133+
#### Step 1.10: E2E Verification
134+
1. Navigate to webauthn.io
135+
2. Call `webauthn_enable`
136+
3. Call `webauthn_add_authenticator` with ctap2/internal/userVerified
137+
4. Fill username, click Register
138+
5. Verify registration succeeds
139+
140+
Commit: `test(webauthn): verify e2e with webauthn.io`
141+
142+
### Phase 2: Expand Coverage
143+
144+
After vertical slice works:
145+
- `webauthn_disable`
146+
- `webauthn_remove_authenticator`
147+
- `webauthn_get_credentials`
148+
- `webauthn_add_credential`
149+
- `webauthn_remove_credential`
150+
- `webauthn_clear_credentials`
151+
- `webauthn_set_user_verified`
152+
153+
### Phase 3: Polish
154+
- Error handling tests
155+
- Run `npm run docs` to update documentation
156+
- Run `npm run check-format` and fix any issues
157+
- Full test suite pass
158+
159+
## Local Development Setup
160+
161+
```bash
162+
# MCP is configured to use local build:
163+
# claude mcp add-json chrome-devtools '{"command": "node", "args": ["/tmp/chrome-devtools-mcp-investigation/build/src/index.js"]}'
164+
165+
# Build after changes:
166+
cd /tmp/chrome-devtools-mcp-investigation && npm run build
167+
168+
# Run specific tests:
169+
npm run test -- --test-name-pattern="webauthn"
170+
171+
# Run all tests:
172+
npm run test
173+
174+
# Check formatting:
175+
npm run check-format
176+
```
177+
178+
## Notes
179+
180+
- Node version: v24.9.0 (compatible)
181+
- Baseline: 288/288 tests passing
182+
- License header required on new files (see existing files for format)
183+
- MCP may need restart after rebuild to pick up changes (TBD - need to verify)
184+
185+
## Files to Create/Modify
186+
187+
1. **Create**: `src/tools/webauthn.ts` - Tool definitions
188+
2. **Modify**: `src/tools/tools.ts` - Add exports
189+
3. **Create**: `tests/tools/webauthn.test.ts` - Tests
190+
191+
## Reference: Emulation Tool Pattern
192+
193+
From `src/tools/emulation.ts`:
194+
```typescript
195+
export const emulate = defineTool({
196+
name: 'emulate',
197+
description: '...',
198+
annotations: {
199+
category: ToolCategory.EMULATION,
200+
readOnlyHint: false,
201+
},
202+
schema: {
203+
param1: zod.string().optional().describe('Description'),
204+
},
205+
handler: async (request, response, context) => {
206+
const page = context.getSelectedPage();
207+
// ... implementation
208+
response.appendResponseLine('Status message');
209+
},
210+
});
211+
```
212+
213+
## Reference: Test Utilities
214+
215+
From `tests/utils.ts`:
216+
217+
- `withMcpContext(callback)` - Spawns browser, creates McpContext, calls callback
218+
- `McpResponse` - Mock response object with `appendResponseLine()`, etc.
219+
- Access CDP via: `context.getSelectedPage()._client()` returns CDPSession
220+
221+
```typescript
222+
import assert from 'node:assert';
223+
import {describe, it} from 'node:test';
224+
import {withMcpContext} from '../utils.js';
225+
import {myTool} from '../../src/tools/myTool.js';
226+
227+
describe('myTool', () => {
228+
it('does something', async () => {
229+
await withMcpContext(async (response, context) => {
230+
await myTool.handler({params: {...}}, response, context);
231+
// Assert on response or context state
232+
});
233+
});
234+
});
235+
```
236+
237+
## Progress Log
238+
239+
Track each step completion here:
240+
241+
- [ ] Step 1.1: Observe missing functionality
242+
- [ ] Step 1.2: Failing test - tool exists
243+
- [ ] Step 1.3: Implement tool skeleton
244+
- [ ] Step 1.4: Verify tool appears in MCP
245+
- [ ] Step 1.5: Failing test - enable works
246+
- [ ] Step 1.6: Implement CDP call
247+
- [ ] Step 1.7: Verify via MCP
248+
- [ ] Step 1.8: Failing test - add authenticator
249+
- [ ] Step 1.9: Implement add authenticator
250+
- [ ] Step 1.10: E2E verification

src/tools/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as performanceTools from './performance.js';
1414
import * as screenshotTools from './screenshot.js';
1515
import * as scriptTools from './script.js';
1616
import * as snapshotTools from './snapshot.js';
17+
import * as webauthnTools from './webauthn.js';
1718
import type {ToolDefinition} from './ToolDefinition.js';
1819

1920
const tools = [
@@ -27,6 +28,7 @@ const tools = [
2728
...Object.values(screenshotTools),
2829
...Object.values(scriptTools),
2930
...Object.values(snapshotTools),
31+
...Object.values(webauthnTools),
3032
] as ToolDefinition[];
3133

3234
tools.sort((a, b) => {

src/tools/webauthn.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {ToolCategory} from './categories.js';
8+
import {defineTool} from './ToolDefinition.js';
9+
10+
export const enableWebAuthn = defineTool({
11+
name: 'webauthn_enable',
12+
description: 'Enable the WebAuthn virtual authenticator environment for the selected page.',
13+
annotations: {
14+
category: ToolCategory.EMULATION,
15+
readOnlyHint: false,
16+
},
17+
schema: {},
18+
handler: async (_request, response, _context) => {
19+
// Skeleton - does nothing yet
20+
response.appendResponseLine('WebAuthn enabled');
21+
},
22+
});

tests/tools/webauthn.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import assert from 'node:assert';
8+
import {describe, it} from 'node:test';
9+
10+
import {enableWebAuthn} from '../../src/tools/webauthn.js';
11+
import {withMcpContext} from '../utils.js';
12+
13+
describe('webauthn', () => {
14+
describe('webauthn_enable', () => {
15+
it('can be called without error', async () => {
16+
await withMcpContext(async (response, context) => {
17+
await enableWebAuthn.handler({params: {}}, response, context);
18+
// If we get here without error, the tool exists and can be called
19+
assert.ok(true);
20+
});
21+
});
22+
});
23+
});

0 commit comments

Comments
 (0)