{"id":"9ee696e5-15b0-410c-9c03-fa803fb9db8c","slug":"pxtree","kind":"mcp","name":"pxtree","summary":"Rendered-page measurements as text, for coding agents","owner":"alabsi91","repoUrl":"https://github.com/alabsi91/pxtree","license":"MIT","licenseTier":"permissive","trust":"community","stars":0,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-09-23T15:57:08.000Z","addedAt":"2026-10-14T05:24:03.684Z","logoUrl":"https://avatars.githubusercontent.com/u/58077449?u=068cbcaef84cedd5f32e17a3d0f27ed737e2d56b&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["utilities"],"tags":[],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-21T01:32:59.025Z","sample":false,"url":"https://conduits.dev/tools/pxtree","language":"TypeScript","transports":["stdio"],"install":{"npm":"pxtree"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# pxtree\n\nLoads a webpage in headless Chromium and prints what actually rendered as a compact text tree, so an AI coding agent can check a layout without a screenshot.\n\nA dev server page with a row of three cards, a row of three plans, a list of steps, a row of stats, an article, a form, a column of prices and a row of tiles:\n\n```\n$ npx -y pxtree@latest localhost:5173\n1280x800 light dpr 1 ltr scroll 0/382 page 1280x1182 painted to 1134\nsince last run: no changes\nsummary: 7 findings\n  a.button tops 64..136 across siblings: ul.cards\n  div.stat tops 0..6 across siblings: div.stats\n  input.field starts 0..3 across siblings: form.fields\n  12 wider than li.plan: li.plan.featured\n  72 taller than li.card: li.card 3 of 3\n  20 shorter than div.tile: div.tile.short\n  gaps 16 16 24 16 between li.step: ul.steps\nbody 1280x1182 [pad 24][gaps 24 24 24 24 16 16 …, free 24 at end]\n  ul.cards 1232x208 [gaps across 16, free 600 at end][!! a.button tops 64..136 across siblings]\n    li.card 200x136 [pad 16][gaps 8][renders background]\n      h3 \"Starter\" 168x24 [text 19/24]\n      p \"Short text.\" 168x24 @0,32 [text 16/24]\n      a.button \"Choose\" 168x40 @0,64 [pad 8 16][text 16/24][renders background]\n    li.card 200x136 @216,0 [pad 16][gaps 8][renders background]\n      h3 \"Team\" 168x24 [text 19/24]\n      p \"Short text.\" 168x24 @0,32 [text 16/24]\n      a.button \"Choose\" 168x40 @0,64 [pad 8 16][text 16/24][renders background]\n    li.card 200x208 @432,0 [pad 16][gaps 8][renders background][!! 72 taller than li.card]\n      h3 \"Business\" 168x24 [text 19/24]\n      p \"A much longer…\" 168x96 @0,32 [text 16/24, 4 lines, 444 on one line]\n      a.button \"Choose\" 168x40 @0,136 [pad 8 16][text 16/24][renders background]\n  ul.plans 1232x56 @0,232 [gaps across 16, free 588 at end]\n    li.plan \"Monthly\" 200x56 [pad 16][text 16/24][renders background]\n    li.plan \"Yearly\" 200x56 @216,0 [pad 16][text 16/24][renders background]\n    li.plan.featured \"Lifetime\" 212x56 @432,0 [pad 16][text 16/24][renders background][!! 12 wider than li.plan]\n  ul.steps 1232x192 @0,312 [gaps 16 16 24 16][!! gaps 16 16 24 16 between li.step]\n    li.step \"One\" 1232x24 [text 16/24]\n    li.step \"Two\" 1232x24 @0,40 [text 16/24]\n    li.step.active \"Three\" 1232x24 @0,80 [text 16/24]\n    li.step \"Four\" 1232x24 @0,128 [text 16/24]\n    li.step \"Five\" 1232x24 @0,168 [text 16/24]\n  div.stats 1232x62 @0,528 [gaps across 16, free 456 at end][!! div.stat tops 0..6 across siblings]\n    div.stat \"Revenue\" 182x56 [pad 16][text 16/24][renders background]\n    div.stat \"Orders\" 182x56 @198,0 [pad 16][text 16/24][renders background]\n    div.stat.alert \"Refunds\" 182x56 @396,6 [pad 16][text 16/24][renders background]\n    div.stat \"Customers\" 182x56 @594,0 [pad 16][text 16/24][renders background]\n  article 1232x132 @0,614 [gaps 12]\n    h2 \"First heading\" 1232x24 [text 24/24]\n    p \"First paragraph.\" 1232x24 @0,36 [text 16/24]\n    h2 \"Second heading\" 1232x24 @0,72 [text 24/24]\n    p \"Second paragraph.\" 1232x24 @0,108 [text 16/24]\n  p.with-link \"Read the first.\" 1232x24 @0,762 [text 16/24]\n    a \"guide\" 39x17 @69,3 [text 16/24]\n  form.fields 1232x108 @0,802 [gaps 8][!! input.field starts 0..3 across siblings]\n    input.field \"Name\" 208x21 [pad 1 2][text 13/15][renders background, border]\n    input.field \"Email\" 208x21 @0,29 [pad 1 2][text 13/15][renders background, border]\n    input.field.nudged \"Company\" 208x21 @3,58 [pad 1 2][text 13/15][renders background, border]\n    input.field \"Phone\" 208x21 @0,87 [pad 1 2][text 13/15][renders background, border]\n  div.prices 300x72 @0,934\n    span.price \"$9\" 18x24 @282,0 [text 16/24]\n    …×2 similar span.price 36x24..49x24\n  div.tiles 1232x80 @0,1030 [gaps across 16, free 900 at end]\n    div.tile 100x80 [renders background]\n    div.tile.short 100x60 @116,0 [renders background][!! 20 shorter than div.tile]\n    div.tile 100x80 @232,0 [renders background]\n```\n\nThe first line is the facts line: viewport, color scheme, device pixel ratio, direction, scroll position out of the maximum, document size, and `painted to`, the lowest painted pixel. `since last run` compares with the previous run of the same URL and settings, or of the same `--diff-key` name. That history lives in `~/.cache/pxtree`, never in your project, and `--no-diff` skips it.\n\nThe `ul.cards` line says the \"Choose\" buttons in the three cards have tops from 64 px to 136 px below the top of their card. The `li.plan.featured \"Lifetime\"` line says that plan is 212x56 at x 432 in its row, 12 px wider than the width its siblings share. Findings are measurements with a threshold, never verdicts. The agent decides from the code whether they are intended.\n\n## Why not a screenshot\n\n- It gives numbers in CSS px, not pixels the model has to estimate.\n- The agent reads it in one pass, as text, with the findings listed on top.\n- One call covers several viewports and color schemes.\n\n## Install\n\npxtree drives Playwright's Chromium. Install it once, about 150 MB:\n\n```\nnpx -y playwright@1.63.0 install chromium\n```\n\npxtree itself downloads nothing and never edits your project. `--channel chrome` uses an installed Chrome instead. Needs Node 20 or newer.\n\n### MCP server\n\n| Client          | Install                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Scope                                           |\n| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |\n| Codex           | `codex mcp add pxtree -- npx -y pxtree@latest mcp`                                                                                                                                                                                                                                                                                                                                                                                                         | user, `~/.codex/config.toml`                    |\n| Claude Code     | `claude mcp add --scope user pxtree -- npx -y pxtree@latest mcp`                                                                                                                                                                                                                                                                                                                                                                                           | user. `--scope project` writes `.mcp.json`      |\n| Cursor          | [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=pxtree&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInB4dHJlZUBsYXRlc3QiLCJtY3AiXX0%3D) or `~/.cursor/mcp.json` / `.cursor/mcp.json`                                                                                                                                                                                                        | user / project                                  |\n| VS Code Copilot | [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square)](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522pxtree%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522pxtree%2540latest%2522%252C%2522mcp%2522%255D%257D) or `code --add-mcp '{\"name\":\"pxtree\",\"command\":\"npx\",\"args\":[\"-y\",\"pxtree@latest\",\"mcp\"]}'` | user. `.vscode/mcp.json` for project, see below |\n| OpenCode        | `~/.config/opencode/opencode.json` or `opencode.json`, see below                                                                                                                                                                                                                                                                                                                                                                                           | user / project                                  |\n\nOpenCode, `opencode.json`:\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"mcp\": {\n    \"pxtree\": { \"type\": \"local\", \"command\": [\"npx\", \"-y\", \"pxtree@latest\", \"mcp\"], \"enabled\": true }\n  }\n}\n```\n\n<details>\n<summary>Other clients and config files</summary>\n\nAny MCP client that reads `mcpServers`:\n\n```json\n{\n  \"mcpServers\": {\n    \"pxtree\": { \"command\": \"npx\", \"args\": [\"-y\", \"pxtree@latest\", \"mcp\"] }\n  }\n}\n```\n\n| Client                                    | Command or config path                                                                                                                       | Scope                                                    |\n| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| Claude Code plugin (MCP server and skill) | `/plugin marketplace add alabsi91/pxtree`, then `/plugin install pxtree@pxtree`                                                              | user                                                     |\n| Gemini CLI                                | `gemini mcp add -s user pxtree npx -y pxtree@latest mcp`                                                                                     | user. Without `-s user`: project `.gemini/settings.json` |\n| Devin (formerly Windsurf)                 | `devin mcp add pxtree -- npx -y pxtree@latest mcp`, files `~/.config/devin/mcp_config.json` / `.devin/mcp_config.json`                       | user / project                                           |\n| Amp                                       | `amp mcp add pxtree -- npx -y pxtree@latest mcp`, files `~/.config/amp/settings.json` / `.amp/settings.json`, see below                      | user / project                                           |\n| Zed                                       | `~/.config/zed/settings.json`, see below                                                                                                     | user                                                     |\n| Cline                                     | CLI: `cline mcp` or `~/.cline/mcp.json`. VS Code extension: Cline panel, MCP Servers, Configure                                              | user                                                     |\n| Roo Code                                  | `.roo/mcp.json`, or `mcp_settings.json` from Roo's settings                                                                                  | project / user                                           |\n| JetBrains AI Assistant                    | Settings, Tools, AI Assistant, Model Context Protocol (MCP): paste the JSON above. The file path is not documented, check your client's docs | user / project                                           |\n\nVS Code, `.vscode/mcp.json`:\n\n```json\n{\n  \"servers\": {\n    \"pxtree\": { \"type\": \"stdio\", \"command\": \"npx\", \"args\": [\"-y\", \"pxtree@latest\", \"mcp\"] }\n  }\n}\n```\n\nAmp, `settings.json`:\n\n```json\n{\n  \"amp.mcpServers\": {\n    \"pxtree\": { \"command\": \"npx\", \"args\": [\"-y\", \"pxtree@latest\", \"mcp\"] }\n  }\n}\n```\n\nZed, `settings.json`:\n\n```json\n{\n  \"context_servers\": {\n    \"pxtree\": { \"command\": \"npx\", \"args\": [\"-y\", \"pxtree@latest\", \"mcp\"], \"env\": {} }\n  }\n}\n```\n\n</details>\n\nMCP Registry name: `io.github.alabsi91/pxtree`. The server has two tools: `measure`, and `read_me_first`, which returns what the skill holds: how to use pxtree, how to report, and the reading guide. Its instructions tell the agent to call `read_me_first` once per session, so no skill install is needed.\n\n`measure` takes `target` and the CLI flags as inputs: `viewports`, `schemes`, `scroll`, `element`, `children`, `colors`, `wait`, `script`, `screenshot` (true saves a PNG per run under the temp directory), `timeout`, `maxChars` (default 80000), `diff`, `diffKey`, `report` (`tree`, `findings`, `summary`, `changes` or `none`) and `aria` (true adds the aria tree). One call can return the report, the aria tree and a screenshot together. Inputs are bounded: viewport sides 1 to 10000, at most 10 viewports, `timeout` at most 120000. A file target must sit under the server's working directory. That rule prevents accidents and is not a security boundary: `script` is trusted code that runs as Node in the server process.\n\n### Skill\n\nFor agents that read [Agent Skills](https://agentskills.io), Codex included through `.agents/skills`:\n\n```\nnpx -y skills add alabsi91/pxtree\n```\n\nOr link it by hand. `~/.agents/skills` is read by Codex, Cursor, Copilot, Gemini CLI, OpenCode, Amp and Devin. Claude Code reads `~/.claude/skills`.\n\n```\ngit clone https://github.com/alabsi91/pxtree\nmkdir -p ~/.agents/skills ~/.claude/skills\nln -s \"$PWD/pxtree/skills/pxtree\" ~/.agents/skills/pxtree\nln -s \"$PWD/pxtree/skills/pxtree\" ~/.claude/skills/pxtree\n```\n\nUse `<project>/.agents/skills` or `<project>/.claude/skills` for one project only.\n\n### Shell-only agents\n\nTell the agent to run this. It prints the flags, the output grammar, every tag and finding, and the limits.\n\n```\nnpx -y pxtree@latest guide\n```\n\n## CLI\n\n```\nnpx -y pxtree@latest https://example.com --viewport 390x844,1280x800 --scheme light,dark\nnpx -y pxtree@latest localhost:5173 --scroll '#pricing' --element '.card'\nnpx -y pxtree@latest ./dist/index.html\nnpx -y pxtree@latest localhost:5173 --script \"await page.click('text=Menu')\" --wait '.menu'\nnpx -y pxtree@latest localhost:5173 --element '#pricing' --screenshot pricing.png\nnpx -y pxtree@latest localhost:5173 --viewport 390,1280\nnpx -y pxtree@latest localhost:5173 --report summary --aria\nnpx -y pxtree@latest localhost:5173 --report findings\nnpx -y pxtree@latest localhost:5173 --report changes\nnpx -y pxtree@latest localhost:5173 --report none --screenshot shot.png\n```\n\nThe screenshot is clipped to the `--element` match only when the selector matches exactly one element. Otherwise it is the whole viewport, and the facts line says how many matched.\n\n`--report` picks how much of the measurement prints: `tree` (the default, everything), `findings` (the tree cut down to the lines with a finding and the names of their ancestors), `summary` (no tree), `changes` (the facts line and since last run, to check a fix) or `none` (the facts line only). A width alone in `--viewport` gets a matching height: 390x844, 768x1024, 820x1180, 1024x768, 1280x800, 1440x900, 1920x1080, and 800 for any other width. `--aria` adds Playwright's aria snapshot of the page, or of each `--element` match, after the report: roles, names, states and reading order, taken in the same state as the measurement. So one call can carry the findings, the accessibility tree and a screenshot.\n\n`npx -y pxtree@latest --help` lists the flags. `npx -y pxtree@latest guide` explains how to read the output.\n\n## Limits\n\n- Coverage is checked only inside the viewport.\n- Declarative closed shadow roots are not walked.\n- iframes are not walked. Measure the frame URL on its own.\n- Pseudo-element ink is placed on the element's box.\n- Clip paths count as their border box. Border radius is ignored.\n- Inside a rotated or scaled box there are no overlap, centering or width findings. Its axis-aligned boxes do not say where things are.\n- Contrast is not measured over images or gradients, where the `[text]` tag says `on image`, nor for a transparent text fill such as gradient text, where it says `fill transparent`.\n- `small target` skips links inside a line of text and targets with enough spacing, as WCAG 2.5.8 allows.\n- pxtree does not guess intent. A full-bleed section, an avatar stack, an open popover or a collapsed panel prints its numbers like anything else.\n- Desktop emulation only: `(hover: hover)` and `(pointer: fine)` match. No touch.\n- Vertical writing modes print physical positions.\n- Loading waits at most 2 s for `load` and 1.5 s for a quiet network, which files skip. Slower pages need `--wait`.\n\n## API\n\n```ts\nimport { createSession, format } from 'pxtree';\n\nconst session = await createSession();\nconst result = await session.measure('localhost:5173', { viewports: [{ width: 390, height: 844 }] });\nconsole.log(format(result));\nawait session.close();\n```\n\n## License\n\nMIT\n"}