Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 52 additions & 3 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
- [`performance_analyze_insight`](#performance_analyze_insight)
- [`performance_start_trace`](#performance_start_trace)
- [`performance_stop_trace`](#performance_stop_trace)
- **[Network](#network)** (2 tools)
- **[Network](#network)** (5 tools)
- [`clear_cookies`](#clear_cookies)
- [`get_cookies`](#get_cookies)
- [`get_network_request`](#get_network_request)
- [`list_network_requests`](#list_network_requests)
- [`set_cookie`](#set_cookie)
- **[Debugging](#debugging)** (9 tools)
- [`evaluate_script`](#evaluate_script)
- [`get_console_message`](#get_console_message)
Expand Down Expand Up @@ -108,7 +111,7 @@

- **pageId** (number) **(required)**: Targets a specific page by ID.
- **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot
- **value** (string) **(required)**: The value to [`fill`](#fill) in. "true" or "false" for checkboxes and toggles, "true" for radio buttons.
- **value** (string) **(required)**: The value to [`fill`](#fill) in. "true" or "false" for checkboxes and toggles, "true" for radio buttons. May contain `{{secret:NAME}}`, which is replaced with the contents of ~/.local/share/chrome-devtools-mcp/secrets/NAME by this MCP server just before the input is sent to the browser, so the secret never has to be passed to this tool. Stage such files by reference (e.g. `pass show x > ~/.local/share/chrome-devtools-mcp/secrets/x`), never by writing the literal value. The secret file is DELETED once the call succeeds; append `:keep` (`{{secret:NAME:keep}}`) to keep it for later calls. Append `:raw` (`{{secret:NAME:raw}}`, `{{secret:NAME:raw:keep}}`) to keep a trailing newline. The directory can be relocated with the CHROME_DEVTOOLS_MCP_SECRETS_DIR environment variable. ALWAYS pick a unique, specific NAME (e.g. "github-login-7f3a" rather than "pw"): ~/.local/share/chrome-devtools-mcp/secrets is shared by all MCP servers running in parallel, so a generic name can be overwritten by another session and make you [`fill`](#fill) the wrong value, or be deleted while you still need it.
- **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false.

---
Expand Down Expand Up @@ -168,7 +171,7 @@
**Parameters:**

- **pageId** (number) **(required)**: Targets a specific page by ID.
- **text** (string) **(required)**: The text to type
- **text** (string) **(required)**: The text to type. May contain `{{secret:NAME}}`, which is replaced with the contents of ~/.local/share/chrome-devtools-mcp/secrets/NAME by this MCP server just before the input is sent to the browser, so the secret never has to be passed to this tool. Stage such files by reference (e.g. `pass show x > ~/.local/share/chrome-devtools-mcp/secrets/x`), never by writing the literal value. The secret file is DELETED once the call succeeds; append `:keep` (`{{secret:NAME:keep}}`) to keep it for later calls. Append `:raw` (`{{secret:NAME:raw}}`, `{{secret:NAME:raw:keep}}`) to keep a trailing newline. The directory can be relocated with the CHROME_DEVTOOLS_MCP_SECRETS_DIR environment variable. ALWAYS pick a unique, specific NAME (e.g. "github-login-7f3a" rather than "pw"): ~/.local/share/chrome-devtools-mcp/secrets is shared by all MCP servers running in parallel, so a generic name can be overwritten by another session and make you [`fill`](#fill) the wrong value, or be deleted while you still need it.
- **submitKey** (string) _(optional)_: Optional key to press after typing. E.g., "Enter", "Tab", "Escape"

---
Expand Down Expand Up @@ -343,6 +346,31 @@

## Network

### `clear_cookies`

**Description:** Deletes cookies from the browser. Pass 'name' and/or 'domain' to delete matching cookies, or 'all' to clear every cookie in the browser. Use this to return to a signed-out or first-visit state.

**Parameters:**

- **pageId** (number) **(required)**: Targets a specific page by ID.
- **all** (boolean) _(optional)_: Set to true to delete EVERY cookie in the browser. This signs the user out of every site, so it is required when no other filter is given, to make a full wipe explicit.
- **domain** (string) _(optional)_: Only delete cookies for this domain and its subdomains, e.g. "example.com" also matches "www.example.com" but not "notexample.com" (case-insensitive).
- **name** (string) _(optional)_: Only delete cookies with this exact name.

---

### `get_cookies`

**Description:** Gets all cookies stored in the browser's default context and writes them, including their values, to a JSON file. The tool reports only which cookies were found (name, domain, and security metadata); cookie values are never returned inline and only exist in the file.

**Parameters:**

- **filePath** (string) **(required)**: The absolute or relative path to a .json file to write the cookies (including their values) to.
- **pageId** (number) **(required)**: Targets a specific page by ID.
- **domain** (string) _(optional)_: Only return cookies for this domain and its subdomains, e.g. "example.com" also matches "www.example.com" (case-insensitive). When omitted, returns all cookies.

---

### `get_network_request`

**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).
Expand Down Expand Up @@ -370,6 +398,25 @@

---

### `set_cookie`

**Description:** Sets a cookie in the browser. Either 'url' or 'domain' must be given. Use this to restore a session, toggle a feature flag, or reproduce a state that depends on a specific cookie.

**Parameters:**

- **name** (string) **(required)**: The name of the cookie.
- **pageId** (number) **(required)**: Targets a specific page by ID.
- **value** (string) **(required)**: The value of the cookie. A cookie value is often a session token, and this parameter is recorded in the conversation transcript, so prefer a placeholder. May contain `{{secret:NAME}}`, which is replaced with the contents of ~/.local/share/chrome-devtools-mcp/secrets/NAME by this MCP server just before the input is sent to the browser, so the secret never has to be passed to this tool. Stage such files by reference (e.g. `pass show x > ~/.local/share/chrome-devtools-mcp/secrets/x`), never by writing the literal value. The secret file is DELETED once the call succeeds; append `:keep` (`{{secret:NAME:keep}}`) to keep it for later calls. Append `:raw` (`{{secret:NAME:raw}}`, `{{secret:NAME:raw:keep}}`) to keep a trailing newline. The directory can be relocated with the CHROME_DEVTOOLS_MCP_SECRETS_DIR environment variable. ALWAYS pick a unique, specific NAME (e.g. "github-login-7f3a" rather than "pw"): ~/.local/share/chrome-devtools-mcp/secrets is shared by all MCP servers running in parallel, so a generic name can be overwritten by another session and make you [`fill`](#fill) the wrong value, or be deleted while you still need it.
- **domain** (string) _(optional)_: The cookie domain, e.g. "example.com" or ".example.com" to include subdomains. Either this or "url" is required.
- **expires** (number) _(optional)_: Expiry as seconds since the UNIX epoch. Omit to create a session cookie that is dropped when the browser closes.
- **httpOnly** (boolean) _(optional)_: Whether the cookie is inaccessible to JavaScript.
- **path** (string) _(optional)_: The cookie path. Defaults to "/" when a domain is given.
- **sameSite** (enum: "Strict", "Lax", "None") _(optional)_: The SameSite policy. "None" requires secure to be true.
- **secure** (boolean) _(optional)_: Whether the cookie is only sent over HTTPS.
- **url** (string) _(optional)_: The request URI to associate the cookie with, which sets its domain and path. Either this or "domain" is required.

---

## Debugging

### `evaluate_script`
Expand All @@ -381,6 +428,8 @@
- **function** (string) **(required)**: A JavaScript function declaration to be executed by the tool in the target page.
Example without arguments: `() => document.title` or `async () => await fetch("example.com")`.
Example with arguments: `(el) => el.innerText`
May contain `{{secret:NAME}}`, which is replaced with the contents of ~/.local/share/chrome-devtools-mcp/secrets/NAME by this MCP server just before the input is sent to the browser, so the secret never has to be passed to this tool. Stage such files by reference (e.g. `pass show x > ~/.local/share/chrome-devtools-mcp/secrets/x`), never by writing the literal value. The secret file is DELETED once the call succeeds; append `:keep` (`{{secret:NAME:keep}}`) to keep it for later calls. Append `:raw` (`{{secret:NAME:raw}}`, `{{secret:NAME:raw:keep}}`) to keep a trailing newline. The directory can be relocated with the CHROME_DEVTOOLS_MCP_SECRETS_DIR environment variable. ALWAYS pick a unique, specific NAME (e.g. "github-login-7f3a" rather than "pw"): ~/.local/share/chrome-devtools-mcp/secrets is shared by all MCP servers running in parallel, so a generic name can be overwritten by another session and make you [`fill`](#fill) the wrong value, or be deleted while you still need it.
To run the same code repeatedly without writing it out in every call, store it in ~/.local/share/chrome-devtools-mcp/scripts/NAME and pass `{{script:NAME}}`, which this MCP server replaces with that file's contents exactly as stored. A script takes no modifiers: it is never deleted, and it is never trimmed.

- **pageId** (number) **(required)**: Targets a specific page by ID.
- **args** (array) _(optional)_: An optional list of arguments to pass to the function.
Expand Down
22 changes: 11 additions & 11 deletions skills/cookie-debugging/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: Uses Chrome DevTools MCP for inspecting, debugging, and testing coo

Cookies marked `HttpOnly` cannot be accessed or modified by client-side JavaScript (`cookieStore` or `document.cookie`). However, the browser **automatically attaches active HttpOnly cookies to outgoing HTTP request headers (`Cookie`)**.

- To inspect current `HttpOnly` values: Look at the `Cookie` request header of any outgoing HTTP request via `get_network_request`.
- To inspect current `HttpOnly` values: Use `get_cookies`, which reads them via CDP and is not subject to the JavaScript restriction. Alternatively, look at the `Cookie` request header of any outgoing HTTP request via `get_network_request`.
- To inspect how cookies were created or configured: Look at the `Set-Cookie` response header of login/auth responses.
- To inspect non-`HttpOnly` cookies: Use `evaluate_script` with the modern `cookieStore` API (`async () => await cookieStore.getAll()`).

Expand All @@ -24,16 +24,16 @@ Choose the right session environment to avoid state contamination (e.g., residua

### Client-Side Capabilities & Limitations

| Action | Client JavaScript (`cookieStore` / `document.cookie`) | DevTools Network & Context Tools |
| :--------------------------------------------------------------- | :---------------------------------------------------- | :------------------------------------------------------ |
| **Read Non-HttpOnly** | ✅ `async () => await cookieStore.getAll()` | ✅ `get_network_request` (Request `Cookie`) |
| **Read HttpOnly** | ❌ Blocked by browser security | ✅ `get_network_request` (Request `Cookie`) |
| **Inspect Attributes** (`Domain`, `Path`, `SameSite`, `Expires`) | ✅ `async () => await cookieStore.getAll()` | ✅ `get_network_request` (Response `Set-Cookie`) |
| **Modify / Delete Non-HttpOnly** | ✅ `async () => await cookieStore.set(...)` | N/A |
| **Modify / Delete HttpOnly** | ❌ **Silent failure** in JavaScript | ✅ Use `new_page(isolatedContext: ...)` for clean state |
| Action | Client JavaScript (`cookieStore` / `document.cookie`) | DevTools Network & Context Tools |
| :--------------------------------------------------------------- | :---------------------------------------------------- | :------------------------------------------------------------------------------------- |
| **Read Non-HttpOnly** | ✅ `async () => await cookieStore.getAll()` | ✅ `get_network_request` (Request `Cookie`) |
| **Read HttpOnly** | ❌ Blocked by browser security | ✅ `get_cookies`, or `get_network_request` (Request `Cookie`) |
| **Inspect Attributes** (`Domain`, `Path`, `SameSite`, `Expires`) | ✅ `async () => await cookieStore.getAll()` | ✅ `get_network_request` (Response `Set-Cookie`) |
| **Modify / Delete Non-HttpOnly** | ✅ `async () => await cookieStore.set(...)` | ✅ `set_cookie` / `clear_cookies` |
| **Modify / Delete HttpOnly** | ❌ **Silent failure** in JavaScript | ✅ `set_cookie` / `clear_cookies`, or `new_page(isolatedContext: ...)` for clean state |

> [!WARNING]
> Attempting to clear an `HttpOnly` cookie via JavaScript (`cookieStore.delete` or `document.cookie = "...; max-age=0"`) will silently fail. To test in an unauthenticated or fresh state, always spawn a new isolated context using `new_page` with `isolatedContext`.
> Attempting to clear an `HttpOnly` cookie via JavaScript (`cookieStore.delete` or `document.cookie = "...; max-age=0"`) will silently fail. Use `clear_cookies`, which deletes via CDP, or spawn a new isolated context with `new_page` and `isolatedContext` for a guaranteed clean slate. Note that `get_cookies`, `set_cookie` and `clear_cookies` act on the default browser context, not on an isolated one.

---

Expand Down Expand Up @@ -140,8 +140,8 @@ For client-accessible, non-`HttpOnly` cookies (e.g., UI preferences, non-sensiti

- **`cookieStore` is undefined**: `cookieStore` requires a Secure Context (`https://`, `localhost`, or `127.0.0.1`). On non-secure HTTP origins, use `() => document.cookie` or test over HTTPS.
- **`evaluate_script` returns empty / unresolved Promise**: `cookieStore` methods are asynchronous. Always wrap calls with `async () => await cookieStore.getAll()`.
- **Cookie not visible in JavaScript**: The cookie is marked `HttpOnly`. Trigger a network request and call `get_network_request` to view it in the `Cookie` request header.
- **JavaScript deletion did not remove cookie**: The cookie is `HttpOnly` or requires matching `Path` and `Domain` parameters. Use a fresh `isolatedContext` with `new_page` for a clean slate.
- **Cookie not visible in JavaScript**: The cookie is marked `HttpOnly`. Call `get_cookies`, or trigger a network request and call `get_network_request` to view it in the `Cookie` request header.
- **JavaScript deletion did not remove cookie**: The cookie is `HttpOnly` or requires matching `Path` and `Domain` parameters. Use `clear_cookies`, or a fresh `isolatedContext` with `new_page` for a clean slate.
- **Cookie set in response but not sent in requests**:
- Verify if page is `http://` while cookie specifies `Secure`.
- Check if `Domain` restricts subdomains.
Expand Down
Loading
Loading