Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b94bd12
feat: Add cookie-debugging skill and improve network/pages tool descr…
natorion Aug 20, 2026
6724c95
docs(skills): address agent feedback on cookie-debugging workflows an…
natorion Aug 21, 2026
5574e21
test: add eval scenarios for active HttpOnly triggers and cookie secu…
natorion Aug 21, 2026
0326940
test: handle optional list_pages and select_page in eval scenarios
natorion Aug 21, 2026
cf9aea7
test: consume all initial setup/navigation boilerplate in eval runner
natorion Aug 21, 2026
9119ac3
test: refine cookie eval scenario assertions and add --isolated to ev…
natorion Aug 21, 2026
249d12c
refactor(eval): retain original SKILL_PATH constant name
natorion Aug 21, 2026
f940b03
fix(eval): use SKILL_PATH as default parameter value in runSingleScen…
natorion Aug 21, 2026
c533b36
chore: run npm run gen to update generated CLI options and docs
natorion Aug 21, 2026
89aafa4
refactor(eval): streamline consumePageNavigation and update JSDoc
natorion Aug 27, 2026
177d723
refactor(skills): remove cookie-snippets.md and streamline cookie-deb…
natorion Aug 28, 2026
eca4598
style: apply npm run format
natorion Aug 28, 2026
950a0af
docs(skills): lead with cookieStore API and add secure context and as…
natorion Aug 31, 2026
921e56c
style: revert pipe formatting in McpPage, ToolDefinition, WaitForHelp…
natorion Aug 31, 2026
0af7760
docs(skills): add consent revocation lifecycle audit step to cookie-d…
natorion Aug 31, 2026
8b5e182
style: apply npm run format
natorion Aug 31, 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ grok mcp add chrome-devtools npx chrome-devtools-mcp@latest
```

See the <a href="https://docs.x.ai/build/features/skills-plugins-marketplaces">docs</a> for more options

</details>

<details>
Expand Down
4 changes: 2 additions & 2 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@

- **url** (string) **(required)**: URL to load in a new page.
- **background** (boolean) _(optional)_: Whether to open the page in the background without bringing it to the front. Default is false (foreground).
- **isolatedContext** (string) _(optional)_: If specified, the page is created in an isolated browser context with the given name. Pages in the same browser context share cookies and storage. Pages in different browser contexts are fully isolated.
- **isolatedContext** (string) _(optional)_: If specified, the page is created in an isolated browser context with the given name. Pages in the same browser context share cookies and storage. Pages in different browser contexts are fully isolated (useful for clean-slate testing of cookies and authentication).
- **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used.

---
Expand Down Expand Up @@ -327,7 +327,7 @@

### `get_network_request`

**Description:** Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel.
**Description:** Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel. Useful for inspecting request headers (including 'Cookie') and response headers (including 'Set-Cookie' and directives).

**Parameters:**

Expand Down
22 changes: 15 additions & 7 deletions scripts/eval_gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const ROOT_DIR = path.resolve(import.meta.dirname, '..');
const SCENARIOS_DIR = path.join(import.meta.dirname, 'eval_scenarios');
const SKILL_PATH = path.join(ROOT_DIR, 'skills', 'chrome-devtools', 'SKILL.md');
Comment thread
natorion marked this conversation as resolved.

import type {CapturedFunctionCall, TestScenario} from './eval_result.ts';
import {Result} from './eval_result.ts';
import type {CapturedFunctionCall, TestScenario} from './eval_result.js';
import {Result} from './eval_result.js';
export type {CapturedFunctionCall, TestScenario};
export {Result};

Expand All @@ -41,6 +41,7 @@ async function runSingleScenario(
modelId: string,
debug: boolean,
includeSkill: boolean,
skillPath: string = SKILL_PATH,
extraServerArgs: string[] = [],
): Promise<void> {
const debugLog = (...args: unknown[]) => {
Expand All @@ -62,12 +63,12 @@ async function runSingleScenario(

// Prepend skill content if requested
if (includeSkill) {
if (!fs.existsSync(SKILL_PATH)) {
if (!fs.existsSync(skillPath)) {
throw new Error(
`Skill file not found at ${SKILL_PATH}. Please ensure the skill file exists.`,
`Skill file not found at ${skillPath}. Please ensure the skill file exists.`,
);
}
const skillContent = fs.readFileSync(SKILL_PATH, 'utf-8');
const skillContent = fs.readFileSync(skillPath, 'utf-8');
scenario.prompt = `${skillContent}\n\n---\n\n${scenario.prompt}`;
}

Expand Down Expand Up @@ -106,7 +107,7 @@ async function runSingleScenario(
});
env['CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS'] = 'true';

const args = [serverPath];
const args = [serverPath, '--isolated'];
if (!debug) {
args.push('--headless');
}
Expand Down Expand Up @@ -200,6 +201,9 @@ async function main() {
type: 'boolean',
default: false,
},
'skill-path': {
type: 'string',
},
'server-args': {
type: 'string',
},
Expand All @@ -210,7 +214,10 @@ async function main() {
const modelId = values.model;
const debug = values.debug;
const repeat = values.repeat;
const includeSkill = values['include-skill'];
const includeSkill = values['include-skill'] || Boolean(values['skill-path']);
const skillPath = values['skill-path']
? path.resolve(ROOT_DIR, values['skill-path'])
: SKILL_PATH;
const extraServerArgs = values['server-args']
? values['server-args'].split(/\s+/)
: [];
Expand Down Expand Up @@ -245,6 +252,7 @@ async function main() {
modelId,
debug,
includeSkill,
skillPath,
extraServerArgs,
);
console.log(`✔ ${path.relative(ROOT_DIR, scenarioPath)} (Run ${i})`);
Expand Down
6 changes: 5 additions & 1 deletion scripts/eval_result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class Result {

/**
* Consumes initial page navigation/setup boilerplate.
* - Ignores/skips leading list_pages calls.
* - Ignores/skips leading or trailing list_pages calls.
* - Asserts that new_page or navigate_page was called.
* - Determines the expected pageId.
* - Returns the active pageId.
Expand All @@ -49,6 +49,10 @@ export class Result {
);
this.nextCallIndex++;

if (this.calls[this.nextCallIndex]?.name === 'list_pages') {
this.nextCallIndex++;
}

const isNewPage = navCall.name === 'new_page';
let pageId: number | undefined;
if (this.hasPageIdRouting) {
Expand Down
52 changes: 52 additions & 0 deletions scripts/eval_scenarios/cookie_banner_conformance_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'node:assert';

import type {TestScenario} from '../eval_gemini.js';

export const scenario: TestScenario = {
prompt:
'Open <TEST_URL> in an isolated browser context called banner-test to check the cookie consent banner, take a snapshot, and click Decline.',
maxTurns: 5,
htmlRoute: {
path: '/cookie_banner_test.html',
htmlContent: `
<h1>Cookie Consent Test</h1>
<div id="cookie-banner">
<p>We use cookies to improve your experience.</p>
<button id="accept-btn">Accept All</button>
<button id="decline-btn">Decline</button>
</div>
`,
},
expectations: result => {
const newPageCall = result.calls.find(c => c.name === 'new_page');
assert.ok(
newPageCall,
'Expected new_page to be called for isolated context testing',
);
assert.strictEqual(
newPageCall.args.isolatedContext,
'banner-test',
"Expected isolatedContext to be 'banner-test'",
);

const pageId = result.consumePageNavigation();
assert.ok(result.remainingCalls.length >= 2);
const snapshotCall = result.calls.find(c => c.name === 'take_snapshot');
assert.ok(snapshotCall, 'Expected take_snapshot to be called');
const clickCall = result.calls.find(c => c.name === 'click');
assert.ok(clickCall, 'Expected click to be called');
assert.ok(
clickCall.args.uid,
'Expected click to specify a valid element uid',
);
if (result.hasPageIdRouting && pageId !== undefined) {
assert.strictEqual(clickCall.args.pageId, pageId);
}
},
};
41 changes: 41 additions & 0 deletions scripts/eval_scenarios/cookie_debugging_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'node:assert';

import type {TestScenario} from '../eval_gemini.js';

export const scenario: TestScenario = {
prompt:
'Navigate to <TEST_URL> and inspect the network request headers to diagnose the authentication failure.',
maxTurns: 6,
htmlRoute: {
path: '/cookie_auth_test.html',
htmlContent: `
<h1>Authentication Test</h1>
<script>
fetch('/api/user', {
headers: { 'Accept': 'application/json' },
credentials: 'include'
});
</script>
`,
},
expectations: result => {
result.consumePageNavigation();
const listRequestsCall = result.calls.find(
c => c.name === 'list_network_requests',
);
assert.ok(listRequestsCall, 'Expected list_network_requests to be called');
const getRequestCall = result.calls.find(
c => c.name === 'get_network_request',
);
assert.ok(
getRequestCall,
'Expected get_network_request to be called to inspect headers',
);
},
};
31 changes: 31 additions & 0 deletions scripts/eval_scenarios/cookie_httponly_trigger_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'node:assert';

import type {TestScenario} from '../eval_gemini.js';

export const scenario: TestScenario = {
prompt:
'Reload the page <TEST_URL> and inspect the network request headers to view the active HttpOnly cookie.',
maxTurns: 4,
htmlRoute: {
path: '/cookie_httponly_test.html',
htmlContent: `
<h1>HttpOnly Session Test</h1>
`,
},
expectations: result => {
result.consumePageNavigation();
const getRequestCall = result.calls.find(
c => c.name === 'get_network_request',
);
assert.ok(
getRequestCall,
'Expected get_network_request to be called to inspect HttpOnly headers',
);
},
};
30 changes: 30 additions & 0 deletions scripts/eval_scenarios/cookie_issues_audit_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'node:assert';

import type {TestScenario} from '../eval_gemini.ts';

export const scenario: TestScenario = {
prompt:
'Navigate to <TEST_URL> and inspect the console issues to check for cookie security or SameSite policy warnings.',
maxTurns: 3,
htmlRoute: {
path: '/cookie_issues_test.html',
htmlContent: `
<h1>Cookie Issues Test</h1>
<p>Testing SameSite and CHIPS issues</p>
`,
},
expectations: result => {
const pageId = result.consumePageNavigation();
assert.ok(result.remainingCalls.length >= 1);
result.assertNextCall('list_console_messages', {
types: ['issue'],
...(result.hasPageIdRouting ? {pageId} : {}),
});
},
};
14 changes: 13 additions & 1 deletion scripts/generate-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,22 @@ function updateReadmeWithToolsTOC(toolsTOC: string): void {
console.log('Updated README.md with tools table of contents');
}

interface OptionConfig {
hidden?: boolean;
alias?: string;
description?: string;
describe?: string;
type?: string;
choices?: string[];
default?: unknown;
}

function generateConfigOptionsMarkdown(): string {
let markdown = '';

for (const [optionName, optionConfig] of Object.entries(mcpOptions)) {
for (const [optionName, optionConfig] of Object.entries(
mcpOptions as Record<string, OptionConfig>,
)) {
// Skip hidden options
if (optionConfig.hidden) {
continue;
Expand Down
Loading
Loading