@drupal-forge/server-web-observe-capture
v0.1.11
Published
Headless browser observation: screenshots, DOM excerpts, layout, baseline compare (not theme registry)
Readme
👁 Web Observe & Capture (Spec 07)
MCP server for visual truth and DOM-level observation. It provides screenshot capabilities and lightweight DOM inspection to help agents verify frontend state.
✨ Features
- Session-Aware Browser: Maintain state (auth, cookies) across multiple tool calls via
session_id. - Actor-Aware Sessions: Open anonymously (default) or, on allowlisted environments, as an authenticated actor. Credentials, tokens, cookies, and one-time login URLs are never returned or logged.
- Precise Screenshots: Capture full page, specific viewports, or targeted regions/selectors.
- Real Baseline Comparison: Perceptual screenshot diff against a baseline PNG with an actual
PASS/FAIL/BLOCKEDverdict and diff artifacts. - Bounded DOM Inspection: Retrieve HTML/Text excerpts with automatic truncation to save tokens.
- Geometric Analysis: Inspect bounding boxes, styles, and visibility for layout debugging.
- Structural Snapshots: Get a narrative summary of the page structure without raw HTML noise.
🧰 Available Tools (9)
| Tool | Purpose |
|---|---|
| open_page_session | Start a new browser session for a URL, optionally as an authenticated actor. |
| capture_full_page_screenshot | High-resolution capture of the entire page. |
| capture_viewport_screenshot | Capture only what is currently visible. |
| capture_region_screenshot | Capture specific coordinates or a selector. |
| capture_page_snapshot | Narrative summary of top DOM nodes. |
| inspect_dom_excerpt | Targeted HTML/Text retrieval (bounded). |
| inspect_layout | Audit bounding boxes and CSS styles. |
| compare_screenshot_baseline | Perceptual diff of the current page against a baseline PNG. |
| close_page_session | Cleanup browser resources. |
🔐 Actor Contract & Environment Allowlisting
open_page_session accepts an optional Actor contract. Anonymous is always the default and needs no configuration. Authenticated actors are gated by .web-observe-capture.json (see .web-observe-capture.example.json):
anonymous— always permitted, on any URL.ephemeral_account— permitted only onlocal-class (local/disposable) environments that enable thedrush_ulidriver. Uses a Drush one-time login link (never returned or logged).actor_idis a uid or username.preprovisioned_account— permitted only on approvedshared_non_productionenvironments. Credentials are resolved from environment-variable references (username_env/password_env) — never raw values in config.actor_idis the account reference name.- Production-class environments strictly prohibit authenticated sessions.
When a requested authenticated actor cannot be satisfied, open_page_session opens no session and returns a distinct status so traceability can tell the cases apart:
| Status | Meaning |
|---|---|
| ENVIRONMENT_NOT_ALLOWLISTED | URL matches no allowlisted environment, the actor type is not allowed there, or it is a production-class target. |
| DRIVER_UNAVAILABLE | The required auth driver (e.g. Drush) could not run. |
| ACCOUNT_NOT_CONFIGURED | The referenced account or its secret references are missing/unset. |
Session metadata records actor_type, provenance (anonymous / ephemeral_account / preprovisioned_account), and role — never a credential.
Tool Schema Details
1. open_page_session
Starts a browser session for a given URL and returns a session_id used by all subsequent tools.
- Parameters:
url(string, required): Absolute HTTP(S) URL.wait_until(string, optional, default"networkidle"): Playwright wait state:"load"|"domcontentloaded"|"networkidle"|"commit".width(number, optional, default1280): Viewport width in px.height(number, optional, default720): Viewport height in px.actor_type(string, optional, default"anonymous"):"anonymous"|"ephemeral_account"|"preprovisioned_account". See the Actor Contract section.actor_id(string, optional): Forephemeral_account, a uid or username; forpreprovisioned_account, the account reference name. Never pass raw credentials.
- Returns:
{ "session_id": "uuid-string-here", "url": "https://example.com", "title": "Example Page", "viewport": { "width": 1280, "height": 720 }, "actor": { "actor_type": "anonymous", "provenance": "anonymous", "role": null, "environment_id": null, "environment_class": null } }
2. capture_full_page_screenshot
Screenshots the entire scrollable page, including content below the fold.
- Parameters:
session_id(string, required): Session UUID fromopen_page_session.
- Returns:
{ "image_path": "/absolute/path/to/artifacts/full_page_123456789_abcd1234.png", "width": 1280, "height": 2450, "full_page": true }
3. capture_viewport_screenshot
Screenshots only the currently visible viewport at the active scroll position.
- Parameters:
session_id(string, required): Session UUID fromopen_page_session.
- Returns:
{ "image_path": "/absolute/path/to/artifacts/viewport_123456789_abcd1234.png", "width": 1280, "height": 720 }
4. capture_region_screenshot
Screenshots a specific DOM element (via selector) or pixel rectangle (via coordinates).
- Parameters:
session_id(string, required): Session UUID fromopen_page_session.selector(string, optional): CSS selector of the element. Takes precedence.x(number, optional): Clip origin X in px (requiresy).y(number, optional): Clip origin Y in px (requiresx).width(number, optional, default100): Clip width in px.height(number, optional, default100): Clip height in px.
- Behavior / Errors:
- Throws an error if
selectoris provided but not found on the page or lacks a bounding box. - Throws an error if coordinate validation fails (e.g. only one of
xoryis provided). - Throws an error if neither
selectornor coordinates are supplied.
- Throws an error if
- Returns:
{ "image_path": "/absolute/path/to/artifacts/region_123456789_abcd1234.png", "bounds": { "x": 100, "y": 150, "width": 400, "height": 300 }, "source": "selector:.main-content" // or "coordinates" }
5. inspect_dom_excerpt
Gets HTML content for one CSS selector, with character-limit truncation.
- Parameters:
session_id(string, required): Session UUID fromopen_page_session.selector(string, optional, default"body"): CSS selector to retrieve.max_chars(number, optional, default2000): Character limit before truncation.include_outer_html(boolean, optional, defaultfalse):truefor outerHTML,falsefor innerHTML.
- Returns:
- If found:
{ "source": "body", "found": true, "excerpt": "<div>...</div>", "truncated": false } - If not found:
{ "source": ".missing-selector", "found": false }
- If found:
6. inspect_layout
Audits bounding boxes and computed styles (display, visibility, opacity, z-index, overflow) per selector.
- Parameters:
session_id(string, required): Session UUID.selectors(array of strings, required): CSS selectors to inspect.
- Returns:
{ "inspected_count": 2, "items": [ { "selector": ".card", "found": true, "bounds": { "x": 50, "y": 120, "width": 300, "height": 200 }, "styles": { "display": "block", "visibility": "visible", "opacity": "1", "zIndex": "auto", "overflow": "hidden" } }, { "selector": "#not-found", "found": false } ] }
7. capture_page_snapshot
Generates a token-efficient DOM outline showing element hierarchy, tag, ID, classes, and text preview.
- Parameters:
session_id(string, required): Session UUID.max_nodes(number, optional, default100): Max DOM nodes to outline.
- Returns:
{ "node_count": 5, "summary": "Page snapshot with 5 nodes (capped at 100)", "excerpt": [ { "tag": "html", "id": "", "classes": [], "text_preview": "" }, { "tag": "body", "id": "content", "classes": ["dark"], "text_preview": "Hello world!" } ] }
8. compare_screenshot_baseline
Captures the current page and runs a real perceptual diff (pixelmatch) against a baseline PNG.
- Parameters:
session_id(string, required): Session UUID.baseline_path(string, required): Absolute path to the baseline reference PNG.tolerance(number, optional, default0.05): Aggregate diff tolerance0..1.PASSonly when dimensions match and the diff ratio is<= tolerance.full_page(boolean, optional, defaultfalse): Compare the full scrollable page instead of the viewport.
- Behavior:
- A missing or unreadable baseline returns
verdict: "BLOCKED"— never a silentPASS. - A dimension mismatch is a real
FAIL(the overlap is still diffed and reported). - A capture without comparison cannot independently produce an automated
PASS.
- A missing or unreadable baseline returns
- Returns:
{ "session_id": "uuid-string-here", "verdict": "FAIL", "match": false, "diff_percentage": 0.1234, "diff_pixels": 12340, "total_pixels": 100000, "tolerance": 0.05, "dimensions_match": true, "current_path": "/abs/artifacts/viewport_...png", "baseline_path": "/abs/baseline.png", "diff_path": "/abs/artifacts/viewport_..._diff.png", "viewport": { "width": 1280, "height": 720 }, "browser": { "name": "chromium", "version": "..." }, "platform": "linux", "actor": { "actor_type": "anonymous", "provenance": "anonymous", "role": null } }
9. close_page_session
Closes the Playwright browser context and releases all session resources.
- Parameters:
session_id(string, required): Session UUID to close.
- Returns:
{ "session_id": "uuid-string-here", "closed": true }
🚀 Installation & Configuration
Via npm (Recommended)
Install the server globally:
npm install -g @drupal-forge/server-web-observe-captureAdd the following to your MCP client configuration (e.g.,
claude_desktop_config.jsonor Cursor settings):{ "mcpServers": { "web-observe-capture": { "command": "npx", "args": [ "-y", "@drupal-forge/server-web-observe-capture" ] } } }
Note: Playwright requires browsers to be installed. You may need to run npx playwright install chromium if it is not already installed.
🎬 Exploratory Demo Scenario
Follow this step-by-step developer journey to explore the visual observation, structural analysis, layout debugging, and screenshot capture capabilities of the Web Observe & Capture server. This demo showcases how to inspect page hierarchy, analyze element positions, and capture high-fidelity visual context for front-end understanding.
1. Initiating the Interactive Browser Session
To begin exploring, establish a persistent browser context for your target web page. This session preserves page state, cookies, and login configurations across subsequent requests.
[!TIP] Prior to opening the session, the agent should dynamically inspect the local workspace to extract the site URL (e.g., by querying local dev configurations like
lando infoorddev describeif available). Fall back tohttps://example.comif no local URL can be determined.
- Tool:
open_page_session- Parameters:
{ "url": "https://my-local-project.ddev.site", // dynamically determined local URL, fallback: "https://example.com" "wait_until": "networkidle", "width": 1280, "height": 720 } - Insight: This tool launches a browser instance, navigates to the URL, and yields a
session_idto link all upcoming interactions to the same session.
- Parameters:
2. Inspecting the Page's Structural Outline
Before requesting full screenshots or parsing large chunks of HTML, retrieve a lightweight outline of the page's structure. This saves token budget and clarifies layout organization.
- Tool:
capture_page_snapshot- Parameters:
{ "session_id": "YOUR_SESSION_UUID", "max_nodes": 50 } - Insight: You receive a structured hierarchy of critical DOM nodes including tag names, IDs, CSS classes, and preview text, offering a quick understanding of the page layout.
- Parameters:
3. Deep-Diving Into Target HTML Excerpts
When you locate a specific element (like a header, card, or dashboard section) in the outline, fetch its precise inner or outer HTML block.
- Tool:
inspect_dom_excerpt- Parameters:
{ "session_id": "YOUR_SESSION_UUID", "selector": "main section.features", "max_chars": 1500, "include_outer_html": true } - Insight: This tool retrieves the specific node's HTML content. If the content exceeds your character budget, it truncates gracefully with a
truncated: trueflag.
- Parameters:
4. Auditing Layout Styles and Coordinates
If you are inspecting layout alignment, margins, or element overlays, retrieve the computed layout details and visibility properties.
- Tool:
inspect_layout- Parameters:
{ "session_id": "YOUR_SESSION_UUID", "selectors": ["header.navigation", "main section.features"] } - Insight: This tool returns the exact coordinates, width, height, and CSS alignment properties (visibility, display, overflow, opacity, z-index) for the queried selectors.
- Parameters:
5. Capturing Visual Screenshots
Now let's capture visual images of the page. You can capture the active viewport, the entire scrollable document, or crop specifically to a given element.
Tool:
capture_viewport_screenshot- Parameters:
{ "session_id": "YOUR_SESSION_UUID" } - Insight: Captures exactly what is visible in the current browser viewport.
- Parameters:
Tool:
capture_full_page_screenshot- Parameters:
{ "session_id": "YOUR_SESSION_UUID" } - Insight: Automatically scrolls and captures the complete document from top to bottom, returning the filesystem path to the saved image.
- Parameters:
Tool:
capture_region_screenshot- Parameters:
{ "session_id": "YOUR_SESSION_UUID", "selector": "main section.features" } - Insight: Isolates and crops the screenshot exactly to the bounding box of the specified CSS selector (or coordinates), avoiding surrounding visual clutter.
- Parameters:
6. Closing the Browser Session
When your journey is complete, clean up and release the browser resources.
- Tool:
close_page_session- Parameters:
{ "session_id": "YOUR_SESSION_UUID" } - Insight: Shuts down the browser context, ensuring system memory and resources are immediately reclaimed.
- Parameters:
